/*
  MIT License

  Copyright (c) 2025-2026 Kay Kasper

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.
*/

#ifndef SW_ROTARYENCODER_H
#define SW_ROTARYENCODER_H

#include "Switch.h"

// Constants for the RotaryEncoderAnalyzer implementation based on SwitchStateAnalyzer
#define REA_STATE_E_OFF           0x00   // external relevant output state, no movement identified
#define REA_STATE_E_DIRECTION_R   0x01   // external relevant output state, right turn finished
#define REA_STATE_E_DIRECTION_L   0x02   // external relevant output state, left turn finished
#define REA_STATE_I_OFF           0x00   // internal state, raw state off, no direction
#define REA_STATE_I_RIGHT_A       0x01   // internal state, right turn started, raw state A first
#define REA_STATE_I_RIGHT_B       0x02   // internal state, right turn started, raw state B first
#define REA_STATE_I_RIGHT_AB      0x04   // internal state, right turn started, raw state AB
#define REA_STATE_I_LEFT_B        0x08   // internal state, left turn started, raw state B first
#define REA_STATE_I_LEFT_A        0x10   // internal state, left turn started, raw state A first
#define REA_STATE_I_LEFT_AB       0x20   // internal state, left turn started, raw state AB
#define REA_STATE_I_AB            0x40   // internal state, raw state A and B high, no direction
#define REA_STATE_I_UNDEFINED     0x80   // internal state, raw state undefined
#define REA_SWSTATE_OFF           0x00   // raw input state off, A and B low
#define REA_SWSTATE_A             0x01   // raw input state only A high
#define REA_SWSTATE_B             0x02   // raw input state only B high
#define REA_SWSTATE_AB            0x03   // raw input state A and B high


/*
  Implementation of a rotary encoder specific state analyzer.
  
  This analyzer is waiting till the end of the full cycle to generate an external
  state change and is NOT expecting the full sequence of input state changes.
  
  Analysis of rotary encoder input signals from 2 digital pins (4 raw states)
  to deliver the information (3 states) which movements were done:
  
  no movement (REA_STATE_E_OFF)
  right turn one step (REA_STATE_E_DIRECTION_R)
  left turn on step (REA_STATE_E_DIRECTION_L)
  
  Prerequisite are the 4 raw input states
  REA_SWSTATE_OFF, REA_SWSTATE_A, REA_SWSTATE_B, REA_SWSTATE_AB
  
  The return of an REA_STATE_E_DIRECTION_R is based on the input sequence
  OFF -> A -> optionally AB -> optionally B -> OFF
  
  The return of an REA_STATE_E_DIRECTION_L is based on the input sequence
  OFF -> B -> optionally AB -> optionally A -> OFF
  
  @seealso Switch.h
*/
class RotaryEncoderAnalyzer : public SwitchStateAnalyzer {
  protected:
    byte _internalState;

    void init(){
      reset();
    }

  public:
    RotaryEncoderAnalyzer() : SwitchStateAnalyzer() {
      RotaryEncoderAnalyzer::init();
    }

    byte getReadCycleMillis() {
      return 2;
    };

    void reset(){
      _internalState = REA_STATE_I_UNDEFINED;
    }

    byte getNumAnalyzerStates(){
      return 3;
    }

    byte getNumSwitchStates(){
      return 4;
    }

