#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <wchar.h>
#include <ctype.h>
#include <wctype.h>
#include <time.h>

#include "Map.h"
#include "Errors.h"


/** 
 *  Detect UTF-8 character length
 */

int utf8_char_len(unsigned char c)
{
    if ((c & 0x80) == 0) return 1;
    if ((c & 0xE0) == 0xC0) return 2;
    if ((c & 0xF0) == 0xE0) return 3;
    if ((c & 0xF8) == 0xF0) return 4;
    return 1;
}


/** 
 *  Check is next char is a utf8 letter
 */

int is_utf8_letter(const unsigned char *s, int *len)
{
    unsigned char c1 = s[0];
    *len = utf8_char_len(c1);

    // ASCII
    if (*len == 1)
        return isalpha(c1);

    // UTF-8 de 2 bytes (Latin-1)
    if (*len == 2)
    {
        unsigned char c2 = s[1];

        // exclude symbols
        if (c1 == 0xC2 && (c2 == 0xA1 || c2 == 0xBF || c2 == 0xAB || c2 == 0xBB))
            return 0;

        // Accept letters in Latin-1 Supplement: U+00C0 - U+00FF
        if ((c1 == 0xC3 && c2 >= 0x80 && c2 <= 0xBF) || // à, á, â, ã, ä, å, æ, ç, è...ÿ
            (c1 == 0xC2 && c2 >= 0xC0 && c2 <= 0xFF))   // extended Latin-1 letters
            return 1;

        return 0; // others are symbols
    }

    // characters >2 bytes → are not letters
    return 0;
}


/** 
 *  Normalize word strings to utf8 and lowercase
 */

void normalize_utf8(char *word)
{
    wchar_t wbuf[DMAX_WORD];

    mbstowcs(wbuf, word, DMAX_WORD);

    for (int i = 0; wbuf[i]; i++)
        wbuf[i] = towlower(wbuf[i]);

    wcstombs(word, wbuf, DMAX_WORD);
}


/**
*  Output buffering
*/
void flush_out(int fd_out, char *buf, int *pos)
{
    if (*pos > 0)
    {
        if (write(fd_out, buf, *pos)<0)
            Error("[Map:flush_out] Error writting map result (final).\n");
        *pos = 0;
    }
}


/** 
 *  Mappper process an input file and returns the number of words found.
 */

int map_process_file(int fd_in, int fd_out) 
{
    char inbuf[DBUFFER_IN_SIZE];
    char outbuf[DBUFFER_OUT_SIZE];
    char word[DMAX_WORD];
    int wlen = 0, outpos = 0, words = 0;

    ssize_t n;

    if (DEBUG) {
        sprintf(MsgDebug,"[Map:map_process_file] map_process_file %d %d\n", fd_in, fd_out);
        printDebug(MsgDebug);
    } 

    while ((n = read(fd_in, inbuf, sizeof(inbuf))) > 0)
    {
        int i = 0;

        while (i < n)
        {
            unsigned char *p = (unsigned char *)&inbuf[i];
            int len;

            if (is_utf8_letter(p, &len))
            {
                // Copy entire UTF-8 character
                if (wlen + len < sizeof(word))
                {
                    memcpy(word + wlen, p, len);
                    wlen += len;
                }
            }
            else
            {
                // Emit word using buffer
                if (wlen > 0)
                {
                    // Write word to output buffer
                    if (outpos + wlen + 3 >= DBUFFER_OUT_SIZE)
                        flush_out(fd_out, outbuf, &outpos);

                    normalize_utf8(word);
                    memcpy(outbuf + outpos, word, wlen);
                    outpos += wlen;

                    outbuf[outpos++] = ' ';
                    outbuf[outpos++] = '1';
                    outbuf[outpos++] = '\n';
                    words++;

                    wlen = 0;
                }
            }

            i += len;
        }
    }

    /* Process last word if file does not end with a separator */
    if (wlen > 0)
    {
        if (outpos + wlen + 3 >= DBUFFER_OUT_SIZE)
            flush_out(fd_out, outbuf, &outpos);

        normalize_utf8(word);
        memcpy(outbuf + outpos, word, wlen);
        outpos += wlen;

        outbuf[outpos++] = ' ';
        outbuf[outpos++] = '1';
        outbuf[outpos++] = '\n';
        words++;
    }

    // Fina flush to file
    flush_out(fd_out, outbuf, &outpos);

    return (words);
}


/** 
 *  Function to perfom map stage. 
 *  Process all inpunt files an generate one or multiple map files depending on multiple_mappers value (to allow parallel processing of input)
 */

int wordcount_mapper(int ninputfiles, char *input_files[], bool multiple_mappers) 
{
    int fd_out, totalwords=0, words;
    struct timespec start, finish;
    float time;


    if (VERBOSE) {
        sprintf(Msg,"[Map Stage]\tStart Mapper to process %d input files.\n", ninputfiles);
        print(Msg);
    }

    if (DEBUG) {
        sprintf(MsgDebug,"[Map:wordcount_mapper] wordcount_mapper %d %s %d\n", ninputfiles, input_files[0], multiple_mappers);
        printDebug(MsgDebug);
    } 


    clock_gettime(CLOCK_MONOTONIC, &start);

    // Mapper Stage -> Process al input files calling map_process_file

    // Open only one mapper output file.
    if (multiple_mappers==false)
    {
        // Open one mapper output file.
        fd_out = open(DMapOutputFileName, O_WRONLY | O_CREAT | O_TRUNC, 0644);
        if (fd_out<0)
        {
            sprintf(MsgError,"[Map:wordcount_map] Error opening mapper output file %s\n",DMapOutputFileName);
            Error(MsgError);
        }
    }

    // Process input files
    for (int i = 0; i < ninputfiles; i++) 
    {
        int fd_in;

        fd_in = open(input_files[i], O_RDONLY);
        if (fd_in < 0)
        {
            sprintf(MsgError,"[Map:wordcount_map] Error opening mapper input file %s\n",input_files[i]);
            Error(MsgError);
        }

        if (multiple_mappers) 
        {
            /* Process all input files (one mapper for each file) and generate multiple output files */

            char map_file[128];
            sprintf(map_file, "%s%s_%d%s",DAuxiliarDiretory ,DMapFilePrefix, i, DAuxiliarFilesExtension);
            fd_out = open(map_file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
            if (fd_out<0)
            {
                sprintf(MsgError,"[Map:wordcount_map] Error opening mapper output file %s\n",map_file);
                Error(MsgError);
            }
        }

        words = map_process_file(fd_in,fd_out);
        totalwords += words;

        if (VERBOSE) 
        {
            sprintf(Msg,"\t\tMapper processed %d words from %s file.\n", words, input_files[i]);
            printMessage(Msg, COLOR_GREEN_L);
        }

        if (multiple_mappers) close(fd_out);
        close(fd_in);
    }
    close(fd_out);

    if (VERBOSE) 
    {
        clock_gettime(CLOCK_MONOTONIC, &finish);
        time = (finish.tv_sec - start.tv_sec)+((finish.tv_nsec - start.tv_nsec) / 1000000000.0);
        sprintf(Msg,"[Map Stage]\tMapper Done. Processed %d words from %d input files in %.3f secs.\n", totalwords, ninputfiles, time);
        print(Msg);
    }

    return totalwords;
}



