#include "msystem.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[] = {
    [MLOG_LVL_ASSERT]  = (F_MAGENTA  B_NULL S_NORMAL),
    [MLOG_LVL_ERROR]   = (F_RED      B_NULL S_NORMAL),
    [MLOG_LVL_WARN]    = (F_YELLOW   B_NULL S_NORMAL),
    [MLOG_LVAL_CCUINFO]= (F_WHITE    B_NULL S_NORMAL),
    [MLOG_LVL_INFO]    = (F_WHITE    B_NULL S_NORMAL),
    [MLOG_LVL_DEBUG]   = (F_GREEN    B_NULL S_NORMAL),
    [MLOG_LVL_VERBOSE] = (F_CYAN     B_NULL S_NORMAL),
};
static const char *console_level_info[] = {
    [MLOG_LVL_ASSERT]  = "[ASSERT]: ",
    [MLOG_LVL_ERROR]   = "[ERROR]: ",
    [MLOG_LVL_WARN]    = "[WARN]:",
    [MLOG_LVAL_CCUINFO]   = "[MLOG_LVAL_CCUINFO]:",             
    [MLOG_LVL_INFO]    = "[INFO]:",
    [MLOG_LVL_DEBUG]   = "[DEBUG]:",
    [MLOG_LVL_VERBOSE] = "[VERB]:",
};

//static uint32_t console_level_ctrl = (1<<MLOG_LVL_ASSERT)|(1<<MLOG_LVL_ERROR)|(1<<MLOG_LVL_WARN)|(1<<MLOG_LVL_INFO)|(1<<MLOG_LVL_DEBUG)|(1<<MLOG_LVL_VERBOSE);
static uint32_t console_level_ctrl = (1<<(MLOG_LVAL_CCUINFO+1)-1);

mlog_t clogger;

void mlog_control(int level, int on)
{
    if (level < MLOG_LVL_ASSERT) level = MLOG_LVL_ASSERT;
    if (level > MLOG_LVL_VERBOSE) level = MLOG_LVL_VERBOSE;

    if (on)
        console_level_ctrl |=  (1<<level);
    else
        console_level_ctrl &= ~(1<<level);
}

void show_flist(mlog_t* dev)
{
    int i;
    printf("\nflist.fnumb:%d\n", dev->fnumb);
    for (i=0; i<dev->fnumb; i++)
    {
        printf("flist:%d, utc:%ld, log_basename:%s\n", i, dev->flutc[i], dev->flist[i]);
    }
    printf("\n");
}
static void swap_filename(char* filename1, char* filename2)
{
    char filename0[256];
    sprintf(filename0, "%s", filename1);
    sprintf(filename1, "%s", filename2);
    sprintf(filename2, "%s", filename0);
}
static void swap_lutc(time_t lutc1, time_t lutc2)
{
    time_t lutc0;
    lutc0 = lutc1;
    lutc1 = lutc2;
    lutc2 = lutc0;
}
static void mlog_flast(mlog_t* dev, time_t lutc, char* log_fullname)
{
    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];
                    sprintf(dev->flist[i], "%s", dev->flist[i-1]);
                }else{
                    break;
                }
            }
            dev->flutc[i] = lutc;
            sprintf(dev->flist[i], "%s", log_fullname);
            dev->fnumb++;
        }
        else
        {
            if (lutc < dev->flutc[0]) return;//不需加入缓存队列
            char lbasename[256];
            char tbasename[512];
            sprintf(lbasename, "%s", log_fullname);
            for (i = dev->fnumb-1; i >= 0; i--)
            {
                if (lutc >= dev->flutc[i])
                {
                    swap_lutc(dev->flutc[i], lutc);
                    swap_filename(dev->flist[i], lbasename);
                }
                if (lutc == dev->flutc[i]) return;
            }
            snprintf(tbasename, 511, "%s/%s", dev->log_pathname, lbasename);
            remove(tbasename);
        }
        show_flist(dev);
    }
}

static void mlog_fload(mlog_t* dev)
{
    if (dev)
    {
        DIR *dir = NULL;
        int len = strlen(dev->log_basename);
        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_basename, len) == 0)
                {
                    // memset(&ltm, 9, sizeof(ltm));
                    ltm.tm_hour = 0;
                    ltm.tm_min = 0;
                    ltm.tm_sec = 0;
                    char* ftime = &dirent->d_name[len+1];
                    sscanf(ftime, "%4d%2d%2d.log", &ltm.tm_year, &ltm.tm_mon, &ltm.tm_mday);
                    ltm.tm_year -= 1900; 
                    ltm.tm_mon -= 1; 
                    ltm.tm_isdst = -1;
                    dev->utc = mktime(&ltm);
                    printf("dname:%s, utc:%lld\n", dirent->d_name, dev->utc);
                    mlog_flast(dev, dev->utc, dirent->d_name);
                }
            }
            closedir(dir);
        }
    }
}

static void mlog_fopen(mlog_t* dev)
{
    int len;
    char logfilename[256];
    if (dev)
    {
        if (dev->fp) fclose(dev->fp);
        
        struct tm ltm;
        time_t utc = time(NULL);
        localtime_r(&utc, &ltm);
        ltm.tm_hour = 0;
        ltm.tm_min = 0;
        ltm.tm_sec = 0;
        len = sprintf(logfilename, "%s_%04d%02d%02d.log", dev->log_basename, ltm.tm_year+1900, ltm.tm_mon+1, ltm.tm_mday);
        mlog_flast(dev, mktime(&ltm), logfilename);
        
        len = sprintf(logfilename, "%s/%s_%04d%02d%02d.log", dev->log_pathname, dev->log_basename, ltm.tm_year+1900, ltm.tm_mon+1, ltm.tm_mday);
        // printf("logfilename:%s\n", logfilename);
        dev->fp = fopen(logfilename, "a+");
        dev->log_filesize = 0;
    }
}