    /*
      Analysis of the state sequence.

      @param switchState  4 possible raw states
                          REA_SWSTATE_OFF, REA_SWSTATE_A, REA_SWSTATE_B, REA_SWSTATE_AB
      @returns            1 of the 3 states
                          REA_STATE_E_OFF, REA_STATE_E_DIRECTION_R, REA_STATE_E_DIRECTION_L
    */
    byte getAnalyzerState(byte switchState){

      if(_internalState == REA_STATE_I_UNDEFINED){
        _internalState = REA_STATE_I_OFF;
      }

      // find next internal state based on current internal state and raw state
      if(_internalState == REA_STATE_I_OFF){
        if(switchState == REA_SWSTATE_A){
          // start of a new sequence
          _internalState = REA_STATE_I_RIGHT_A;
        }
        else if(switchState == REA_SWSTATE_B){
          // start of a new sequence
          _internalState = REA_STATE_I_LEFT_B;
        }
        // all other raw states changes can be ignored
      }
      else if(_internalState == REA_STATE_I_RIGHT_A){
        if(switchState == REA_SWSTATE_OFF){
          _internalState = REA_STATE_I_OFF;
          return REA_STATE_E_DIRECTION_R;
        }
        // all other raw states changes can be ignored
      }
      else if(_internalState == REA_STATE_I_LEFT_B){
        if(switchState == REA_SWSTATE_OFF){
          _internalState = REA_STATE_I_OFF;
          return REA_STATE_E_DIRECTION_L;
        }
        // all other raw states changes can be ignored
      }

      // default return value, result still unclear
      return REA_STATE_E_OFF;
    }
};

/*
  Implementation of a rotary encoder specific state analyzer
  for a full input state change cycle (from REA_SWSTATE_OFF to REA_SWSTATE_OFF)
  to generate 1 output state change. This may need, depending on the
  encoder specification, 1 detent or 2 detent movements.

  This analyzer is waiting till the end of the full cycle to generate an external
  state change and is expecting nearly the full sequence of input state changes.
  
  Analysis of rotary encoder input signals from 2 digital pins (4 raw states)
  to deliver the information (3 states) which movements were done:
  
  no movement (REA_STATE_E_OFF)
  right turn one cycle (REA_STATE_E_DIRECTION_R)
  left turn on cycle (REA_STATE_E_DIRECTION_L)
  
  Prerequisite are the 4 raw input states
  REA_SWSTATE_OFF, REA_SWSTATE_A, REA_SWSTATE_B, REA_SWSTATE_AB
  
  The return of an REA_STATE_E_DIRECTION_R is based on the input sequence
  OFF -> A -> AB -> optionally B -> OFF
  
  The return of an REA_STATE_E_DIRECTION_L is based on the input sequence
  OFF -> B -> AB -> optionally A -> OFF
  
  @seealso Switch.h
*/
class RotaryEncoderStrictAnalyzer : public RotaryEncoderAnalyzer {
  protected:
    byte prevValue = REA_SWSTATE_OFF;

  public:
    RotaryEncoderStrictAnalyzer() : RotaryEncoderAnalyzer() {
      RotaryEncoderStrictAnalyzer::init();
    }

    byte getReadCycleMillis() {
      return 0;
    };

