#include "gpio_evaluateSwitch.hpp"

static const char *TAG = "evaluateSwitch";


gpio_evaluatedSwitch::gpio_evaluatedSwitch( //minimal (use default values)
        gpio_num_t gpio_num_declare
        ){
    gpio_num = gpio_num_declare;
    pullup = true;
    inverted = false;
    inputSource = inputSource_t::GPIO;

    initGpio();
};


gpio_evaluatedSwitch::gpio_evaluatedSwitch( //optional parameters given
        gpio_num_t gpio_num_declare,
        bool pullup_declare,
        bool inverted_declare
        ){
    gpio_num = gpio_num_declare;
    pullup = pullup_declare;
    inverted = inverted_declare;
    inputSource = inputSource_t::GPIO;

    initGpio();
};


gpio_evaluatedSwitch::gpio_evaluatedSwitch( //with function as input source
        bool (*getInputStatePtr_f)(void),
        bool inverted_f){
    //gpio_num = NULL;
    //pullup = NULL;
    inverted = inverted_f;
    getInputStatePtr = getInputStatePtr_f;
    inputSource = inputSource_t::FUNCTION;
};



void gpio_evaluatedSwitch::initGpio(){
    ESP_LOGI(TAG, "initializing gpio pin %d", (int)gpio_num);

    //define gpio pin as input
    gpio_pad_select_gpio(gpio_num);
    gpio_set_direction(gpio_num, GPIO_MODE_INPUT);

    if (pullup == true){ //enable pullup if desired (default)
        gpio_pad_select_gpio(gpio_num);
        gpio_set_pull_mode(gpio_num, GPIO_PULLUP_ONLY);
    }else{
        gpio_set_pull_mode(gpio_num, GPIO_FLOATING);
        gpio_pad_select_gpio(gpio_num);
    }
    //TODO add pulldown option
    //gpio_set_pull_mode(gpio_num, GPIO_PULLDOWN_ONLY);
};



void gpio_evaluatedSwitch::handle(){  //Statemachine for debouncing and edge detection

    //--- get pin state with required method ---
    switch (inputSource){
        case inputSource_t::GPIO: //from gpio pin
            if (gpio_get_level(gpio_num) == 0){ //pin low
                inputState = true;
            } else { //pin high
                inputState = false;
            }
            break;
        case inputSource_t::FUNCTION: //from funtion
            inputState = (*getInputStatePtr)();
            break;
    }

    //--- invert state ---
    //not inverted: switch switches to GND when active
    //inverted: switch switched to VCC when active
    if (inverted == true){
        inputState = !inputState;
    }


    //=========================================================
    //========= Statemachine for evaluateing switch ===========
    //=========================================================
    switch (p_state){
        default:
            p_state = switchState::FALSE;
            break;

        case switchState::FALSE:  //input confirmed high (released)
            fallingEdge = false; //reset edge event
            if (inputState == true){ //pin high (on)
                p_state = switchState::HIGH;
                timestampHigh = esp_log_timestamp(); //save timestamp switched from low to high
            } else {
                msReleased = esp_log_timestamp() - timestampLow; //update duration released
            }
            break;

        case switchState::HIGH: //input recently switched to high (pressed)
            if (inputState == true){ //pin still high (on)
                if (esp_log_timestamp() - timestampHigh > minOnMs){ //pin in same state long enough
                    p_state = switchState::TRUE;
                    state = true;
                    risingEdge = true;
                    msReleased = timestampHigh - timestampLow; //calculate duration the button was released 
                }
            }else{
                p_state = switchState::FALSE;
            }
            break;

        case switchState::TRUE:  //input confirmed high (pressed)
            risingEdge = false; //reset edge event
            if (inputState == false){ //pin low (off)
                timestampLow = esp_log_timestamp();
                p_state = switchState::LOW;
            } else {
                msPressed = esp_log_timestamp() - timestampHigh; //update duration pressed
            }

            break;

        case switchState::LOW: //input recently switched to low (released)
            if (inputState == false){ //pin still low (off)
                if (esp_log_timestamp() - timestampLow > minOffMs){ //pin in same state long enough
                    p_state = switchState::FALSE;
                    msPressed = timestampLow - timestampHigh; //calculate duration the button was pressed
                    state=false;
                    fallingEdge=true;
                }
            }else{
                p_state = switchState::TRUE;
            }
            break;
    }

}