#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <limits.h>
#include <unistd.h>
#include <sys/stat.h>
#include <time.h>
#include <fts.h>
#include <errno.h>
#include "qwk.h"
#include "../../deps/jamlib/jam.h"
#include "../../src/inih/ini.h"

char *inbound_path;
char *message_base_path;
char *temp_dir;
char *unpack_cmd;

int recursive_delete(const char *dir) {
    int ret = 0;
    FTS *ftsp = NULL;
    FTSENT *curr;

    char *files[] = { (char *) dir, NULL };

    ftsp = fts_open(files, FTS_NOCHDIR | FTS_PHYSICAL | FTS_XDEV, NULL);
    if (!ftsp) {
        fprintf(stderr, "%s: fts_open failed: %s\n", dir, strerror(errno));
        ret = -1;
        goto finish;
    }

    while ((curr = fts_read(ftsp))) {
        switch (curr->fts_info) {
        case FTS_NS:
        case FTS_DNR:
        case FTS_ERR:
            fprintf(stderr, "%s: fts_read error: %s\n", curr->fts_accpath, strerror(curr->fts_errno));
            break;

        case FTS_DC:
        case FTS_DOT:
        case FTS_NSOK:
            break;

        case FTS_D:
            break;

        case FTS_DP:
        case FTS_F:
        case FTS_SL:
        case FTS_SLNONE:
        case FTS_DEFAULT:
            if (remove(curr->fts_accpath) < 0) {
                fprintf(stderr, "%s: Failed to remove: %s", curr->fts_path, strerror(errno));
                ret = -1;
            }
            break;
        }
    }

finish:
    if (ftsp) {
        fts_close(ftsp);
    }

    return ret;
}

s_JamBase *open_jam_base(char *path) {
	int ret;
	s_JamBase *jb;

	ret = JAM_OpenMB((char *)path, &jb);

	if (ret != 0) {
		if (ret == JAM_IO_ERROR) {
			free(jb);
			ret = JAM_CreateMB((char *)path, 1, &jb);
			if (ret != 0) {
				free(jb);
				return NULL;
			}
		} else {
			free(jb);
			return NULL;
		}
	}
	return jb;
}

static int handler(void* user, const char* section, const char* name,
                   const char* value)
{
    if (strcasecmp(section, "main") == 0) {
        if (strcasecmp(name, "message path") == 0) {
            message_base_path = strdup(value);
        } else if (strcasecmp(name, "inbound") == 0) {
            inbound_path = strdup(value);
        } else if (strcasecmp(name, "temp dir") == 0) {
            temp_dir = strdup(value);
        } else if (strcasecmp(name, "unpack command") == 0) {
            unpack_cmd = strdup(value);
        }
    }
    return 1;
}

size_t trimwhitespace(char *out, size_t len, const char *str) {
    if(len == 0)
        return 0;

    const char *end;
    size_t out_size;

    // Trim trailing space
    end = str + strlen(str) - 1;
    while(end > str && isspace((unsigned char)*end)) end--;
    end++;

    // Set output size to minimum of trimmed string length and buffer size minus 1
    out_size = (end - str) < len-1 ? (end - str) : len-1;

    // Copy trimmed string and add null terminator
    memcpy(out, str, out_size);
    out[out_size] = 0;

    return out_size;
}

