#include "tc_system.h"

/**
 * CSI(Control Sequence Introducer/Initiator) sign
 * more information on https://en.wikipedia.org/wiki/ANSI_escape_code
 */
#define CSI_START                      "\033["
#define CSI_END                        "\033[0m"
/* output log front color */
#define F_BLACK                        "30;"
#define F_RED                          "31;"
#define F_GREEN                        "32;"
#define F_YELLOW                       "33;"
#define F_BLUE                         "34;"
#define F_MAGENTA                      "35;"
#define F_CYAN                         "36;"
#define F_WHITE                        "37;"
/* output log background color */
#define B_NULL
#define B_BLACK                        "40;"
#define B_RED                          "41;"
#define B_GREEN                        "42;"
#define B_YELLOW                       "43;"
#define B_BLUE                         "44;"
#define B_MAGENTA                      "45;"
#define B_CYAN                         "46;"
#define B_WHITE                        "47;"
/* output log fonts style */
#define S_BOLD                         "1m"
#define S_UNDERLINE                    "4m"
#define S_BLINK                        "5m"
#define S_NORMAL                       "22m"
/* output log default color definition: [front color] + [background color] + [show style] */

/* color output info */
static const char *console_color_info[] = {
    [CLOG_LVL_ASSERT]  = (F_MAGENTA B_NULL S_NORMAL),
    [CLOG_LVL_ERROR]   = (F_RED B_NULL S_NORMAL),
    [CLOG_LVL_WARN]    = (F_YELLOW B_NULL S_NORMAL),
    [CLOG_LVL_INFO]    = (F_CYAN B_NULL S_NORMAL),
    [CLOG_LVL_DEBUG]   = (F_GREEN B_NULL S_NORMAL),
    [CLOG_LVL_VERBOSE] = (F_BLUE B_NULL S_NORMAL),
};

typedef struct clog
{
    char  buff[CLOG_BUFF_SIZE];
    int idx_r;
    int idx_w;
    int bytes;
    
    sem_t sempare;
    pthread_mutex_t mutex;
    pthread_t threadID;

    char *log_pathname;
    char *log_filename;
    FILE *fp;

    int log_filesize; // bytes current file
    int max_filesize; // bytes pre file
    int max_filenumb; // files from running

    time_t  utc;
    struct tm ltm;

    int quit;

    char** flist;
    time_t *flutc;
    int fnumb;

    //----------------
    int xhlog_fd;
    struct sockaddr_in peer_addr;
    socklen_t peer_addrlen;
    // struct sockaddr_in self_addr;
    // socklen_t self_addrlen;
}clog_t;

static clog_t clogger;

void clog_bind_serverip(const char* serverip, short port)
{
    clog_t *dev = (clog_t*)&clogger;
    dev->xhlog_fd = socket(AF_INET, SOCK_DGRAM, 0);
    dev->peer_addr.sin_family = AF_INET;
    dev->peer_addr.sin_addr.s_addr = inet_addr(serverip);
    dev->peer_addr.sin_port = htons(port);
    dev->peer_addrlen = sizeof(dev->peer_addr);
    printf("clog.sendto(%s.%d).\n", serverip, port);
}

int clog_sendto_serverip(uint8_t *loginfo)
{
    clog_t *dev = (clog_t*)&clogger;
    if (dev->xhlog_fd > 0)
    {
        char logline[CLOG_LINE_SIZE]={0};
        int length = sprintf(logline, "HW | %s", loginfo);
        int ret = sendto(dev->xhlog_fd, logline, length, 0, (struct sockaddr*)&dev->peer_addr, dev->peer_addrlen);
        if (ret < 0)
        {
            printf("clog_sendto_serverip.errno=%d, ret=%d, log:%s\n", errno, ret, logline);
        }
        return ret;
    }
    return -1;
}

