#include "liftlibcontrol.h"
#include "liftlibio.h"

/* Global variables */
int lift_levelPos[16];
int lift_one_level;

/**
   Is the counter valid for level positioning?
*/

// Wasm loop bounds

__attribute__((import_module("__pragma"), import_name("loopbound"))) extern void
__pragma_loopbound(unsigned int min_bound, unsigned int max_bound);

int lift_cntValid;

/**
   Position absolute or relative<.
*/
int lift_cnt;

/**
   Last stoped level (1..13) if position is absolute else 0.
*/
int lift_level;

/**
   load position in level, 0 means we don't know
*/
int lift_loadLevel;

/**
   we're going TOP or BOTTOM, but stop at load position.
*/
int lift_loadPending;

/**
   we're waiting for the load sensor to go.
*/
int lift_loadSensor;

/**
   cmd keeps the value of the command until the command is finished.
   It is only updated by the switches if it's current value is CMD_NONE.
*/
int lift_cmd;

int lift_timMotor;

int lift_timImp;

/**
   Remember last direction for impuls count after motor off;
*/
int lift_directionUp;

/**
   Last value of impuls sensor.
*/
int lift_lastImp;

int lift_dbgCnt;

/**
   stop value for the counter.
*/
int lift_endCnt;

void
lift_ctrl_init() {
    int i;
    lift_checksum = 0;

    lift_io_init();
    lift_cntValid = 0;
    lift_cnt = 0;
    lift_cmd = lift_CMD_NONE;
    lift_timMotor = 0;
    lift_timImp = 0;
    lift_directionUp = 1;
    lift_lastImp = 0;
    lift_loadLevel = 0;
    lift_loadPending = 0;
    lift_loadSensor = 0;
    i = 0;
    lift_levelPos[i++] = 0;
    lift_levelPos[i++] = 58;
    lift_levelPos[i++] = 115;
    lift_levelPos[i++] = 173;
    lift_levelPos[i++] = 230;
    lift_levelPos[i++] = 288;
    lift_levelPos[i++] = 346;
    lift_levelPos[i++] = 403;
    lift_levelPos[i++] = 461;
    lift_levelPos[i++] = 518;
    lift_levelPos[i++] = 576;
    lift_levelPos[i++] = 634;
    lift_levelPos[i++] = 691;
    lift_levelPos[i++] = 749;
    lift_levelPos[i++] = 806;
    lift_levelPos[i++] = 864;
    lift_one_level = lift_levelPos[1];
}

void
lift_ctrl_loop() {
    if (lift_cmd == lift_CMD_NONE)
        lift_check_cmd();
    else {
        lift_do_impulse(lift_ctrl_io_in[lift_SENS_IMPULS],
                        lift_ctrl_io_out[lift_MOTOR_ON],
                        lift_ctrl_io_in[lift_SENS_BOTTOM]);
        lift_do_cmd();
    }
    lift_check_level();
    lift_ctrl_io_led[13] = (lift_dbgCnt & 0x80) != 0;
    ++lift_dbgCnt;
}

void
lift_check_level() {
    int i;
    int middle = lift_one_level >> 2;
    if (lift_cntValid) {
        __pragma_loopbound(14, 14);
        for (lift_level = 1; lift_level < 14; ++lift_level) {
            if (lift_cnt < lift_levelPos[lift_level] - middle)
                break;
        }
    } else
        lift_level = 0;
    __pragma_loopbound(14, 14);
    for (i = 0; i < 14; ++i)
        lift_ctrl_io_led[i] = (i == lift_level - 1);
}

