#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>           // close()
#include <string.h>           // strcpy, memset(), and memcpy()
#include <stdarg.h>
#include <errno.h>            // errno, perror()
#include <stdbool.h>
#include <pthread.h>
#include <semaphore.h>
#include <math.h>
#include <dirent.h> 
#include <fcntl.h>
#include <signal.h>
#include <getopt.h>
#include <termios.h>
#include <netdb.h>            // struct addrinfo
#include <sys/stat.h>
#include <sys/ioctl.h>        // macro ioctl is defined
#include <bits/ioctls.h>      // defines values for argument "request" of ioctl.
#include <sys/syscall.h>
#include <sys/types.h>        // needed for socket(), uint8_t, uint16_t, uint32_t
#include <sys/select.h>
#include <sys/socket.h>       // needed for socket()
#include <sys/time.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <libgen.h>
#include <netinet/in.h>       // IPPROTO_ICMP, INET_ADDRSTRLEN
#include <netinet/ip.h>       // struct ip and IP_MAXPACKET (which is 65535)
#include <netinet/ip_icmp.h>  // struct icmp, ICMP_ECHO
#include <arpa/inet.h>        // inet_pton() and inet_ntop()
#include <net/if.h>           // struct ifreq
#include <net/ethernet.h>
#include <linux/if_ether.h>   // ETH_P_IP = 0x0800, ETH_P_IPV6 = 0x86DD
#include <linux/if_packet.h>  // struct sockaddr_ll (see man 7 packet)
#include <linux/serial.h>
#include <linux/can.h>
#include <linux/can/raw.h>

typedef enum
{
    ACCEPTED,
    REJECTED,
    SUCCESS,
    FAIL,
    IDLE,
    UPDATING,
}state_t;

typedef struct update
{
    int quit;
    int fd;
    int state;

    struct sockaddr_in peer_addr;
    socklen_t peer_addrlen;

    uint8_t rxbuff[1024];
    int rxbyte;

    uint8_t txbuff[1024];
    int txbyte;

    int filesize;
    int majorVersion;
    int minorVersion;
    int buildVersion;

    int major;
    int minor;
    int build;

    char ip_string[64];
    int port;

    FILE *logfp;
}update_t;

static update_t update =
{
    .fd = -1,
	.logfp = NULL
};

 #define printd(fmt, ...) printf("%s:%d, %s | "fmt, __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__)
//#define printd(fmt, ...) fprintf(dev->logfp, "%s:%d, %s | "fmt, __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__)

int wait_android_message(update_t* dev)
{
    dev->rxbyte = 0;
    while(dev->rxbyte < 1024)
    {
        int ret = recv(dev->fd, &dev->rxbuff[dev->rxbyte], 1, 0);
        if (ret <= 0) { 
            printd("ret : %d, errno:%d\n", ret, errno); 
            shutdown(dev->fd, SHUT_RDWR);
            close(dev->fd);
            dev->fd = -1;
            return -1; 
        }
        if (ret > 0) dev->rxbyte += ret;

        if (dev->rxbyte>=5)
        {
            if (strncmp(dev->rxbuff, "Event", 5)==0)
            {
                if (dev->rxbuff[dev->rxbyte-1] == '\n')
                {
                    printd("recv:%s\n",dev->rxbuff);
                    return 0;
                }
            }else{
                memmove(&dev->rxbuff[0], &dev->rxbuff[1], dev->rxbyte - 1);
                dev->rxbyte = dev->rxbyte - 1;
            }
        }
    }
    return -1;
}

int android_LoadVersion(update_t* dev, const char* filename)
{
    char filebuff[256];
    dev->major = dev->major = dev->build = 0;
    memset(filebuff,0,sizeof(filebuff));
    int ret = readlink(filename, filebuff, sizeof(filebuff));
    printd("app_version.linkname=%s\n", filebuff);
    if (ret > 0)
    {
       char* basename_str = strstr(filebuff, "app_XHHW_");
       printf("%s\n",basename_str);
       if (basename_str)
        {
            sscanf(basename_str, "app_XHHW_%d_%d_%d", &dev->major, &dev->minor, &dev->build);
            printd("app_XHHW_%d_%d_%d\n", dev->major, dev->minor, dev->build);
        }
    }
    return 0;
}