int mlog_print(int level, const char *tag, const char* FUNCNAME, const char* FILENAME, int LINENO, const char *format, ...)
{
    int  len = 0;
    char logline[MLOG_LINE_SIZE];
    char strline[MLOG_LINE_SIZE];
    mlog_t *dev = (mlog_t*)&clogger;
    if (dev)
    {
        if (level < MLOG_LVL_ASSERT) level = MLOG_LVL_ASSERT;
        if (level > MLOG_LVL_VERBOSE) level = MLOG_LVL_VERBOSE;
        if ((console_level_ctrl&(1<<level)) == 0) return 0;

        len += sprintf(&logline[len], "%s", CSI_START);
        len += sprintf(&logline[len], "%s", console_color_info[level]);
        len += sprintf(&logline[len], "%s", console_level_info[level]);
        {
            struct timeval tv;
            gettimeofday(&tv, NULL);
            len += sprintf(&logline[len], "[");
            len += sprintf_structTV(&logline[len], &tv);
            len += sprintf(&logline[len], "]");
        }

        if (tag) 
            len += sprintf(&logline[len], "[%s] ", tag);
        else
            len += sprintf(&logline[len], "[%s:%d %s] ", format_basename(strline, FILENAME), LINENO, FUNCNAME);

        {
            va_list args;
            va_start(args, format);
            len += vsnprintf(&logline[len], MLOG_LINE_SIZE - len, format, args);
            va_end(args);
        }
        if (logline[len-1]=='\n') logline[len-1]=' ';
        len += sprintf(&logline[len], "%s\n", CSI_END);

        // flush scream
        printf(logline);


        pthread_mutex_lock(&dev->mutex);
        int frees = MLOG_BUFF_SIZE - dev->bytes;
        if (len < frees)
        {
            if (dev->w_idx < dev->r_idx){
                memcpy(&dev->buff[dev->w_idx], logline, len);
            }else{
                int seg1 = MLOG_BUFF_SIZE - dev->w_idx;
                int seg2 = len - seg1;
                if (len <= seg1)
                {
                    memcpy(&dev->buff[dev->w_idx], logline, len);
                }else{
                    memcpy(&dev->buff[dev->w_idx], logline, seg1);
                    memcpy(&dev->buff[0], &logline[seg1], seg2);
                }
            }
            dev->w_idx = (dev->w_idx + len)%MLOG_BUFF_SIZE;
            dev->bytes += len;
        }else{
            printf("miss:%.*s", len, logline);
        }

        pthread_mutex_unlock(&dev->mutex);
        sem_post(&dev->sempare);
        return len;
    }
    return 0;
}

void* mlog_loop(void* args)
{
    int len;
    struct timespec  ts;
    static char logline[MLOG_BUFF_SIZE];
    mlog_t* dev = (mlog_t*)args;
    while(dev->quit == 0)
    {
        ts.tv_sec = time(NULL) + 1;
        ts.tv_nsec = 0;
        if (sem_timedwait(&dev->sempare, &ts) != 0) continue;

        len = 0;
        pthread_mutex_lock(&dev->mutex);
        if (dev->bytes > 0)
        {
            if (dev->w_idx > dev->r_idx)
            {
                len = dev->w_idx - dev->r_idx;
                memcpy(logline, &dev->buff[dev->r_idx], len);
            }else{
                len = MLOG_BUFF_SIZE - dev->r_idx;
                memcpy(&logline[0], &dev->buff[dev->r_idx], len);
                memcpy(&logline[len], &dev->buff[0], dev->w_idx);
                len += dev->w_idx;
            }
            dev->bytes -= len;
            dev->r_idx = (dev->r_idx + len)%MLOG_BUFF_SIZE;
        }
        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;
                mlog_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;
            //     mlog_fopen(dev);
            // }

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


void mlog_init(const char* log_filename, int max_filesize, int max_filenumb)
{
    mlog_t *dev = (mlog_t*)&clogger;
    printf("\n\tmlog_init(MAX_FILE_SIZE:%d MB, MAX_FILE_NUMB:%d)...\n\n", max_filesize/1024/1024, max_filenumb);
    if (dev)
    {
        memset(dev, 0, sizeof(mlog_t));
        // char* tmp0 = strdup(log_filename); sprintf(dev->log_pathname, "%s", dirname(tmp0)); free(tmp0);
        // char* tmp1 = strdup(log_filename); sprintf(dev->log_basename, "%s", basename(tmp1)); free(tmp1);
        sprintf_basename(dev->log_basename, log_filename);
        sprintf_pathname(dev->log_pathname, log_filename);
        printf("dev->log_basename:%s\n",dev->log_basename);
        printf("dev->log_pathname:%s\n",dev->log_pathname);
        dev->max_filesize = max_filesize;
        dev->max_filenumb = max_filenumb;
        dev->fnumb = 0;
        dev->bytes = 0;
        dev->r_idx = 0;
        dev->w_idx = 0;
        {
            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)*256);
                    if (dev->flist[i] == NULL)
                    {
                        printf("malloc(%d, %p) failed.\n", i, dev->flist[i]);
                    }
                }
            }else{
                printf("malloc(%p) failed.\n",dev->flist);
            }
        }
        mlog_fload(dev);

        time(&dev->utc);
        localtime_r(&dev->utc, &dev->ltm);
        mlog_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, mlog_loop, dev);
            pthread_attr_destroy(&thread_attr);
        }
    }
}

void mlog_exit(void)
{
    mlog_t *dev = (mlog_t*)&clogger;
    printf("\n\tmlog_exit...\n");
    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->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;
        }
    }
}

