#include <xc.h>
#include <stdint.h>
#include "system.h"
#include "i2c.h"        //header for i2c state machine
#include "msghandler.h" //header for i2c message handler

/** GENERAL DEFINES **/
#define I2C_ACKNOWLEDGE (!MGC_SSPCON2_BITS.ACKSTAT)

/** I2C STATEMACHINE STATES DEFINES **/
#define I2C_CHECKNEWREQUESTS 0
// I2C Write Process States
#define I2C_WRITE_WAITSTART 2
#define I2C_WRITE_NEXTBYTE 4
#define I2C_WRITE_WAITBYTE 5
#define I2C_WRITE_STOP 6
#define I2C_WRITE_RESETMSSP_CLEANUP 8
#define I2C_WRITE_FINISHED 9
#define I2C_CLEANUPWRITE I2C_WRITE_FINISHED // used for failure case
// I2C Read Process States
#define I2C_READ_WAITSTART 17
#define I2C_READ_WAITADDRESS 18
#define I2C_READ_WAITLEN 19
#define I2C_READ_ACKLEN 20
#define I2C_READ_WAITBYTE 22
#define I2C_READ_ACKBYTE 23
#define I2C_READ_RESETMSSP_CLEANUP 25
#define I2C_READ_FINISHED 26
#define I2C_CLEANUPREAD I2C_READ_FINISHED// used for failure case

/** VARIABLES **/
static uint8_t i2cGestIC_buf[MAX_LEN_TO_GESTIC];
uint8_t i2cGestIC_len = 0;
static uint8_t i2cGestIC_idx = 0; //index counter for message

static uint8_t i2cstate = I2C_CHECKNEWREQUESTS;

volatile static uint8_t BytesToRead = 0; // number of bytes left to read

/** TS LINE HANDLING FUNCTIONS **/

/* only in the case that TS has been configured to bidirectional
 * PIC has to signal that the transfer is finished (releasing TS line)*/
static inline void i2c_irqFinished(void) {
#   if BIDIRECTIONAL
        INTCONbits.GIE = 0; // the next two instructions must not be interrupted
        MGC_TS_LAT = 1;
        MGC_TS_TRIS = 1; //set TS line as input
        INTCONbits.GIE = 1;
#   endif
}

/* only in the case that TS has been configured to bidirectional
 * PIC has to signal that the transfer is started (asserting TS line low)*/
static inline void i2c_tsSignalStarted(void) {
#   if BIDIRECTIONAL
        MGC_TS_LAT = 0;
        MGC_TS_TRIS = 0; //pull TS line low
#   endif
}

/* returns TS status if bidirectional protocol is used, else (TS pin)*/
 inline int8_t i2c_gestICSignalsI2C(void) {
# if BIDIRECTIONAL
    if(MGC_TS_LAT) {return !MGC_TS_PORT;}     //if bidirectional mode and TS is currently an input
    else {return 0;}
# else
    return MGC_TS_PORT;
#endif
}

/*configures TS line depending on build configuration (preprocessor define BIDIRECTIONAL)
 * see readMe.txt for more details
*/
void i2c_setbidirectional(void)
{
#   if BIDIRECTIONAL
        // TS line is used for bidirectional signaling (MGC3130 software package 0.4 and higher)
        // don't change anything, port configuration will be taken care of during start and end of transfer
#   else
        // TS line is used for unidirectional signaling (Alpha release and Sabrewing Demonstrator)
        // set TS as input since PIC only needs to read Pin state
        INTCONbits.GIE = 0; // the next two instructions must not be interrupted
        LATCbits.LATC0 = 0; TRISCbits.TRISC0 = 1;
        INTCONbits.GIE = 1;
#   endif
}

/** MGC RESET HANDLING FUNCTIONS ******************************************************/

