#include "can.h"
#ifdef _WIN32
    #include "Windows.h"
#endif

#include "serial.h"

#define CAN_HND_SIGN  0xCA0055FAUL

#define CAN_RS_TOUT 1000
#define CAN_RS_FRAME_LEN 11

typedef enum
{
    CAN_INTF_RS,
    CAN_INTF_USB
} t_can_intf;

struct st_can_state
{
    uint32_t sign;
    t_can_intf intf;
    union
    {
        struct
        {
            t_com_hnd hnd;
        } com;
    };
};


typedef struct
 {
   uint16_t hdr;
   uint8_t data[CAN_MAX_DATA_SIZE];
   uint8_t crc8;
   uint8_t dummy;
} t_can_rs_msg;

#define CAN_CHECK_HND(hnd) (hnd && hnd->sign==CAN_HND_SIGN) ? 0 : CAN_ERR_INVALID_HANDLE;


#ifdef _WIN32
    #define COM_API_RST(port)    com_rst_port(port)
    void com_rst_port(t_com_hnd port)
    {
        DCB ComParam;
        COMMTIMEOUTS ComTmout;

        GetCommState( port, &ComParam );
        ComParam.BaudRate = CBR_115200;
        ComParam.fDtrControl = DTR_CONTROL_ENABLE;
        ComParam.fRtsControl = RTS_CONTROL_ENABLE;
        ComParam.ByteSize = 8 ;
        ComParam.Parity = NOPARITY;
        ComParam.StopBits = TWOSTOPBITS;

        SetCommState( port, &ComParam );


        ComTmout.ReadIntervalTimeout = 1000;
        ComTmout.ReadTotalTimeoutMultiplier = 100;
        ComTmout.ReadTotalTimeoutConstant = 20;
        ComTmout.WriteTotalTimeoutMultiplier = 0;
        ComTmout.WriteTotalTimeoutConstant = 0;


        SetCommTimeouts( port, &ComTmout);

        SetupComm( port, 1024, 1024 );


        PurgeComm( port, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR );
        FlushFileBuffers( port );
        EscapeCommFunction( port, CLRRTS ); \
        Sleep(10); \
        EscapeCommFunction( port, SETDTR ); \
        Sleep(10); \
        EscapeCommFunction( port, CLRDTR ); \
    }
#else
    #define COM_API_RST(port)
#endif



static unsigned char f_calc_crc8( unsigned char *b, unsigned char N)
{
    unsigned char crc;
    unsigned char i;
    unsigned char j;

    for(i=crc=0; i<N; i++)
     {
      j=b[i];
      crc = crc ^ j;
      for(j=0;j<8;j++)
       {
       if(crc & 0x80) crc = (crc<<1) ^ 0x8C;
         else crc <<= 1;
       }
     }

    return crc;
}


t_can_hnd can_open_rs(const char* port)
{
    t_can_hnd hnd = 0;
    t_com_hnd com = com_open(port);
    if (com)
    {
        /*int err = com_set_br(com, 115200, COM_PARITY_NONE,
                             COM_SET_FLAGS_TWO_STOP_BITS |
                             COM_SET_FLAGS_RTS_CTL_EN |
                             COM_SET_FLAGS_DTR_CTL_EN);
        if (!err)
        {
            com_reset(com);*/
            COM_API_RST(com);
            hnd = malloc(sizeof(struct st_can_state));
            if (hnd)
            {
                hnd->com.hnd = com;
                hnd->intf = CAN_INTF_RS;
                hnd->sign = CAN_HND_SIGN;
            }
            else
            {
                com_close(com);
            }
        //}
    }

    return hnd;
}


int can_send_msg(t_can_hnd hnd, const t_can_msg* msg)
{
    int err = CAN_CHECK_HND(hnd);
    if (!err)
    {
        if (hnd->intf == CAN_INTF_RS)
        {
            t_can_rs_msg rs_msg;
            rs_msg.hdr = (msg->id&0x7FF) | ((uint16_t)msg->len << 11);
            memcpy(rs_msg.data, msg->data, CAN_MAX_DATA_SIZE);
            rs_msg.crc8 =  f_calc_crc8((unsigned char *)&rs_msg, CAN_RS_FRAME_LEN - 1);
            err = (com_write(hnd->com.hnd, (unsigned char*)&rs_msg, CAN_RS_FRAME_LEN,
                             CAN_RS_TOUT)==CAN_RS_FRAME_LEN)? 0 : CAN_ERR_COM_WRITE;
        }
        else err = CAN_ERR_INVALID_INTERFACE;

    }
    return err;
}