    /*
      Analysis of the state sequence.

      @param switchState  4 possible raw states
                          REA_SWSTATE_OFF, REA_SWSTATE_A, REA_SWSTATE_B, REA_SWSTATE_AB
      @returns            1 of the 3 states
                          REA_STATE_E_OFF, REA_STATE_E_DIRECTION_R, REA_STATE_E_DIRECTION_L
    */
    byte getAnalyzerState(byte switchState){
      // performance optimization
      if(switchState == prevValue){
        // default return value, result still unclear
        return REA_STATE_E_OFF;
      }
      prevValue=switchState;

      if(_internalState == REA_STATE_I_UNDEFINED){
        _internalState = REA_STATE_I_OFF;
      }

      // find next internal state based on current internal state and raw state
      if(_internalState == REA_STATE_I_OFF){
        if(switchState == REA_SWSTATE_A){
          // start of a new sequence
          _internalState = REA_STATE_I_RIGHT_A;
        }
        else if(switchState == REA_SWSTATE_B){
          // start of a new sequence
          _internalState = REA_STATE_I_LEFT_B;
        }
      }
      else if(switchState == REA_SWSTATE_OFF){
        if((_internalState & (REA_STATE_I_RIGHT_AB | REA_STATE_I_RIGHT_B)) > 0){
          _internalState = REA_STATE_I_OFF;
          return REA_STATE_E_DIRECTION_R;
        }
        else if((_internalState & (REA_STATE_I_LEFT_AB | REA_STATE_I_LEFT_A)) > 0){
          _internalState = REA_STATE_I_OFF;
          return REA_STATE_E_DIRECTION_L;
        }
      }
      // illegal sequences reset the internal state
      else if(_internalState == REA_STATE_I_RIGHT_A){
        if(switchState == REA_SWSTATE_AB){
          _internalState = REA_STATE_I_RIGHT_AB;
        }
		else{
          _internalState = REA_STATE_I_OFF;
        }
      }
      else if(_internalState == REA_STATE_I_RIGHT_AB){
        if(switchState == REA_SWSTATE_B){
          _internalState = REA_STATE_I_RIGHT_B;
        }
		else{
          _internalState = REA_STATE_I_OFF;
        }
      }
      else if(_internalState == REA_STATE_I_LEFT_B){
        if(switchState == REA_SWSTATE_AB){
          _internalState = REA_STATE_I_LEFT_AB;
        }
		else{
          _internalState = REA_STATE_I_OFF;
        }
      }
      else if(_internalState == REA_STATE_I_LEFT_AB){
        if(switchState == REA_SWSTATE_A){
          _internalState = REA_STATE_I_LEFT_A;
        }
		else{
          _internalState = REA_STATE_I_OFF;
        }
      }

      // default return value, result still unclear
      return REA_STATE_E_OFF;
    }
};

/*
  Implementation of a rotary encoder specific state analyzer
  for a reduced input state change cycle (from REA_SWSTATE_OFF to REA_SWSTATE_AB
  or from REA_SWSTATE_AB to REA_SWSTATE_OFF)
  to generate 1 output state change. This may need, depending on the
  encoder specification, 1/2 detent or 1 detent movement.

  This analyzer is waiting till the end of the half cycle to generate an external
  state change. 2 input changes are expected to be there in this strict implementation.

  Analysis of rotary encoder input signals from 2 digital pins (4 raw states)
  to deliver the information (3 states) which movements were done:
  
  no movement (REA_STATE_E_OFF)
  right turn one step (REA_STATE_E_DIRECTION_R)
  left turn on step (REA_STATE_E_DIRECTION_L)
  
  Prerequisite are the 4 raw input states
  REA_SWSTATE_OFF, REA_SWSTATE_A, REA_SWSTATE_B, REA_SWSTATE_AB
  
  The return of an REA_STATE_E_DIRECTION_R is based on the input sequence
  OFF -> A -> AB   or   AB -> B -> OFF
  
  The return of an REA_STATE_E_DIRECTION_L is based on the input sequence
  OFF -> B -> AB   or   AB -> A -> OFF
  
  @seealso Switch.h
*/
class RotaryEncoder2Analyzer : public RotaryEncoderAnalyzer {
  protected:
    byte prevValue = REA_SWSTATE_OFF;

  public:
    RotaryEncoder2Analyzer() : RotaryEncoderAnalyzer() {
      RotaryEncoder2Analyzer::init();
    }

    byte getReadCycleMillis() {
      return 1;
    };

