#include <Arduino.h>
#include "FastAccelStepper.h"
#include "Ps3Controller.h"
#include "algorithm"




#define PAN_MOTOR_STEP_PIN 23
#define PAN_MOTOR_DIRECTION_PIN 22
#define TILT_MOTOR_STEP_PIN 16
#define TILT_MOTOR_DIRECTION_PIN 4
#define SLIDE_MOTOR_STEP_PIN 21
#define SLIDE_MOTOR_DIRECTION_PIN 19
#define MOTOR_EN_PIN 18
#define MOTOR_MS1_PIN 5
#define MOTOR_MS2_PIN 17
#define PAN_HALL_PIN 36
#define TILT_HALL_PIN 39
#define SLIDE_HALL_PIN 34
#define PAN_MAX_SPEED 2500
#define PAN_MAX_ACCELERATION 8000
#define TILT_MAX_SPEED 1250
#define TILT_MAX_ACCELERATION 8000
#define SLIDE_MAX_SPEED 3500
#define SLIDE_MAX_ACCELERATION 8000
#define KEYFRAME_ARRAY_LENGTH 10
#define SPEEDADJUSTMENTFACTOR 0.483

unsigned long previousMillis = 0; 
const long debouncedelay = 100; 
float speedfactor=1;
float accelerationfactor=1;
float panspeed_current=0;
float tiltspeed_current=0;
float slidespeed_current=0;
int keyframe_elements = 0;
int current_keyframe_index = -1;
int last_movedto_keyframe_index=-1;
int cntr_moves=0;
int cntr_moves_continous=0;
bool pan_is_moving=true;
bool tilt_is_moving=true;
bool slide_is_moving=true;
long pan_steps_to_move=0;
long tilt_steps_to_move=0;
long slide_steps_to_move=0;
long steps_to_move[3]={0,0,0};
double pan_time_motion_complete=0;
double tilt_time_motion_complete=0;
double slide_time_motion_complete=0;
int pan_speed_motion_complete=0;
int tilt_speed_motion_complete=0;
int slide_speed_motion_complete=0;
bool continous_mode=false;
int delay_continous_mode=0;

struct KeyframeElement {
    long panStepCount = 0;
    float panSpeed = 0;
    long tiltStepCount = 0;
    float tiltSpeed = 0;
    long sliderStepCount = 0;
    float sliderSpeed = 0;
    int msDelay = 0;
};

KeyframeElement keyframe_array[KEYFRAME_ARRAY_LENGTH];

FastAccelStepperEngine engine = FastAccelStepperEngine();
FastAccelStepper *pan_stepper = NULL;
FastAccelStepper *tilt_stepper = NULL;
FastAccelStepper *slide_stepper = NULL;

class Stick {
    private:
        bool _state;
        bool _2ndstate;
        int _pin;
        int _value;

    public:
        Stick(int pin) : _pin(pin) {}
        
        int ps3stickread(int pin2read)
        {
          switch(pin2read)
          {
            case 0:
              _value=Ps3.data.analog.stick.lx;
              break;
            case 1:
              _value=Ps3.data.analog.stick.ly;
              break;
            case 2:
               _value=Ps3.data.analog.stick.rx;
               break;
            case 3:
               _value=Ps3.data.analog.stick.ry;
               break;                    
          }          

          if(_value<-1 || _value>1)
          {
            _2ndstate=true;
          }
          if(_value==0 || _value==-1 || _value==1)
          {
            _2ndstate=false;
          }
          return _2ndstate;         
        }

        void begin() 
        {            
            _state = ps3stickread(_pin);
        }

        bool isPressed() 
        {
           bool v = ps3stickread(_pin);
           return v;
           /*
            bool v = ps3stickread(_pin);
            if (v != _state) {
                _state = v;
                if (_state) {
                    return true;
                }
            }
            return false;
            */
        }

        int getValue()
        {
           return _value;

        }
};


class Button {
    private:
        bool _state;
        bool _2ndstate;
        int _pin;
        int _value;

    public:
        Button(int pin) : _pin(pin) {}
        