int can_recv_msg(t_can_hnd hnd, t_can_msg* msg, int tout)
{
    int err = CAN_CHECK_HND(hnd);
    if (!err)
    {
        if (hnd->intf == CAN_INTF_RS)
        {
            t_can_rs_msg rs_msg;
            err = (com_read(hnd->com.hnd, (unsigned char*)&rs_msg, CAN_RS_FRAME_LEN,
                             tout) == CAN_RS_FRAME_LEN)? 0 : CAN_ERR_COM_READ;
            if (!err)
            {
                uint8_t crc =  f_calc_crc8((unsigned char *)&rs_msg, CAN_RS_FRAME_LEN - 1);
                if (crc == rs_msg.crc8)
                {
                    msg->len = (rs_msg.hdr >> 11) & 0xF;
                    msg->id = rs_msg.hdr & 0x7FF;
                    if (msg->len)
                        memcpy(msg->data, rs_msg.data, msg->len);
                }
                else
                {
                    err = CAN_ERR_RX_CRC;
                }

            }
        }
        else err = CAN_ERR_INVALID_INTERFACE;
    }
    return err;
}





int can_set_filter(t_can_hnd hnd, unsigned int id)
{
    int err = 0;
    t_can_rs_msg rs_msg;
    rs_msg.hdr  = 0x8101;

    ((unsigned short int *)&rs_msg.data[0])[0] = id;
    ((unsigned short int *)&rs_msg.data[0])[1] = 0;
    ((unsigned short int *)&rs_msg.data[0])[2] = 0;
    ((unsigned short int *)&rs_msg.data[0])[3] = 0;

    rs_msg.crc8 =  ~f_calc_crc8((unsigned char *)&rs_msg, CAN_RS_FRAME_LEN - 1);
    err = (com_write(hnd->com.hnd, (unsigned char*)&rs_msg, CAN_RS_FRAME_LEN,
              CAN_RS_TOUT) == CAN_RS_FRAME_LEN) ? 0 : CAN_ERR_COM_WRITE;

    return err;
}


int can_filter_disable(t_can_hnd hnd)
{
    int err = 0;
    t_can_rs_msg rs_msg;
    rs_msg.hdr  = 0x8101;
    ((unsigned short int *)&rs_msg.data[0])[0] = 0;
    ((unsigned short int *)&rs_msg.data[0])[1] = 0;
    ((unsigned short int *)&rs_msg.data[0])[2] = 0;
    ((unsigned short int *)&rs_msg.data[0])[3] = 0;

    rs_msg.crc8 =  ~f_calc_crc8((unsigned char *)&rs_msg, CAN_RS_FRAME_LEN - 1);
    err = (com_write(hnd->com.hnd, (unsigned char*)&rs_msg, CAN_RS_FRAME_LEN,
              CAN_RS_TOUT) == CAN_RS_FRAME_LEN) ? 0 : CAN_ERR_COM_WRITE;

    return err;
}


int can_set_speed(t_can_hnd hnd, unsigned char spd)
{
    int err = 0;
    t_can_rs_msg rs_msg;
    rs_msg.hdr  = 0x8202;
    ((unsigned short int *)&rs_msg.data[0])[0] = spd;
    ((unsigned short int *)&rs_msg.data[0])[1] = 0;
    ((unsigned short int *)&rs_msg.data[0])[2] = 0;
    ((unsigned short int *)&rs_msg.data[0])[3] = 0;

    rs_msg.crc8 =  ~f_calc_crc8((unsigned char *)&rs_msg, CAN_RS_FRAME_LEN - 1);
    err = (com_write(hnd->com.hnd, (unsigned char*)&rs_msg, CAN_RS_FRAME_LEN,
              CAN_RS_TOUT) == CAN_RS_FRAME_LEN) ? 0 : CAN_ERR_COM_WRITE;

    return err;
}












