#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>

#define CLIENT_FIFO "./client_%d"

struct Word
{
    int Amount;
    char* Text;
};

int wordComp(const void* e1, const void* e2)
{
    int a1 = ((struct Word*)e1)->Amount;
    int a2 = ((struct Word*)e2)->Amount;
    if(a1 < a2) return 1;
    if(a1 > a2) return -1;
    return 0;
}

void SendData(int fd, char* filename)
{
    // prepare the file processing with the all words list
    int in = open(filename, O_RDONLY);

    char word[4096];
    char c = 0;
    int i = 0;

    int wordsSize = 0, wordsCount = 10;
    struct Word* words = (struct Word*)malloc(wordsCount * sizeof(struct Word));

    // read char by char the input file
    int len = 0;
    while((len = read(in, &c, sizeof(char))) >= 1)
    {
        // if it's not a valid char it's interpreted as end of word
        if(c == '\n' || c == ',' || c == '.' || c == '\t' || c == '\r' || c == ' ')
        {
            // if the current word is empty skip the char
            if(i <= 0)
                continue;

            word[i] = '\0';

            // compare the current word with all the stored ones
            int founded = 0;
            for(int j = 0; j < wordsSize; ++j)
            {
                // if a word matches simply increase the counter
                if(strcmp(words[j].Text, word) == 0)
                {
                    founded = 1;
                    words[j].Amount++;
                    break;
                }
            }

            // if the word is new it will be inserted in the list
            if(founded == 0)
            {
                // if the words list capability is not enough, increase the size
                if(wordsSize == wordsCount - 1)
                {
                    struct Word* prevData = words;
                    words = (struct Word*)malloc(wordsCount * 2 * sizeof(struct Word));
                    memcpy(words, prevData, wordsCount * sizeof(struct Word));
                    wordsCount *= 2;
                }

                // build a new string to be stored
                int len = strlen(word);
                char* newStr = (char*)malloc(len + 1);
                memcpy(newStr, word, len);
                newStr[len] = '\0';

                // store the new string with amount 1
                words[wordsSize].Text = newStr;
                words[wordsSize++].Amount = 1;
            }

            i = 0;
        }
        else
        {
            // if the character is valid it will be added to the current word
            word[i++] = c;
        }
    }

    // sort all the words by the amount
    qsort(words, wordsSize, sizeof(struct Word), wordComp);

    // send the words:
    // 1. send the total amount size
    // 2. for each word:
    //    -- write the amount
    //    -- write the text size
    //    -- write the text chars
    write(fd, &wordsSize, sizeof(int));
    for(i = 0; i < wordsSize; ++i)
    {
        struct Word* w = &words[i];
        write(fd, &w->Amount, sizeof(int));

        int len = strlen(w->Text);
        write(fd, &len, sizeof(int));
        write(fd, w->Text, len);
    }

    close(in);
}

void HandleClient(char* clientFifoName, char* filename)
{
    printf("Client Handling!\n");

    // open the channel with the client
    int clientFifo = -1;
    if((clientFifo = open(clientFifoName, O_WRONLY)) == -1)
    {
        printf("Client handle error!\n");
        printf("Errno: %d\n", errno);
        return;
    }

    // handle the input file
    SendData(clientFifo, filename);

    close(clientFifo);
}

void Server(char* publicFifo)
{
    printf("Init Server!\n");

    // make public fifo
    if(mkfifo(publicFifo, 0666) == -1)
    {
        printf("Server error [MKFIFO]!\n");
        printf("Errno: %d\n", errno);
        return;
    }

    // open it in read only mode
    int fifo = -1;
    if((fifo = open(publicFifo, O_RDONLY)) == -1)
    {
        printf("Server error [OPEN FIFO]!\n");
        printf("Errno: %d\n", errno);
        return;
    }

    printf("Server is running and waiting for clients...\n");

    char clientInfo[4096];
    char clientFifo[4096];
    char filename[4096];

    // begin to listen
    while (1)
    {
        // if someone writes something
        if (read(fifo, clientInfo, 4096) > 0)
        {
            // split the information in client fifo name and input filename
            int len = strlen(clientInfo);
            memcpy(clientFifo, clientInfo, len + 1);

            len = strlen(clientInfo + len + 1);
            memcpy(filename, clientInfo + len + 1, len);

            // begin to fork to handle the client
            pid_t pid = fork();
            if (pid == 0)
            {
                HandleClient(clientFifo, filename);
                exit(0);
            }
            else if (pid < 0)
            {
                printf("Failed to fork process\n");
                printf("Errno: %d\n", errno);
            }
        }
    }

    close(fifo);
    unlink(publicFifo);
}

void Client(char* publicFifo, char* filename)
{
    printf("Init Client!\n");

    // build the client fifo name based on the client pid
    char clientFifo[4096];
    snprintf(clientFifo, 4096, CLIENT_FIFO, getpid());

    // make the client fifo
    if(mkfifo(clientFifo, 0666) == -1)
    {
        printf("Client error [MKFIFO]!\n");
        printf("Errno: %d\n", errno);
        return;
    }

    // open the public fifo
    int fifo = -1;
    if((fifo = open(publicFifo, O_WRONLY)) == -1)
    {
        printf("Client error [OPEN FIFO]!\n");
        printf("Errno: %d\n", errno);
        return;
    }

    // build the data for the server:
    // 1. clientFifo name
    // 2. input filename
    int clientLen = strlen(clientFifo), fileLen = strlen(filename);
    char* data = (char*)malloc(clientLen + 1 + fileLen + 1);
    memcpy(data, clientFifo, clientLen);
    data[clientLen] = '\0';
    memcpy(data + clientLen + 1, filename, fileLen);
    data[clientLen + fileLen + 1] = '\0';

    // send the data
    write(fifo, data, clientLen + 1 + fileLen + 1);
    close(fifo);

    printf("Client connected!\n");

    // open the client fifo
    int cFifo = -1;
    if ((cFifo = open(clientFifo, O_RDONLY)) == -1)
    {
        printf("Client error [OPEN CLIENT FIFO]!\n");
        printf("Errno: %d\n", errno);
        return;
    }

    // begin to read the server's data

    // raed the total amount size of words
    int size = 0;
    read(cFifo, &size, sizeof(int));

    // foreach word read the amount and the string
    for(int i = 0; i < size; ++i)
    {
        int amount = 0, len = 0;
        read(cFifo, &amount, sizeof(int));
        read(cFifo, &len, sizeof(int));

        char* text = (char*)malloc(len + 1);
        read(cFifo, text, len);
        text[len] = '\0';

        // print the result
        printf("%d: %s\n", amount, text);
    }
    
    close(cFifo);
    unlink(clientFifo);
}

int main(int argc, char* argv[])
{
    int p = fork();
    if(p == 0)
        Server("/tmp/publicFifo2");
    else
        Client("/tmp/publicFifo2", "input.txt");
        
  return 0;
}