int android_DowanloadFile(update_t* dev, const char* filename)
{
    FILE *fp = fopen(filename, "a+");
    printf("fp =%p\n",fp);
    if (fp)
    {
        dev->state = UPDATING;
        int recved = 0;
        while(recved < dev->filesize)
        {
            int len = dev->filesize - recved;
            if (len > 1024) len = 1024;
            int ret = recv(dev->fd, dev->rxbuff, len, 0);
            if (ret <= 0) { 
                printd("ret:%d, errno:%d\n", ret, errno);
                shutdown(dev->fd, SHUT_RDWR);
                close(dev->fd);
                dev->fd = -1;
                return -1; 
            }
            if (ret > 0) recved += ret;
            fwrite(dev->rxbuff, ret, 1, fp);
        }
        
        if (recved == dev->filesize)
        {
            dev->state = SUCCESS;
        }else{
            dev->state = FAIL;
        }
        fclose(fp);
        printd("recvd.filename=%s, recvd.bytes:%d\n", filename, recved);
        return 0;
    }
    return -1;
}


int android_Upgrade(update_t* dev, const char* oldfilename, const char* newfilename)
{
    if (dev->state == SUCCESS)
    {
        int idx = 0;
        char cmdbuff[256];
        memset(cmdbuff,0,sizeof(cmdbuff));
        idx += sprintf(&cmdbuff[idx], "pkill app_XHHW\n");
        idx += sprintf(&cmdbuff[idx], "chmod +x %s\n", newfilename);
        idx += sprintf(&cmdbuff[idx], "rm -f %s\n", oldfilename);
        idx += sprintf(&cmdbuff[idx], "ln -s %s %s\n", newfilename, oldfilename);
        idx += sprintf(&cmdbuff[idx], "sync\n");
        idx += sprintf(&cmdbuff[idx], "reboot\n");
        int ret = system(cmdbuff);
        printf( "ret = %d, cmd = %s\n", ret, cmdbuff);

        // sprintf(cmdbuff, "chmod +x %s", newfilename);system(cmdbuff);
        // sprintf(cmdbuff, "rm -f %s", oldfilename);system(cmdbuff);
        // sprintf(cmdbuff, "ln -s %s %s", newfilename, oldfilename);system(cmdbuff);
        // sprintf(cmdbuff, "/home/root/bin/hmi.sh");system(cmdbuff);
        // chmod(filename, S_IRUSR|S_IWUSR|S_IXUSR | S_IXGRP|S_IRGRP|S_IXGRP | S_IROTH|S_IWOTH|S_IXOTH);
        // unlink(newfilename);
        // symlink(filename, newfilename);
        dev->state = IDLE;
        return 0;
    }
    return -1;
}

int android_NotifyUpdateFirmware_res(update_t* dev, const char* status)
{
    int len = sprintf(dev->txbuff, "Event=NotifyUpdateFirmware,Type=HW,Status=%s,Message=NONE\n", status);
    printd("send : %s\n",dev->txbuff);
    if (dev->fd > 0)
        return send(dev->fd, dev->txbuff, len, 0);
    else
        return -1;
}

int android_RequestUpdateFirmware_req(update_t* dev,int major, int minor, int build)
{
    int len = sprintf(dev->txbuff, "Event=UpdateFirmware,Type=HW,Version=%d.%d.%d\n",major, minor, build);
    printd("send : %s\n",dev->txbuff);
    if (dev->fd > 0)
        return send(dev->fd, dev->txbuff, len, 0);
    else
        return -1;
}

int android_NotifyUpdateFirmwareResult_res(update_t* dev, const char* status,int major, int minor, int build)
{
    int len = sprintf(dev->txbuff, "Event=NotifyUpdateFirmwareResult,Type=HW,Status=%s,Version=%d.%d.%d,Message=NONE\n", status, major, minor, build);
    printd("send : %s\n",dev->txbuff);
    if (dev->fd > 0)
        return send(dev->fd, dev->txbuff, len, 0);
    else
        return -1;
}

int android_GetFirmwareVersion_res(update_t* dev, int major, int minor, int build)
{
    int len = sprintf(dev->txbuff, "Event=GetFirmwareVersion,Type=HW,Version=%d.%d.%d\n", major, minor, build);
    printd("send : %s\n",dev->txbuff);
    if (dev->fd > 0)
        return send(dev->fd, dev->txbuff, len, 0);
    else
        return -1;
}