int process_msgs_dat(char *msgsdat) {
    FILE *fptr;
    char buffer[PATH_MAX];
    struct QwkHeader qhdr;
    int msgrecs;
    char *msgbody;
    char mbuf[129];
    int i;
    time_t msgdate;
    struct tm thedate;
    int year;
    char msgto[26];
    char msgfrom[26];
    char msgsubj[26];
    unsigned int msgconf;
	s_JamBase *jb;
	s_JamMsgHeader jmh;
	s_JamSubPacket* jsp;
	s_JamSubfield jsf;
    int z;

    snprintf(buffer, PATH_MAX, "%s/%s", temp_dir, msgsdat);

    fptr = fopen(buffer, "rb");

    if (!fptr) {
        return -1;
    }

    fread(&qhdr, sizeof(struct QwkHeader), 1, fptr);
    while (!feof(fptr)) {
        if (fread(&qhdr, sizeof(struct QwkHeader), 1, fptr) != 1) {
            break;
        }
        msgrecs = atoi(qhdr.Msgrecs);
        msgbody = (char *)malloc((msgrecs * 128) + 1);
        memset(msgbody, 0, (msgrecs * 128) + 1);
        for (i=1;i<msgrecs;i++) {
            fread(mbuf, 1, 128, fptr);
            if (i == msgrecs - 1) {
                trimwhitespace(msgbody + ((i-1) * 128), 128, mbuf);
            } else {
                memcpy(msgbody + ((i-1) * 128), mbuf, 128);
            }
        }
        
        for (i = 0; i < strlen(msgbody); i++) {
            if (msgbody[i] == '\xe3') {
                msgbody[i] = '\r';
            }
        }

        memset(&thedate, 0, sizeof(struct tm));

        thedate.tm_mday = (qhdr.Msgdate[3] - '0') * 10 + (qhdr.Msgdate[4] - '0');
        thedate.tm_mon = ((qhdr.Msgdate[0] - '0') * 10 + (qhdr.Msgdate[1] - '0')) - 1;
        year = (qhdr.Msgdate[6] - '0') * 10 + (qhdr.Msgdate[7] - '0');
        if (year < 80) {
            year += 100;
        }
        thedate.tm_year = year;

        thedate.tm_hour = (qhdr.Msgtime[0] -'0') * 10 + (qhdr.Msgtime[1] - '0');
        thedate.tm_min = (qhdr.Msgtime[3] -'0') * 10 + (qhdr.Msgtime[4] - '0');
        
        msgdate = mktime(&thedate);

        memset(buffer, 0, PATH_MAX);
        memset(msgto, 0, 26);
        strncpy(buffer, qhdr.MsgTo, 25);
        trimwhitespace(msgto, 25, buffer);

        memset(buffer, 0, PATH_MAX);
        memset(msgfrom, 0, 26);
        strncpy(buffer, qhdr.MsgFrom, 25);
        trimwhitespace(msgfrom, 25, buffer);

        memset(buffer, 0, PATH_MAX);
        memset(msgsubj, 0, 26);
        strncpy(buffer, qhdr.MsgSubj, 25);
        trimwhitespace(msgsubj, 25, buffer);

        msgconf = ((qhdr.Msgareahi & 0xff) << 8) | qhdr.Msgarealo;

        snprintf(buffer, PATH_MAX, "%s/%d", message_base_path, msgconf);

        jb = open_jam_base(buffer);
        if (!jb) {
            fprintf(stderr, "Unable to open JAM base: %s\n", buffer);
            free(msgbody);
            return -1;
        }
        JAM_ClearMsgHeader( &jmh );
	    jmh.DateWritten = msgdate;

        jsp = JAM_NewSubPacket();

        jsf.LoID   = JAMSFLD_SENDERNAME;
        jsf.HiID   = 0;
        jsf.DatLen = strlen(msgfrom);
        jsf.Buffer = (char *)msgfrom;
        JAM_PutSubfield(jsp, &jsf);        

 		jsf.LoID   = JAMSFLD_RECVRNAME;
		jsf.HiID   = 0;
		jsf.DatLen = strlen(msgto);
		jsf.Buffer = (char *)msgto;
		JAM_PutSubfield(jsp, &jsf);       

        jsf.LoID   = JAMSFLD_SUBJECT;
        jsf.HiID   = 0;
        jsf.DatLen = strlen(msgsubj);
        jsf.Buffer = (char *)msgsubj;
        JAM_PutSubfield(jsp, &jsf);

        jmh.Attribute |= JAM_MSG_TYPEECHO;

        while (1) {
            z = JAM_LockMB(jb, 100);
            if (z == 0) {
                break;
            } else if (z == JAM_LOCK_FAILED) {
                sleep(1);
            } else {
                fprintf(stderr, "Failed to lock msg base!\n");
                JAM_CloseMB(jb);
                free(jb);	
                free(msgbody);
                return 1;
            }
        }
        if (JAM_AddMessage(jb, &jmh, jsp, (char *)msgbody, strlen(msgbody))) {
            fprintf(stderr, "Failed to add message\n");
            JAM_UnlockMB(jb);
            
            JAM_DelSubPacket(jsp);
            JAM_CloseMB(jb);
            free(jb);
            free(msgbody);
            return -1;
        } else {
            JAM_UnlockMB(jb);

            JAM_DelSubPacket(jsp);
            JAM_CloseMB(jb);
            free(jb);
            free(msgbody);
        }             
    }
    fclose(fptr);
    return 0;
}