        bool ps3readpin(int pin2read)
        {
          switch(pin2read)
          {
            case 0:
              _value=Ps3.data.analog.button.up;
              break;
            case 1:
              _value=Ps3.data.analog.button.down;
              break;
            case 2:
               _value=Ps3.data.analog.button.left;
               break;
            case 3:
               _value=Ps3.data.analog.button.right;
               break;
            case 4:
               _value=Ps3.data.analog.button.cross;
               break;
            case 5:
               _value=Ps3.data.analog.button.square;
               break;
            case 6:
               _value=Ps3.data.analog.button.triangle;
               break;
            case 7:
               _value=Ps3.data.analog.button.circle;
               break;
            case 8:
               _value=Ps3.data.analog.button.l1;
               break;
            case 9:
               _value=Ps3.data.analog.button.r1;
               break;
            case 10:
               _value=Ps3.data.button.l3;
               break;
            case 11:
               _value=Ps3.data.button.r3;
               break;
            case 12:
               _value=Ps3.data.button.select;
               break;
            case 13:
               _value=Ps3.data.button.start;
               break;
             case 14:
               _value=Ps3.data.button.l2;
               break;
            case 15:
               _value=Ps3.data.button.r2;
               break;
          }          

          if(_value!=0)
          {
            _2ndstate=true;
          }
          if(_value==0)
          {
            _2ndstate=false;
          }
          return _2ndstate;         
        }

        void begin() 
        {            
            _state = ps3readpin(_pin);
        }

        bool isReleased() 
        {
            bool v = ps3readpin(_pin);
            if (v != _state) {
                _state = v;
                if (_state) {
                    return true;
                }
            }
            return false;
        }
};

Button button_dPadUP(0);
Button button_dPadDOWN(1);
Button button_dPadLEFT(2);
Button button_dPadRIGHT(3);
Button button_CROSS(4);
Button button_SQUARE(5);
Button button_TRIANGLE(6);
Button button_CIRCLE(7);
Button button_L1(8);
Button button_R1(9);
Button button_L3(10);
Button button_R3(11);
Button button_SELCET(12);
Button button_START(13);
Button button_L2(14);
Button button_R2(15);

Stick stick_LX(0);
Stick stick_LY(1);
Stick stick_RX(2);
Stick stick_RY(3);


void onConnect() 
{
  Serial.println("Connected.");
}

int add_Position(void) {
  if (keyframe_elements >= 0 && keyframe_elements < KEYFRAME_ARRAY_LENGTH) {
    keyframe_array[keyframe_elements].panStepCount = pan_stepper->getCurrentPosition();
    keyframe_array[keyframe_elements].tiltStepCount = tilt_stepper->getCurrentPosition();
    keyframe_array[keyframe_elements].sliderStepCount = slide_stepper->getCurrentPosition();
    keyframe_array[keyframe_elements].panSpeed = PAN_MAX_SPEED;
    keyframe_array[keyframe_elements].tiltSpeed = TILT_MAX_SPEED;
    keyframe_array[keyframe_elements].sliderSpeed = SLIDE_MAX_SPEED;
    keyframe_array[keyframe_elements].msDelay = 0;

    Serial.print("Pan: "); 
    Serial.println(keyframe_array[keyframe_elements].panStepCount);
    Serial.print("Tilt: "); 
    Serial.println(keyframe_array[keyframe_elements].tiltStepCount);
    Serial.print("Slide: "); 
    Serial.println(keyframe_array[keyframe_elements].sliderStepCount);

    current_keyframe_index = keyframe_elements;
    keyframe_elements++;    
    Serial.print("Added at index: ");    
    Serial.println(current_keyframe_index);
    return 0;
  }
  else {
    Serial.println("Max number of keyframes reached");    
  }
  return -1;
}