void show_flist(clog_t* dev)
{
    int i;
    printf("\nflist.fnumb:%d\n", dev->fnumb);
    for (i=0; i<dev->fnumb; i++)
    {
        printf("flist:%d, utc:%ld, log_filename:%s\n", i, dev->flutc[i], dev->flist[i]);
    }
    printf("\n");
}

static void clog_flast(clog_t* dev, time_t lutc, char* log_filename)
{
    if (dev)
    {
        int i;
        if (dev->fnumb < dev->max_filenumb)
        {
            for (i = dev->fnumb; i > 0; i--)
            {
                if (lutc == dev->flutc[i - 1]){
                    //在缓存队列中已经存在这个文件，不行再次保存这个文件
                    return;
                }else if (lutc < dev->flutc[i - 1])
                {
                    dev->flutc[i] = dev->flutc[i-1];
                    strcpy(dev->flist[i], dev->flist[i-1]);
                }else{
                    break;
                }
            }
            dev->flutc[i] = lutc;
            strcpy(dev->flist[i], log_filename);
            dev->fnumb++;
        }
        else
        {
            if (lutc < dev->flutc[0]) return;//不需加入缓存队列
            time_t tutc = lutc;
            char lfilename[256];
            char tfilename[512];
            char *pfilename = lfilename;
            strcpy(lfilename, log_filename);
            for (i = dev->fnumb - 1; i >= 0; i--)
            {
                if (lutc > dev->flutc[i])
                {
                    tutc = dev->flutc[i];
                    dev->flutc[i] = lutc;
                    lutc = tutc;

                    strcpy(tfilename, dev->flist[i]);
                    strcpy(dev->flist[i], lfilename);
                    strcpy(lfilename, tfilename);
                }else{
                    break;
                }
                if (lutc == dev->flutc[i]) return;
            }
            snprintf(tfilename, 511, "%s/%s", dev->log_pathname, pfilename);
            remove(tfilename);
        }
        // show_flist(dev);
    }
}

static void clog_fload(clog_t* dev)
{
    if (dev)
    {
        DIR *dir = NULL;
        int len = strlen(dev->log_filename);
        dir = opendir(dev->log_pathname);
        if (dir)
        {
            struct tm ltm;
            struct dirent *dirent = NULL;
            while ((dirent = readdir(dir)))
            {
                if (strncmp(dirent->d_name, dev->log_filename, len)==0)
                {
                    // memset(&ltm, 9, sizeof(ltm));
                    char* ftime = &dirent->d_name[len+1];
                    sscanf(ftime, "%4d%2d%2d_%2d%2d%2d.log", &ltm.tm_year, &ltm.tm_mon, &ltm.tm_mday, &ltm.tm_hour, &ltm.tm_min, &ltm.tm_sec);
                    ltm.tm_year -= 1900; ltm.tm_mon -= 1; ltm.tm_isdst = -1;
                    dev->utc = mktime(&ltm);
                    clog_flast(dev, dev->utc, dirent->d_name);
                }
            }
            closedir(dir);
        }
    }
}

static void clog_fopen(clog_t* dev)
{
    int len;
    char logfilename[256];
    if (dev)
    {
        if (dev->fp) fclose(dev->fp);
        len = sprintf(logfilename, "%s_%04d%02d%02d_%02d%02d%02d.log", dev->log_filename, dev->ltm.tm_year+1900, dev->ltm.tm_mon+1, dev->ltm.tm_mday, dev->ltm.tm_hour, dev->ltm.tm_min, dev->ltm.tm_sec);
        clog_flast(dev, mktime(&dev->ltm), logfilename);
        len = sprintf(logfilename, "%s/%s_%04d%02d%02d_%02d%02d%02d.log", dev->log_pathname, dev->log_filename, dev->ltm.tm_year+1900, dev->ltm.tm_mon+1, dev->ltm.tm_mday, dev->ltm.tm_hour, dev->ltm.tm_min, dev->ltm.tm_sec);
        // printf("logfilename:%s\n", logfilename);
        dev->fp = fopen(logfilename, "a+");
        dev->log_filesize = 0;
    }
}