/* handleGestICReset(void) tells the state machine to keep quiet for
 * a short duration, instead of trying to generate I2C requests.
 * This shouldn't be strictly necessary, it's just done to be on the
 * safe side */
 inline void i2c_handleGestICReset(void){
    i2cGestIC_len = 0;
    i2cGestIC_idx = 0;
    BytesToRead = 0;
    countDownReset = 25;//this stalls i2c state machine and is decreased in Timer0 ISR
	i2cTmr_start();
}

/*returns 1 if a MGC reset just occured*/
static inline int8_t checkMasterReset(void) {
    if(GESTIC_IN_RESET()) {
        i2c_handleGestICReset();
        return 1;
    } else if(countDownReset != 0) {
        return 1;
    } else {
        return 0;
    }
}

/** I2C SEND MESSAGES FUNCTIONS ******************************************************/

/*returns 0 if there is no message to be sent, otherwise the length of the send
 * buffer is returned */
inline uint8_t i2c_writeBufferInUse(void) {
    return i2cGestIC_len;
}

/* Add a message to the write buffer. This will only work if there is
 * no message in the write buffer and the message is less than MAX_LEN_TO_GESTIC
 * bytes long.
 * Will return 0 when the message could not be put into the write buffer.*/
int8_t i2c_sendMsg(uint8_t *msg, uint8_t len) {
    uint8_t i;
    
    if(i2cGestIC_len || (len > MAX_LEN_TO_GESTIC)){
        return 0;
    }

    i2cGestIC_len = len;
    i2cGestIC_idx = 0;

    for(i = 0; i < len; i++) {
        i2cGestIC_buf[i] = msg[i];
    }

    return 1;
}

//I2C bus collision detected - set correct next state to deal with it
void i2c_BCLhandler(void) {
    MGC_SSP_IE = 0;     //disable SSP interrupt
    switch(i2cstate) {
        case I2C_WRITE_WAITSTART:
            i2cstate = I2C_WRITE_RESETMSSP_CLEANUP;
            break;
        case I2C_WRITE_WAITBYTE:        //fall-thru
        case I2C_WRITE_NEXTBYTE:
        case I2C_WRITE_STOP:
            i2cstate = I2C_CLEANUPWRITE;
            break;
        case I2C_READ_WAITSTART:
            i2cstate = I2C_READ_RESETMSSP_CLEANUP;
            break;
        case I2C_READ_WAITADDRESS:      //fall-thru
        case I2C_READ_WAITLEN:
        case I2C_READ_ACKLEN:
        case I2C_READ_WAITBYTE:
        case I2C_READ_ACKBYTE:
            i2cstate = I2C_CLEANUPREAD;
            break;
        default:                        //should never hit this - try resetting everything
            MGC_SSPCON1_BITS.SSPEN = 0;
            MGC_SSPCON1_BITS.SSPEN = 1;
            i2c_irqFinished();
            i2cGestIC_idx = 0;
            i2cGestIC_len = 0;
            waitTSline = I2C_WAIT_FOR_TS;
            countDownI2C = BERR_WAITTIME;
			i2cTmr_start();
            i2cstate = I2C_CHECKNEWREQUESTS;
            break;
    }
    MGC_SSPCON1_BITS.WCOL = 0;
    i2c_runstatemachine();      //call state machine
}

//TS line asserted by MGC - set correct next state to deal with it
void i2c_TShandler(void) {
    if(countDownReset || countDownI2C || i2cMsgFlag) {
        return; // don't start anything while reset is counting down or we already have an unprocessed message
    }

    if(i2cstate == I2C_CHECKNEWREQUESTS) {  //only handle if we're not in the middle of something else
        MGC_SSP_IE = 0;
        MGC_SSPCON2_BITS.RCEN = 0;   // just to be sure : disable the receive mode for for i2c
        md_start();                 // initilize the md_idx = 0 of the reception buffer
        if(checkMasterReset()) {
            i2cstate = I2C_READ_RESETMSSP_CLEANUP;
            mgcSSPintFlag = 1;      //call i2c state machine
        }
        else {
            i2c_tsSignalStarted();
            i2cstate = I2C_READ_WAITSTART;
            MGC_SSPCON2_BITS.SEN = 1; // generate start condition - will generate SSP int when done
        }
        MGC_SSP_IE = 1;
    }
}