    /*
      Analysis of the state sequence.

      @param switchState  4 possible raw states
                          REA_SWSTATE_OFF, REA_SWSTATE_A, REA_SWSTATE_B, REA_SWSTATE_AB
      @returns            1 of the 3 states
                          REA_STATE_E_OFF, REA_STATE_E_DIRECTION_R, REA_STATE_E_DIRECTION_L
    */
    byte getAnalyzerState(byte switchState){
      // performance optimization
      if(switchState == prevValue){
        // default return value, result still unclear
        return REA_STATE_E_OFF;
      }
      prevValue=switchState;

      if(_internalState == REA_STATE_I_UNDEFINED){
        _internalState = REA_STATE_I_OFF;
      }

      // find next internal state based on current internal state and raw state
      if(_internalState == REA_STATE_I_OFF){
        if(switchState == REA_SWSTATE_A){
          _internalState = REA_STATE_I_RIGHT_A;
        }
        else if(switchState == REA_SWSTATE_B){
          _internalState = REA_STATE_I_LEFT_B;
        }
      }
      else if(_internalState == REA_STATE_I_AB){
        if(switchState == REA_SWSTATE_A){
          _internalState = REA_STATE_I_LEFT_A;
        }
		else if(switchState == REA_SWSTATE_B){
		  _internalState = REA_STATE_I_RIGHT_B;
		}
      }
      else if(_internalState == REA_STATE_I_RIGHT_A){
        if(switchState == REA_SWSTATE_AB){
          _internalState = REA_STATE_I_AB;
          return REA_STATE_E_DIRECTION_R;
        }
      }
      else if(_internalState == REA_STATE_I_RIGHT_B){
        if(switchState == REA_SWSTATE_OFF){
          _internalState = REA_STATE_I_OFF;
          return REA_STATE_E_DIRECTION_R;
        }
      }
      else if(_internalState == REA_STATE_I_LEFT_B){
        if(switchState == REA_SWSTATE_AB){
          _internalState = REA_STATE_I_AB;
          return REA_STATE_E_DIRECTION_L;
        }
      }
      else if(_internalState == REA_STATE_I_LEFT_A){
        if(switchState == REA_SWSTATE_OFF){
          _internalState = REA_STATE_I_OFF;
          return REA_STATE_E_DIRECTION_L;
        }
      }

      if(switchState == REA_SWSTATE_OFF){
        _internalState = REA_STATE_I_OFF;
      }
      else if(switchState == REA_SWSTATE_AB){
        _internalState = REA_STATE_I_AB;
      }

      // default return value, result still unclear
      return REA_STATE_E_OFF;
    }
};

/*
  Implementation of a specific switch for rotary encoders.

  Analysis of rotary encoder input signals from 2 digital pins (4 raw states)
  to deliver the information (3 states) which movements were done:

  no movement (REA_STATE_E_OFF)
  right turn one step (REA_STATE_E_DIRECTION_R)
  left turn on step (REA_STATE_E_DIRECTION_L)

  Prerequisite are the 4 raw input states
  REA_SWSTATE_OFF, REA_SWSTATE_A, REA_SWSTATE_B and REA_SWSTATE_AB

  @seealso Switch.h
*/
class RotaryEncoder : public Switch {
  protected:
    // keep value of parameter inputPinB
    byte _inputPinB;

    /*
      @returns 4 raw states for input pins A and B, values 0 to 3
    */
    byte getRawState(){
      return Switch::getRawState() + (digitalRead(_inputPinB) == LOW ? 0 : 2);
    }

  public:
    RotaryEncoder(RotaryEncoderAnalyzer* rea, boolean enableMapping, byte inputPinA,
      byte inputPinB, byte debounceMillis, boolean invertRaw)
      : Switch(rea, enableMapping, inputPinA, debounceMillis, invertRaw) {

      _inputPinB = inputPinB;

    }

    RotaryEncoder(RotaryEncoderAnalyzer* rea, byte* bufferMapping, byte inputPinA,
      byte inputPinB, byte debounceMillis, boolean invertRaw)
      : Switch(rea, bufferMapping, inputPinA, debounceMillis, invertRaw) {

      _inputPinB = inputPinB;

    }

    /*
      Configure both input pins, 
      Not only one pin like default implementation in Switch class
    */
    void configurePins(){
      Switch::configurePin(); // for inputPinA

      if(_invertRaw){
        pinMode(_inputPinB, INPUT_PULLUP);
      }
      else{
        pinMode(_inputPinB, INPUT);
      }
    };
};

#endif