int process_qwk_file(char *qwkfile) {
    // unpack file
    int i;
    char buffer[PATH_MAX];
    int bpos = 0;
    struct stat st;
    int ret;
    DIR *tmpb;
    struct dirent *dent;

    for (i=0;i<strlen(unpack_cmd);i++) {
		if (unpack_cmd[i] == '*') {
			i++;
			if (unpack_cmd[i] == 'a') {
				sprintf(&buffer[bpos], "%s/%s", inbound_path, qwkfile);
				bpos = strlen(buffer);
			} else if (unpack_cmd[i] == 'd') {
				sprintf(&buffer[bpos], "%s", temp_dir);
				bpos = strlen(buffer);				
			} else if (unpack_cmd[i] == '*') {
				buffer[bpos++] = '*';
				buffer[bpos] = '\0';
			}
		} else {
			buffer[bpos++] = unpack_cmd[i];
			buffer[bpos] = '\0';
		}
	}

    // check if tempdir exists

    if (stat(temp_dir, &st) == 0) {
        fprintf(stderr, "Temp Path exists! Please delete it first\n");
        return -1;
    }

    mkdir(temp_dir, 0755);

    ret = system(buffer);
    if (ret == -1 || ret >> 8 == 127) {
        return -1;
    }
    // process NDXs
	tmpb = opendir(temp_dir);
	if (!tmpb) {
		fprintf(stderr, "Error opening temp directory\n");
		return -1;
	}
	while ((dent = readdir(tmpb)) != NULL) {
        if (strcasecmp(dent->d_name, "messages.dat") == 0) {
	    	// process tic file
            ret = process_msgs_dat(dent->d_name);
        }
	}
	closedir(tmpb);

    // delete temp dir
    recursive_delete(temp_dir);
    snprintf(buffer, PATH_MAX, "%s/%s", inbound_path, qwkfile);
    remove(buffer);
    return ret;
}

int main(int argc, char **argv) {
    // read ini file
    DIR *inb;
    struct dirent *dent;


    message_base_path = NULL;
    inbound_path = NULL;
    temp_dir = NULL;
    if (argc < 2) {
        fprintf(stderr, "Usage:\n    ./qwktoss config.ini\n");
        return -1;
    }

	if (ini_parse(argv[1], handler, NULL) <0) {
		fprintf(stderr, "Unable to load configuration ini (%s)!\n", argv[1]);
		exit(-1);
	}

    if (temp_dir == NULL || message_base_path == NULL || inbound_path == NULL) {
        fprintf(stderr, "Message Base Path and Inbound Path must be set\n");
        exit(-1);
    }

    // scan for QWK files
	inb = opendir(inbound_path);
	if (!inb) {
		fprintf(stderr, "Error opening inbound directory\n");
		return -1;
	}
	while ((dent = readdir(inb)) != NULL) {
		if (dent->d_name[strlen(dent->d_name) - 4] == '.' &&
				tolower(dent->d_name[strlen(dent->d_name) - 3]) == 'q' &&
				tolower(dent->d_name[strlen(dent->d_name) - 2]) == 'w' &&
				tolower(dent->d_name[strlen(dent->d_name) - 1]) == 'k'
			) {
				// process qwk file
				fprintf(stderr, "Processing QWK file %s\n", dent->d_name);
				if (process_qwk_file(dent->d_name) != -1) {
                    
					rewinddir(inb);
				}
			}
	}
	closedir(inb);

    return 0;
}