void
lift_check_cmd() {
    if (lift_loadPending) {
        if (lift_ctrl_io_in[lift_SENS_BOTTOM])
            lift_cmd = lift_CMD_TOP;
    } else if (lift_ctrl_io_in[lift_GO_UP]) {
        if (!lift_ctrl_io_in[lift_SENS_TOP] && lift_level != 14)
            lift_cmd = lift_CMD_UP;
    } else if (lift_ctrl_io_in[lift_GO_DOWN]) {
        if (!lift_ctrl_io_in[lift_SENS_BOTTOM] && lift_level != 1)
            lift_cmd = lift_CMD_DOWN;
    } else if (lift_ctrl_io_in[lift_GO_LOAD]) {
        if (lift_loadLevel != 0 && lift_level < lift_loadLevel)
            lift_cmd = lift_CMD_TOP;
        else
            lift_cmd = lift_CMD_BOTTOM;
        lift_loadPending = 1;
        lift_loadSensor = 0;
    } else if (lift_ctrl_io_in[lift_GO_TOP]) {
        if (!lift_ctrl_io_in[lift_SENS_TOP])
            lift_cmd = lift_CMD_TOP;
    } else if (lift_ctrl_io_in[lift_GO_BOTTOM]) {
        if (!lift_ctrl_io_in[lift_SENS_BOTTOM])
            lift_cmd = lift_CMD_BOTTOM;
    }
    if (lift_cmd != lift_CMD_NONE)
        lift_timMotor = 50;
}

void
lift_do_impulse(int val, int motor, int reset) {
    if (val && !lift_lastImp) {
        if (motor || lift_timImp > 0) {
            if (lift_directionUp)
                ++lift_cnt;
            else
                --lift_cnt;
        }
    }
    if (reset) {
        lift_cnt = 0;
        lift_cntValid = 1;
    }
    lift_lastImp = val;
    if (lift_timImp > 0) {
        --lift_timImp;
        if (lift_timImp == 0 && lift_cmd != lift_CMD_NONE)
            lift_cmd = lift_CMD_NONE;
    }
}

void
lift_do_cmd() {
    int run = 0;
    if (lift_timMotor > 0)
        lift_wait_for_motor_start();
    else {
        run = lift_check_run();
        if (lift_ctrl_io_out[lift_MOTOR_ON] && !run) {
            /* motor stopped: */
            lift_cmd = 99;
            lift_timImp = 50;
        }
        lift_ctrl_io_out[lift_MOTOR_ON] = run;
    }
}

void
lift_wait_for_motor_start() {
    int newLevel = 0;
    --lift_timMotor;
    lift_directionUp = (lift_cmd == lift_CMD_UP || lift_cmd == lift_CMD_TOP);
    lift_ctrl_io_out[lift_MOTOR_UP] = lift_directionUp;
    if (!lift_cntValid) {
        lift_cnt = 0; /* use relative counter */
        if (lift_cmd == lift_CMD_UP)
            lift_endCnt = lift_one_level;
        else
            lift_endCnt = -lift_one_level;
    } else {
        lift_endCnt = lift_cnt;
        newLevel = -99;
        if (lift_cmd == lift_CMD_UP)
            newLevel = lift_level + 1;
        else if (lift_cmd == lift_CMD_DOWN)
            newLevel = lift_level - 1;
        --newLevel; /* lift_level is one based */
        if (newLevel >= 0 && newLevel < 14)
            lift_endCnt = lift_levelPos[newLevel];
    }
}

int
lift_check_run() {
    if (lift_cmd == lift_CMD_UP) {
        if (lift_cnt < lift_endCnt - 1 && !lift_ctrl_io_in[lift_SENS_TOP])
            return 1;
    } else if (lift_cmd == lift_CMD_DOWN) {
        if (lift_cnt > lift_endCnt + 1 && !lift_ctrl_io_in[lift_SENS_BOTTOM])
            return 1;
    } else if (lift_cmd == lift_CMD_TOP) {
        if (lift_loadPending && lift_ctrl_io_in[lift_SENS_LOAD]) {
            /* we are at lift_load position */
            lift_loadLevel = lift_level;
            lift_loadPending = 0;
            return 0;
        }
        if (!lift_ctrl_io_in[lift_SENS_TOP])
            return 1;
        /* safe fallback if lift_load sensor does not work */
        lift_loadPending = 0;
    } else if (lift_cmd == lift_CMD_BOTTOM) {
        if (lift_loadPending) {
            if (lift_loadSensor) {
                if (!lift_ctrl_io_in[lift_SENS_LOAD]) {
                    lift_loadSensor = 0;
                    /* we are at lift_load position */
                    lift_loadPending = 0;
                    lift_loadLevel = lift_level;
                    return 0;
                }
            }
            lift_loadSensor = lift_ctrl_io_in[lift_SENS_LOAD];
        }
        if (!lift_ctrl_io_in[lift_SENS_BOTTOM])
            return 1;
    }
    return 0;
}