void set_Speed(char _pts)
{
   switch(_pts)
   {
      case 'p':
      {
         pan_time_motion_complete=double(steps_to_move[2])/double(PAN_MAX_SPEED);
         Serial.print("time to complete pan movement: ");
         Serial.println(pan_time_motion_complete,5);

         pan_speed_motion_complete=PAN_MAX_SPEED;

         tilt_speed_motion_complete=abs(tilt_steps_to_move)/pan_time_motion_complete;
         Serial.print("tilt steps to move: ");
         Serial.println(tilt_steps_to_move);
         Serial.print("tilt speed according to pan: ");
         Serial.println(tilt_speed_motion_complete);

         slide_speed_motion_complete=abs(slide_steps_to_move)/pan_time_motion_complete;
         Serial.print("slide steps to move: ");
         Serial.println(slide_steps_to_move);
         Serial.print("slide speed according to pan: ");
         Serial.println(slide_speed_motion_complete);


         break;
      }
      case 't':
      {
         tilt_time_motion_complete=double(steps_to_move[2])/double(TILT_MAX_SPEED);
         Serial.print("time to complete tilt movement: ");
         Serial.println(tilt_time_motion_complete,5);

         tilt_speed_motion_complete=TILT_MAX_SPEED;

         pan_speed_motion_complete=abs(pan_steps_to_move)/tilt_time_motion_complete;
         Serial.print("pan steps to move: ");
         Serial.println(pan_steps_to_move);
         Serial.print("pan speed according to tilt: ");
         Serial.println(pan_speed_motion_complete);

         slide_speed_motion_complete=abs(slide_steps_to_move)/tilt_time_motion_complete;
         Serial.print("slide steps to move: ");
         Serial.println(slide_steps_to_move);
         Serial.print("slide speed according to tilt: ");
         Serial.println(slide_speed_motion_complete);

         break;
      }
      case 's':
      {
         slide_time_motion_complete=double(steps_to_move[2])/double(SLIDE_MAX_SPEED);
         Serial.print("time to complete slide movement: ");
         Serial.println(slide_time_motion_complete,5);

         slide_speed_motion_complete=SLIDE_MAX_SPEED;

         pan_speed_motion_complete=abs(pan_steps_to_move)/slide_time_motion_complete;
         Serial.print("pan steps to move: ");
         Serial.println(pan_steps_to_move);
         Serial.print("pan speed according to slide: ");
         Serial.println(pan_speed_motion_complete);

         tilt_speed_motion_complete=abs(tilt_steps_to_move)/slide_time_motion_complete;
         Serial.print("tilt steps to move: ");
         Serial.println(tilt_steps_to_move);
         Serial.print("tilt speed according to slide: ");
         Serial.println(tilt_speed_motion_complete);

         break;

      }
   }

}

void move_toPosition(int index)
{
   using namespace std;
   if(index >= 0 && index<KEYFRAME_ARRAY_LENGTH)
   {
      pan_steps_to_move=pan_stepper->getCurrentPosition()-keyframe_array[index].panStepCount;
      tilt_steps_to_move=tilt_stepper->getCurrentPosition()-keyframe_array[index].tiltStepCount;
      slide_steps_to_move=slide_stepper->getCurrentPosition()-keyframe_array[index].sliderStepCount;

      steps_to_move[0]=abs(pan_steps_to_move);
      steps_to_move[1]=abs(tilt_steps_to_move);
      steps_to_move[2]=abs(slide_steps_to_move);    

      sort(steps_to_move,steps_to_move+3);

      if(steps_to_move[2]==abs(pan_steps_to_move))
      {
         Serial.println("most steps 2 move on pan axis");
         set_Speed('p');
      }
      if(steps_to_move[2]==abs(tilt_steps_to_move))
      {
         Serial.println("most steps 2 move on tilt axis");
         set_Speed('t');
      }
      if(steps_to_move[2]==abs(slide_steps_to_move))
      {
         Serial.println("most steps 2 move on slide axis");
         set_Speed('s');
      }

      Serial.println(pan_speed_motion_complete*speedfactor);  
      Serial.println(tilt_speed_motion_complete*speedfactor);  
      Serial.println(slide_speed_motion_complete*speedfactor);   
      pan_stepper->setSpeedInHz(pan_speed_motion_complete*speedfactor);   
      tilt_stepper->setSpeedInHz(tilt_speed_motion_complete*speedfactor);
      slide_stepper->setSpeedInHz(slide_speed_motion_complete*speedfactor);


      

      pan_stepper->moveTo(keyframe_array[index].panStepCount);
      tilt_stepper->moveTo(keyframe_array[index].tiltStepCount);
      slide_stepper->moveTo(keyframe_array[index].sliderStepCount);
      

      Serial.print("moving to target index:");   
      Serial.println(index);
      current_keyframe_index = index;
      

   }
   else
   {
      Serial.println("cant move to invalid index");
   }


}