int android_connect(update_t *dev)
{
    if (dev)
    {
        while (dev->fd < 0)
        {
            dev->fd = socket(AF_INET, SOCK_STREAM, 0);
            if (dev->fd > 0)
            {
                dev->peer_addr.sin_family = AF_INET;
                dev->peer_addr.sin_addr.s_addr = inet_addr(dev->ip_string);
                dev->peer_addr.sin_port = htons(dev->port);
                dev->peer_addrlen = sizeof(dev->peer_addr);
                while(connect(dev->fd, (struct sockaddr*)&dev->peer_addr, dev->peer_addrlen) < 0){
                    printd("local socket connect to %s.%d failed.retry...\n", dev->ip_string, dev->port);
                    sleep(1);
                }
                printf("local socket connect to %s.%d ok.\n", dev->ip_string, dev->port);
                return 0;
            }
			printd("create socket failed.retry...\n");
            sleep(1);
        }
        return 0;
    }
    return -1;
}

void* xh_update_loop(void* argv)
{
    update_t *dev = (update_t*)argv;
    while(dev->quit == 0)
    {
        android_connect(dev);

        if (wait_android_message(dev) == 0)
        {
            if (strncmp(dev->rxbuff, "Event=NotifyUpdateFirmware,Type=HW", 34)==0)
            {
                char newfilename[256];
                char oldfilename[256];
                memset(newfilename,0,sizeof(newfilename));
                memset(oldfilename,0,sizeof(oldfilename));
                sprintf(oldfilename, "/home/root/bin/app_XHHW");
                sscanf(dev->rxbuff, "Event=NotifyUpdateFirmware,Type=HW,Size=%d,Version=%d.%d.%d\n", &dev->filesize, &dev->majorVersion, &dev->minorVersion, &dev->buildVersion);
                sprintf(newfilename, "/home/root/bin/app_XHHW_%d_%d_%d", dev->majorVersion, dev->minorVersion, dev->buildVersion);
                printf("as =%d,%d,%d,%d\n",dev->filesize, dev->majorVersion, dev->minorVersion, dev->buildVersion);
                if (dev->major != dev->majorVersion || dev->minor!=dev->minorVersion || dev->build!=dev->buildVersion)
                {
                    if (android_NotifyUpdateFirmware_res(dev, "Accepted") >= 0)
                    {
                    	sleep(1);
                        if (android_RequestUpdateFirmware_req(dev, dev->majorVersion, dev->minorVersion, dev->buildVersion) >= 0)
                        {   
                            if (android_DowanloadFile(dev, newfilename) == 0)
                            {
                                if (android_Upgrade(dev, oldfilename, newfilename) == 0)
                                {
                                    dev->major = dev->majorVersion;
                                    dev->minor = dev->minorVersion;
                                    dev->build = dev->buildVersion;
                                    android_NotifyUpdateFirmwareResult_res(dev, "Success", dev->majorVersion, dev->minorVersion, dev->buildVersion);
                                }
                                
                            }else{
                                android_NotifyUpdateFirmwareResult_res(dev, "Fail", dev->majorVersion, dev->minorVersion, dev->buildVersion);
                            }
                        }
                    }
                }else{
                    android_NotifyUpdateFirmware_res(dev, "Rejected");
                }
            }
            if (strncmp(dev->rxbuff, "Event=NotifyUpdateFirmwareResult,Type=HW", 40)==0)
            {
                continue;
            }
            if (strncmp(dev->rxbuff, "Event=GetFirmwareVersion,Type=HW", 32)==0)
            {
                android_GetFirmwareVersion_res(dev, dev->major, dev->minor, dev->build);
            }
        }
    }

    return NULL;
}

int xh_update_init(update_t *dev)
{
    if (dev)
    {
        dev->fd = -1;
        // dev->logfp = fopen("/home/root/bin/log.txt", "w");
        android_connect(dev);
        android_LoadVersion(dev, "/home/root/bin/app_XHHW");
    }
    return -1;
}

void xh_update_exit(update_t *dev)
{
    if (dev)
    {
        dev->quit = 1;
        close(dev->fd);
        dev->fd = -1;
    }
}

int main(int argc, char* argv[])
{
    sleep(10);
    update_t* dev = (update_t*)&update;
    {
        int c;
        dev->port = 20248;
        sprintf(dev->ip_string, "%s", "192.168.223.10");
        while ((c = getopt(argc, argv, "ip:")) != -1) {
            switch (c) {
            case 'i':
                sprintf(dev->ip_string, "%s", optarg);
                break;
            case 'p':
                dev->port = atoi(optarg);
                break;
            case '?':
                break;
            default:
                exit(2);
            }
        }
        xh_update_init(dev);
        xh_update_loop(dev);
        xh_update_exit(dev);
    }
    return 0;
}