int clog_print(int level, const char *tag, const char* FUNCNAME, const char* FILENAME, int LINENO, const char *format, ...)
{
    int  len = 0;
    char logline[CLOG_LINE_SIZE]={0};
    char strltime[32];
    clog_t *dev = (clog_t*)&clogger;
    if (dev)
    {
        if (level<0) level = 0;
        if (level>6) level = 6;
        // if (tag == NULL) tag = "NON";

        len += sprintf(&logline[len], "%s", CSI_START);
        len += sprintf(&logline[len], "%s", console_color_info[level]);

        {
            struct tm ltm;
            time_t utc = time(NULL);
            localtime_r(&utc, &ltm);
            len += strftime(&logline[len], 32, "%F %T", &ltm);
            len += snprintf(&logline[len], 16, " PID:%6d", getpid());
            len += snprintf(&logline[len], 16, " TID:%6ld", syscall(SYS_gettid));
        }

        if (tag) { len += snprintf(&logline[len], 6, " %s ", tag); }

        len += snprintf(&logline[len], 64, " %-33s %17s:%3d | ", FUNCNAME, basename((char*)FILENAME), LINENO);

        {
            va_list args;
            va_start(args, format);
            len += vsnprintf(&logline[len], CLOG_LINE_SIZE - len, format, args);
            va_end(args);
        }

        len += sprintf(&logline[len], "%s\n", CSI_END);

        clog_sendto_serverip(logline);

        pthread_mutex_lock(&dev->mutex);
        if ((dev->idx_w + len) <= CLOG_BUFF_SIZE)
        {
            memcpy(&dev->buff[dev->idx_w], logline, len);
            dev->idx_w += len;
        }else{
            int seg1 = CLOG_BUFF_SIZE - dev->idx_w;
            int seg2 = len - seg1;
            memcpy(&dev->buff[dev->idx_w], logline, seg1);
            memcpy(&dev->buff[0], &logline[seg1], seg2);
            dev->idx_w = seg2;
        }
        if ((dev->bytes + len) <= CLOG_BUFF_SIZE)
        {
            dev->bytes += len;
        }else{
            dev->idx_r = dev->idx_w;
            dev->bytes = CLOG_BUFF_SIZE;
        }
        pthread_mutex_unlock(&dev->mutex);
        sem_post(&dev->sempare);
        return len;
    }
    return 0;
}

void* clog_loop(void* args)
{
    int len;
    static char logline[CLOG_BUFF_SIZE];
    clog_t* dev = (clog_t*)args;
    while(dev->quit == 0)
    {
        sem_wait(&dev->sempare);
        len = 0;
        pthread_mutex_lock(&dev->mutex);
        if (dev->bytes > 0)
        {
            if (dev->idx_w > dev->idx_r)
            {
                len = dev->idx_w - dev->idx_r;
                memcpy(logline, &dev->buff[dev->idx_r], len);
            }else{
                len = CLOG_BUFF_SIZE - dev->idx_r;
                memcpy(&logline[0], &dev->buff[dev->idx_r], len);
                memcpy(&logline[len], &dev->buff[0], dev->idx_w);
                len += dev->idx_w;
            }
            dev->bytes -= len;
            dev->idx_r = dev->idx_w;
        }
        pthread_mutex_unlock(&dev->mutex);

        if (len > 0)
        {
            printf("%.*s", len, logline);
            
            struct tm ltm;
            time_t utc = time(NULL);
            localtime_r(&utc, &ltm);

            if (ltm.tm_yday != dev->ltm.tm_yday)
            {
                dev->ltm = ltm;
                clog_fopen(dev);
            }

            // printf(".size:%d, len:%d, all(%d), max(%d)\n", dev->log_filesize, len, (dev->log_filesize + len), dev->max_filesize);

            // if ((dev->log_filesize + len) >= dev->max_filesize)
            // {
            //     dev->ltm = ltm;
            //     clog_fopen(dev);
            // }

            // if ((dev->log_filesize + len) <= dev->max_filesize)
            {
                if (dev->fp) 
                {
                    dev->log_filesize += len;
                    fwrite(logline, len, 1, dev->fp);
                    fflush(dev->fp);
                }
            }
        }
    }
    return NULL;
}