void clearKeyframes(void) 
{
  keyframe_elements = 0;
  current_keyframe_index = -1;
  last_movedto_keyframe_index=-1;
  Serial.println("Keyframes cleared");
}


void setup()
{
   Serial.begin(112500);

   button_dPadUP.begin();
   button_dPadDOWN.begin();
   button_dPadLEFT.begin();
   button_dPadRIGHT.begin();
   button_CROSS.begin();
   button_SQUARE.begin();
   button_TRIANGLE.begin();
   button_CIRCLE.begin();
   button_L1.begin();
   button_R1.begin();
   button_L3.begin();
   button_R3.begin();
   button_SELCET.begin();
   button_START.begin();
   button_L2.begin();
   button_R2.begin();
   stick_LX.begin();
   stick_LY.begin();
   stick_RX.begin();
   stick_RY.begin();
 

  Ps3.attachOnConnect(onConnect);
  Ps3.begin("20:c9:d0:d5:92:3c");
  Serial.println("Ready."); 
     

   engine.init();
   pan_stepper = engine.stepperConnectToPin(PAN_MOTOR_STEP_PIN);
   tilt_stepper = engine.stepperConnectToPin(TILT_MOTOR_STEP_PIN);
   slide_stepper = engine.stepperConnectToPin(SLIDE_MOTOR_STEP_PIN);
   if (pan_stepper) 
   {
      pan_stepper->setDirectionPin(PAN_MOTOR_DIRECTION_PIN);
      pan_stepper->setEnablePin(MOTOR_EN_PIN);
      pan_stepper->setAutoEnable(true);

      pan_stepper->setSpeedInHz(PAN_MAX_SPEED);       // 500 steps/s
      pan_stepper->setAcceleration(PAN_MAX_ACCELERATION);    // 100 steps/s²
      
   }

      if (tilt_stepper) 
    {
      tilt_stepper->setDirectionPin(TILT_MOTOR_DIRECTION_PIN);
      tilt_stepper->setEnablePin(MOTOR_EN_PIN);
      tilt_stepper->setAutoEnable(true);

      tilt_stepper->setSpeedInHz(TILT_MAX_SPEED);       // 500 steps/s
      tilt_stepper->setAcceleration(TILT_MAX_ACCELERATION);    // 100 steps/s²
      
   }
      if (slide_stepper) 
    {
      slide_stepper->setDirectionPin(SLIDE_MOTOR_DIRECTION_PIN);
      slide_stepper->setEnablePin(MOTOR_EN_PIN);
      slide_stepper->setAutoEnable(true);

      slide_stepper->setSpeedInHz(SLIDE_MAX_SPEED);       // 500 steps/s
      slide_stepper->setAcceleration(SLIDE_MAX_ACCELERATION);    // 100 steps/s²
      
   }
}

