#include <Mouse.h>
#include <Keyboard.h>
#include <stdlib.h>

// hardware
int KEYBOARD_DELAY = 20;
int MOUSE_DELAY = 20;
const int SERIAL_PORT = 9600;
const int SERIAL_DELAY = 1;
#define MAX_BUFFER_SIZE 64
#define led 13

// protocol
#define SET_DELAY '0'
#define SET_KEYBOARD_DELAY '0'
#define SET_MOUSE_DELAY '1'
#define PRESS_KEY '1'
#define PRINT_STRING '2'
#define KEY_DOWN '3'
#define KEY_UP '4'
#define MOUSE_MOVE '5'
#define MOUSE_CLICK '6'
#define MOUSE_DOWN '7'
#define MOUSE_UP '8'
#define MOUSE_SCROLL '9'




void setup() {
    Serial.begin(SERIAL_PORT);
    Mouse.begin();
    Keyboard.begin();
    pinMode(led, OUTPUT);
}

void loop()
{
    char buffer[MAX_BUFFER_SIZE];
    int buffPos = 0;

    while (!Serial.available()) {
        delay(SERIAL_DELAY);
    }

    while (Serial.available()) {
        if (buffPos < MAX_BUFFER_SIZE - 1) {
            buffer[buffPos++] = Serial.read();
            buffer[buffPos] = '\0';
        }
        else   //  
        {
          while (Serial.available()) {
            int temp = Serial.read();
          }
            digitalWrite(led, HIGH);   
            delay(3000); // 3     L
            digitalWrite(led, LOW); 
        }
    }




    switch (buffer[0]) {
    case SET_DELAY:  //       
    {
        switch (buffer[1]) {
        case SET_KEYBOARD_DELAY:
            KEYBOARD_DELAY = atoi(&buffer[2]);
            break;
        case SET_MOUSE_DELAY:
            MOUSE_DELAY = atoi(&buffer[2]);
            break;
        }
        break;
    }
    case PRESS_KEY:  //  
    {
        char key = atoi(&buffer[1]);
        Keyboard.press(key);
        delay(KEYBOARD_DELAY);
        Keyboard.release(key);
        break;
    }
    case PRINT_STRING:  //  
    {
        for (int i = 1; i < buffPos; i++) {
            Keyboard.write(buffer[i]);
            delay(KEYBOARD_DELAY);
        }
        break;
    }
    case KEY_DOWN:  //  
    {
        Keyboard.press(atoi(&buffer[1]));
        break;
    }
    case KEY_UP:  //  
    {
        Keyboard.release(atoi(&buffer[1]));
        break;
    }
    case MOUSE_MOVE:  //   
    {
        long coordinate = atol(&buffer[3]);

        // x  y      
        long x = coordinate / 65535;
        long y = coordinate % 65535;

        //   / /
        if (buffer[1] == '-')
            x = -x;
        if (buffer[2] == '-')
            y = -y;

        //    
        int count_step;
        int stepX = 127, stepY = 127;        // 
        int remainsX, remainsY;  //    

        if (abs(x) > abs(y))  //   X      Y
        {
            count_step = abs(x) / 127;
            if (count_step > 0)
            {
                if (x < 0)stepX = -127;

                if (y > 0)stepY = abs(y) / count_step;
                else stepY = y / count_step;

                if (abs(stepY) > 127)
                {
                    if (stepY > 0)stepY = 127;
                    else stepY = -127;
                }
            }
            remainsX = x % stepX;
            remainsY = y % stepY;
        }
        else
        {
            count_step = abs(y) / 127;
            if (count_step > 0)
            {
                if (y < 0)stepY = -127;
                if (x > 0)stepX = abs(x) / count_step;
                else stepX = x / count_step;

                if (abs(stepX) > 127)
                {
                    if (stepX > 0)stepX = 127;
                    else stepX = -127;
                }
            }
            remainsX = x % stepX;
            remainsY = y % stepY;
        }

        for (int i = 0; i < count_step; i++)  //  
        {
            Mouse.move(stepX, stepY, 0);
        }

        Mouse.move(remainsX, remainsY, 0);  //    
        break;
    }
    case MOUSE_CLICK:  //   
    {
        Mouse.click(buffer[1]);
        break;
    }
    case MOUSE_DOWN:  //   
    {
        Mouse.press(buffer[1]);
        break;
    }
    case MOUSE_UP:  //   
    {
        Mouse.release(buffer[1]);
        break;
    }
    case MOUSE_SCROLL:  //  
    {
        char wheel = atoi(&buffer[1]);
        Mouse.move(0, 0, wheel);
        break;
    }
    default:
        break;
    }

}