void clog_init(const char* log_filename, int max_filesize, int max_filenumb)
{
    clog_t *dev = (clog_t*)&clogger;
    if (dev)
    {




        char* tmp0 = strdup(log_filename);
        char* tmp1 = strdup(log_filename);
        memset(dev, 0, sizeof(clog_t));
        dev->log_pathname = strdup(dirname((char*)tmp0));
        dev->log_filename = strdup(basename((char*)tmp1));
        dev->max_filesize = max_filesize;
        dev->max_filenumb = max_filenumb;
        dev->fnumb = 0;
        dev->bytes = 0;
        free(tmp0);
        free(tmp1);
        {
            int flen = strlen(dev->log_filename) + 32;
            dev->flutc = (time_t*)malloc(sizeof(time_t) * dev->max_filenumb);
            if (dev->flutc == NULL)
            {
                printf("malloc(%p) failed.\n",dev->flutc);
            }
            dev->flist = (char**)malloc(sizeof(char*) * dev->max_filenumb);
            if (dev->flist)
            {
                int i;
                for (i = 0; i<dev->max_filenumb; i++)
                {
                    dev->flist[i] = malloc(sizeof(char)*flen);
                    if (dev->flist[i] == NULL)
                    {
                        printf("malloc(%d, %p) failed.\n", i, dev->flist[i]);
                    }
                }
            }else{
                printf("malloc(%p) failed.\n",dev->flist);
            }
        }
        clog_fload(dev);

        time(&dev->utc);
        localtime_r(&dev->utc, &dev->ltm);
        clog_fopen(dev);

        dev->quit = 0;
        pthread_mutex_init(&dev->mutex, NULL);
        sem_init(&dev->sempare, 0, 0);
        {
            pthread_attr_t thread_attr;
            struct sched_param thread_sched_param;
            pthread_attr_init(&thread_attr);
            //pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
            pthread_attr_setstacksize(&thread_attr, 1024);
            pthread_attr_setschedpolicy(&thread_attr, SCHED_RR);
            thread_sched_param.sched_priority = sched_get_priority_max(SCHED_RR) - 1;
            pthread_attr_setschedparam(&thread_attr, &thread_sched_param);
            pthread_create(&dev->threadID, &thread_attr, clog_loop, dev);
            pthread_attr_destroy(&thread_attr);
        }
    }
}

void clog_exit(void)
{
    clog_t *dev = (clog_t*)&clogger;
    if (dev)
    {
        dev->quit = 1;
        pthread_join(dev->threadID, NULL);

        if (dev->fp) 
        {
            fflush(dev->fp);
            fclose(dev->fp);
            dev->fp = NULL;
        }

        pthread_mutex_destroy(&dev->mutex);
        sem_destroy(&dev->sempare);

        if (dev->log_filename)
        {
            free(dev->log_filename);
            dev->log_filename = NULL;
        }

        if (dev->log_pathname)
        {
            free(dev->log_pathname);
            dev->log_pathname = NULL;
        }

        if (dev->flist)
        {
            int i;
            for (i = 0; i<dev->max_filenumb; i++)
            {
                if (dev->flist[i])
                {
                    free(dev->flist[i]);
                    dev->flist[i] = NULL;
                }                
            }
            free(dev->flist);
            dev->flist = NULL;
        }
        if (dev->flutc)
        {
            free(dev->flutc);
            dev->flutc = NULL;
        }
    }
}