void loop() 
{
   // only runs code, if the controller is connected, otherwise everything stops
   if(Ps3.isConnected())
   {

       if(continous_mode)
         { 
            if(!(pan_stepper->isRunning()) && !(tilt_stepper->isRunning()) && !(slide_stepper->isRunning()) && keyframe_elements>1 )
            {
               delay(delay_continous_mode);
               move_toPosition(cntr_moves_continous);               
               if(keyframe_elements>cntr_moves_continous+1)
               {   
                  cntr_moves_continous++;
               }
               else
               {
                  cntr_moves_continous=0;
               } 
            }   
         }
   
      //debouncing of PS3 input
      unsigned long currentMillis = millis(); 
      if (currentMillis - previousMillis >= debouncedelay) 
      { 
         previousMillis = currentMillis; 

         //DPAD
         if(button_dPadUP.isReleased())
         {
            
            Serial.println("pressed button_dPadUP");  
            if(delay_continous_mode<1000)   
            {
               delay_continous_mode=delay_continous_mode+100; 
            }
            Serial.print("increased delay to"); 
            Serial.println(delay_continous_mode); 
               
         }
         if(button_dPadDOWN.isReleased())
         {
            Serial.println("pressed button_dPadDOWN");
            if(delay_continous_mode>=100)   
            {
               delay_continous_mode=delay_continous_mode-100; 
            }
            Serial.print("decreased delay to"); 
            Serial.println(delay_continous_mode); 
         }
         if(button_dPadLEFT.isReleased())
         {
            Serial.println("pressed button_dPadLEFT");         
         }
         if(button_dPadRIGHT.isReleased())
         {
            Serial.println("pressed button_dPadRIGHT");         
         }

         //Buttons
         if(button_CROSS.isReleased())
         {
            Serial.println("pressed button_CROSS");
            if(keyframe_elements!=0)
            {
               last_movedto_keyframe_index=cntr_moves;
               move_toPosition(last_movedto_keyframe_index);
               

               if(keyframe_elements>last_movedto_keyframe_index+1)
               {
                  cntr_moves++;                
               }
               else
               {
                  cntr_moves=0;
               }
            }        
            


           
            
                             
         }
         if(button_SQUARE.isReleased())
         {
            Serial.println("pressed button_SQUARE");
            add_Position();
            last_movedto_keyframe_index=current_keyframe_index;
            
         }
         if(button_TRIANGLE.isReleased())
         {
            
            Serial.println("pressed button_TRIANGLE");
            continous_mode=true;
            
            


         }
         if(button_CIRCLE.isReleased())
         {
            continous_mode=false;
            pan_stepper->stopMove();
            tilt_stepper->stopMove();
            slide_stepper->stopMove();         
            Serial.println("pressed button_CIRCLE");
         }

         //shoulder Buttons
         if(button_L1.isReleased())
         {
            Serial.println("pressed button_L1");
            if(speedfactor>0.000691)
            {
               speedfactor=speedfactor*SPEEDADJUSTMENTFACTOR;
            }
            Serial.println(speedfactor,5);
            /*
            pan_stepper->setSpeedInMilliHz(pan_stepper->getSpeedInMilliHz()*speedfactor);
            tilt_stepper->setSpeedInMilliHz(tilt_stepper->getSpeedInMilliHz()*speedfactor);
            slide_stepper->setSpeedInMilliHz(slide_stepper->getSpeedInMilliHz()*speedfactor);
            */
            
            
            

            
            
         }
         if(button_R1.isReleased())
         {
            Serial.println("pressed button_R1");
            if(speedfactor<1)
            {
               speedfactor=speedfactor/SPEEDADJUSTMENTFACTOR;
            }
            Serial.println(speedfactor,5);
            /*
            pan_stepper->setSpeedInMilliHz(pan_stepper->getSpeedInMilliHz()*speedfactor);
            tilt_stepper->setSpeedInMilliHz(tilt_stepper->getSpeedInMilliHz()*speedfactor);
            slide_stepper->setSpeedInMilliHz(slide_stepper->getSpeedInMilliHz()*speedfactor);
            */
         }
         if(button_L2.isReleased())
         {
            Serial.println("pressed stick_L2");  
            if(accelerationfactor>0.001)
            {
               accelerationfactor=accelerationfactor*0.5;
            }
            Serial.println(accelerationfactor);
            pan_stepper->setAcceleration(PAN_MAX_ACCELERATION*accelerationfactor);
            tilt_stepper->setAcceleration(TILT_MAX_ACCELERATION*accelerationfactor);    
            slide_stepper->setAcceleration(SLIDE_MAX_ACCELERATION*accelerationfactor);           
         }
         if(button_R2.isReleased())
         {
            Serial.println("pressed stick_R2");  
            if(accelerationfactor<1)
            {
               accelerationfactor=accelerationfactor/0.5;
            }
            Serial.println(accelerationfactor);
            pan_stepper->setAcceleration(PAN_MAX_ACCELERATION*accelerationfactor);
            tilt_stepper->setAcceleration(TILT_MAX_ACCELERATION*accelerationfactor);    
            slide_stepper->setAcceleration(SLIDE_MAX_ACCELERATION*accelerationfactor);      
         }
         if(button_L3.isReleased())
         {
            Serial.println("pressed button_L3");
            
         }
         if(button_R3.isReleased())
         {
            Serial.println("pressed button_R3");
            clearKeyframes();
            //Serial.println(slide_stepper->getCurrentPosition());
         }

         //start select
         if(button_SELCET.isReleased())
         {
            Serial.println("pressed button_SELCET");
         }
         if(button_START.isReleased())
         {
            Serial.println("pressed button_START");
         }


         //////////////////////////////////////////////////////////
         //STICK INPUT
         if(stick_LX.isPressed())
         {
            pan_is_moving=true;
            panspeed_current=speedfactor*PAN_MAX_SPEED*float(0.0078125)*abs(stick_LX.getValue());
            Serial.println("pressed stick_LX");
            Serial.println(stick_LX.getValue());
            pan_stepper->setSpeedInHz(panspeed_current);            
            Serial.println("maxspeed:");
            Serial.println(panspeed_current);

            if(stick_LX.getValue()>1)
            {
               pan_stepper->runForward();
            }
            else
            {
               pan_stepper->runBackward();
            }
         }
         else
         {
            if(pan_is_moving)
            {
               pan_stepper->stopMove();
               //pan_stepper->setSpeedInHz(0);
               pan_stepper->setDelayToDisable(65535);
               pan_is_moving=false;
            }
            
         }

         if(stick_LY.isPressed())
         {
            tilt_is_moving=true;
            tiltspeed_current=speedfactor*TILT_MAX_SPEED*float(0.0078125)*abs(stick_LY.getValue());
            Serial.println("pressed stick_LY");
            Serial.println(stick_LY.getValue());
            tilt_stepper->setSpeedInHz(tiltspeed_current);
            Serial.println("maxspeed:");
            Serial.println(tiltspeed_current);

            if(stick_LY.getValue()>1)
            {
               tilt_stepper->runForward();
            }
            else
            {
               tilt_stepper->runBackward();
            }
         }
         else
         {
            if(tilt_is_moving)
            {
               
               tilt_stepper->stopMove();
               tilt_stepper->setDelayToDisable(65535);
               //tilt_stepper->setSpeedInHz(0);
               tilt_is_moving=false;
            }
            
         }

         if(stick_RX.isPressed())
         {  
            slide_is_moving=true;
            slidespeed_current=speedfactor*SLIDE_MAX_SPEED*float(0.0078125)*abs(stick_RX.getValue());    
            Serial.println("pressed stick_RX");
            Serial.println(stick_RX.getValue());
            slide_stepper->setSpeedInHz(slidespeed_current);
            Serial.println("maxspeed:");
            Serial.println(slidespeed_current);

            if(stick_RX.getValue()>1)
            {
               slide_stepper->runBackward();
            }
            else
            {
               slide_stepper->runForward();
            }
         }
         else
         {
            if(slide_is_moving)
            {
               slide_stepper->stopMove();
               //slide_stepper->setSpeedInHz(0);
               slide_stepper->setDelayToDisable(65535);
               slide_is_moving=false;
            }
            
         }


         if(stick_RY.isPressed())
         {
            Serial.println("pressed stick_RY");
            Serial.println(stick_RY.getValue());
         }

        
      
      
      }
   }

   else
   {
      pan_stepper->stopMove();
      tilt_stepper->stopMove();
      slide_stepper->stopMove();

   }


   
}