//Start a write to I2C
void i2c_startWrite(void) {
    if(countDownReset || countDownI2C) {
        return; // don't start anything while reset is counting down
    }

    if(i2cstate == I2C_CHECKNEWREQUESTS) {  //only start a write if we're not in the middle of something else
        MGC_SSP_IE = 0;
        MGC_SSPCON2_BITS.RCEN = 0;   // just to be sure : disable the receive mode for for i2c
        md_start();                 // initilize the md_idx = 0 of the reception buffer
        if(checkMasterReset()) {
            i2cstate = I2C_WRITE_RESETMSSP_CLEANUP;
            mgcSSPintFlag = 1;      //call i2c state machine
        }
        else {
            i2cstate = I2C_WRITE_WAITSTART;
            MGC_SSPCON2_BITS.SEN = 1; // generate start condition - will generate SSP interrupt when done
        }
        MGC_SSP_IE = 1;
    }
}

/** I2C STATE MACHINE ******************************************************/
/*there may be some overhead in this function due to error detection etc.*/
void i2c_runstatemachine(void) {
    if(countDownReset || countDownI2C) {
        return; // don't start anything while reset is counting down
    }
    MGC_SSP_IE = 0;     //disable SSP interrupt
    //SSP interrupt - we have the bus, advance i2c state
    mgcSSPintFlag = 0;

    switch(i2cstate) {
        case I2C_READ_WAITSTART:
            MGC_SSPBUF_REG = (GESTIC_I2C_ADDR << 1) | 1; // write Address to MGC3130
            if(MGC_SSPCON1_BITS.WCOL) { // write collision occured, another task was in progress
                MGC_SSP_BCL_IF = 0;
                MGC_SSPCON1_BITS.WCOL = 0;
                i2cstate = I2C_CLEANUPREAD;
                mgcSSPintFlag = 1;      //force state machine to run again
            }
            else {
                i2cstate = I2C_READ_WAITADDRESS;
            }
            break;

        case I2C_READ_WAITADDRESS:
            if(I2C_ACKNOWLEDGE) { // slave acknowledged address
                if(MGC_SSPCON2_REG & 0x1F) { // MSSP not idle
                    i2cstate = I2C_CLEANUPREAD;
                    mgcSSPintFlag = 1;      //force state machine to run again
                }
                else {
                    MGC_SSPCON2_BITS.RCEN = 1; // initiate reception
                    i2cstate = I2C_READ_WAITLEN;
                }
            }
            else { // no acknowledge
                i2cstate = I2C_CLEANUPREAD;
                mgcSSPintFlag = 1;      //force state machine to run again
            }
            break;

        case I2C_READ_WAITLEN:           // wait for 1st byte (message length)
            if(MGC_SSPCON1_BITS.SSPOV) {         // Receive Overflow Indicator bit indicate that a byte is received while the SSPBUF register is still holding the previous byte
                MGC_SSPCON1_BITS.SSPOV = 0;       // cleared the Receive Overflow Indicator bit
                MGC_SSPCON2_BITS.RCEN = 0;       // Receiver in Idle mode
                i2cstate = I2C_CLEANUPREAD;
                mgcSSPintFlag = 1;      //force state machine to run again
            }
            else if (checkMasterReset()) {
                MGC_SSPCON2_BITS.RCEN = 0;        // Receiver in Idle mode
                i2cstate = I2C_READ_RESETMSSP_CLEANUP;
                mgcSSPintFlag = 1;      //force state machine to run again
            }
            else {         // data received causes the SSPIF to be set
                BytesToRead = MGC_SSPBUF_REG;            // copy the byte of the SSPBUF register received to readLeft, 1st byte of the HEADER is the SIZE of the msg
                MGC_SSPSTAT_BITS.BF = 0;
                MGC_SSPCON2_BITS.ACKDT = 0;       // ACK(negative logic), initiate an ACK sequence at the end of the received
                MGC_SSPCON2_BITS.ACKEN = 1;      // Initiate Acknowledge sequence on SDA and SCL pins and transmit ACKDT data bit
                i2cstate = I2C_READ_ACKLEN;
            }
            break;

        case I2C_READ_ACKLEN:        // wait for ACK of the byte of the first byte (SIZE of the msg) and test that the minimum message is the HEADER alone
                if(BytesToRead == 0xFF) {        // 1st BytesToRead contains the SIZE of the message
                    i2cstate = I2C_CLEANUPREAD;     // Overflow of the buffer used to read the data from the GestIC (max is 160bytes)
                    mgcSSPintFlag = 1;      //force state machine to run again
                } else if(BytesToRead == 0) {       // The length of the message is nul --> a problem occured
                    i2cstate = I2C_READ_RESETMSSP_CLEANUP;       // I2C_CLEANUPREAD;
#ifdef I2C_DEFAULT_WAIT
                    countDownI2C = I2C_DEFAULT_WAIT;
					i2cTmr_start();
#endif

                    mgcSSPintFlag = 1;      //force state machine to run again
                } else if(BytesToRead < 4) {        // minimum length admitted is the HEADER alone, without any msg (Size+Flags+Seq+ID=4bytes)
                    i2cstate = I2C_CLEANUPREAD;          // the first byte received is at least 4bytes correspondig of the HEADER, we accept to read it
                    mgcSSPintFlag = 1;      //force state machine to run again
                } else {             // fill the receive buffer with the message (SIZE) received from the GestIC (1st byte of the HEADER)
                    md_next(BytesToRead);
                    BytesToRead--;
                    i2cstate = I2C_READ_WAITBYTE;
                    MGC_SSPCON2_BITS.RCEN = 1; // initiate reception
                }
            break;

        case I2C_READ_WAITBYTE:
            if(MGC_SSPCON1_BITS.SSPOV) {
                MGC_SSPCON1_BITS.SSPOV = 0;
                MGC_SSPCON2_BITS.RCEN = 0;
                i2cstate = I2C_CLEANUPREAD;
                mgcSSPintFlag = 1;      //force state machine to run again
            }
            else if (checkMasterReset()) {
                MGC_SSPCON2_BITS.RCEN = 0;
                i2cstate = I2C_READ_RESETMSSP_CLEANUP;
                mgcSSPintFlag = 1;      //force state machine to run again
            }
            else {
                volatile uint8_t c = MGC_SSPBUF_REG;
                MGC_SSPCON2_BITS.RCEN = 0;
                --BytesToRead;
                MGC_SSPSTAT_BITS.BF = 0;
                if(BytesToRead) {
                    MGC_SSPCON2_BITS.ACKDT = 0; // ACK(negative logic)
                } else {
                    MGC_SSPCON2_BITS.ACKDT = 1; // send no Ack when finished
                }
                i2cstate = I2C_READ_ACKBYTE;
                MGC_SSPCON2_BITS.ACKEN = 1;     //send ACK
                md_next(c);
            }
            break;

        case I2C_READ_ACKBYTE:
            if(BytesToRead) { // still some bytes left to read
                MGC_SSPCON2_BITS.RCEN = 1; // initiate reception
                i2cstate = I2C_READ_WAITBYTE;
            }
            else { // finished, send stopbyte
                i2cstate = I2C_READ_FINISHED;
                MGC_SSPCON2_BITS.PEN = 1; // generate Stop condition
            }
            break;

        case I2C_READ_RESETMSSP_CLEANUP:
            i2cstate = I2C_CLEANUPREAD;
            MGC_SSPCON1_BITS.SSPEN = 0;
            MGC_SSPCON1_BITS.SSPEN = 1;
            countDownI2C = BERR_WAITTIME;
			i2cTmr_start();
            mgcSSPintFlag = 1;  //force state machine to re-run
            break;

        // case I2C_READ_FINISHED // same as below
        case I2C_CLEANUPREAD:
            i2c_irqFinished();
            if(BytesToRead == 0) { // reading of message successfull
                i2cMsgFlag = 1; //i2c message receive completed; set flag to process information
            }
            waitTSline = I2C_WAIT_FOR_TS;
			i2cTmr_start();
            i2cstate = I2C_CHECKNEWREQUESTS;
            break;

        case I2C_WRITE_WAITSTART:
            MGC_SSPBUF_REG = (GESTIC_I2C_ADDR << 1) | 0; // write writeAddr to i2c
            if(MGC_SSPCON1_BITS.WCOL) { // write collision occured, another task was in progress
                MGC_SSP_BCL_IF = 0;
                MGC_SSPCON1_BITS.WCOL = 0;
                i2cstate = I2C_CLEANUPWRITE;
                mgcSSPintFlag = 1;      //force state machine to run again
            }
            else {
                i2cstate = I2C_WRITE_WAITBYTE;
            }
            break;

        case I2C_WRITE_WAITBYTE:
            if(I2C_ACKNOWLEDGE) { // slave acknowledged address / data
                if(i2cGestIC_idx < i2cGestIC_len) {
                    i2cstate = I2C_WRITE_NEXTBYTE;
                    mgcSSPintFlag = 1;      //force state machine to run again
                }
                else {
                    i2cGestIC_idx = 0;
                    i2cGestIC_len = 0;
                    i2cstate = I2C_WRITE_STOP; // wrote last byte
                    mgcSSPintFlag = 1;      //force state machine to run again
                }
            }
            else { // no acknowledge
                i2cstate = I2C_CLEANUPWRITE;
                mgcSSPintFlag = 1;      //force state machine to run again
            }
            break;

        case I2C_WRITE_NEXTBYTE: // write next byte from buffer
            if(i2cGestIC_idx < i2cGestIC_len) {
                if (checkMasterReset()) {
                    i2cstate = I2C_WRITE_RESETMSSP_CLEANUP;
                    mgcSSPintFlag = 1;      //force state machine to run again
                }
                else {
                    MGC_SSPBUF_REG = i2cGestIC_buf[i2cGestIC_idx++]; // write next byte of message
                    if(MGC_SSPCON1_BITS.WCOL) { // write collision occured, another task was in progress
                        MGC_SSP_BCL_IF = 0;
                        MGC_SSPCON1_BITS.WCOL = 0;
                        i2cstate = I2C_CLEANUPWRITE;
                        mgcSSPintFlag = 1;      //force state machine to run again
                    }
                    else {
                        i2cstate = I2C_WRITE_WAITBYTE;
                    }
                }
            }
            else {
                i2cGestIC_idx = 0;
                i2cGestIC_len = 0;
                i2cstate = I2C_WRITE_STOP; // write stop condition
                mgcSSPintFlag = 1;      //force state machine to run again
            }
            break;

        case I2C_WRITE_STOP:
            MGC_SSPCON2_BITS.PEN = 1; // generate Stop condition
            i2cstate = I2C_WRITE_FINISHED;
            break;

        case I2C_WRITE_RESETMSSP_CLEANUP:
            i2cstate = I2C_CLEANUPWRITE;
            MGC_SSPCON1_BITS.SSPEN = 0;
            MGC_SSPCON1_BITS.SSPEN = 1;
            countDownI2C = BERR_WAITTIME;
			i2cTmr_start();
            mgcSSPintFlag = 1;  //force state machine to re-run
            break;

        //case I2C_WRITE_FINISHED: //same as I2C_CLEANUPWRITE
        case I2C_CLEANUPWRITE:
            i2cGestIC_idx = 0;
            i2cGestIC_len = 0;
            i2cstate = I2C_CHECKNEWREQUESTS;
            break;

        default:
            break;
        } //end of switch(i2cstate)
    MGC_SSP_IE = 1;     //enable SSP interrupt
}