/*
 *  cacamoo
 *  Copyright (c) 2006 Jean-Yves Lamoureux <jylam@lnxscene.org>
 *                All Rights Reserved
 *
 *  $Id$
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the Do What The Fuck You Want To
 *  Public License, Version 2, as published by Sam Hocevar. See
 *  http://sam.zoy.org/wtfpl/COPYING for more details.
 */


#include "config.h"

#if defined(HAVE_INTTYPES_H)
#   include <inttypes.h>
#endif
#if defined(HAVE_GETOPT_H)
#   include <getopt.h>
#endif
#if defined(HAVE_SYS_IOCTL_H) && defined(TIOCGWINSZ)
#   include <sys/ioctl.h>
#endif

#include "cacamoo.h"
#include <cucul.h>

char const *cacamoo_export = "utf8";
char const *cacamoo_file = "default";
char const *cacamoo_dir = "/usr/share/cowsay/cows";

/* Default glyphs */
char *cacamoo_eyes = "oo";
char *cacamoo_tongue = "  ";
char *cacamoo_thoughts = "\\";
char *cacamoo_think = "o";
char *cacamoo_borg = "==";
char *cacamoo_tired = "--";
char *cacamoo_dead = "xx";
char *cacamoo_greedy = "$$";
char *cacamoo_parano = "@@";
char *cacamoo_stoned = "**";
char *cacamoo_youth = "..";
char *cacamoo_wired = "OO";
char cacamoo_use_eyes[3] = {' ',' ',0};
char cacamoo_use_tongue[3] = {' ',' ',0};

/* String we have to display */
char *string = NULL;
/* Wrapped and balloonified */
char *wrapped = NULL;


/* Width */
unsigned int term_width = 40-1; /* Default for cowsay */

/* Think ? */
unsigned char think = 0;

/* Unicode */
unsigned char unicode = 0;


int main (int argc, char **argv)
{
    int i, length;
    char *buffer = NULL;
    unsigned int buffer_size = 0;
    unsigned int new_width = 0;
    char *initial = NULL;
    unsigned int no_wrap = 0;
    cucul_buffer_t* input_buffer;
    cucul_buffer_t* output_buffer;
    cucul_canvas_t* canvas;
    int buf_size;
    char *buf_data;

    if ((strstr(argv[0], "cacathink")) != NULL) {
        think = 1;
    }

#if defined(HAVE_GETOPT_H)
    for(;;)
    {
#   ifdef HAVE_GETOPT_LONG
#       define MOREINFO "Try `%s --help' for more information.\n"
        int option_index = 0;
        static struct option long_options[] =
            {
                /* Long option, needs arg, flag, short option */
                { "file", 1, NULL, 'f' },
                { "directory", 1, NULL, 'D' },
                { "width", 1, NULL, 'W' },
                { "no-wrap", 1, NULL, 'n' },
                { "eyes", 1, NULL, 'e' },
                { "tongue", 1, NULL, 'T' },
                { "borg", 1, NULL, 'b' },
                { "tired", 1, NULL, 't' },
                { "dead", 1, NULL, 'd' },
                { "greedy", 1, NULL, 'g' },
                { "parano", 1, NULL, 'p' },
                { "stoned", 1, NULL, 's' },
                { "youth", 1, NULL, 'y' },
                { "wired", 1, NULL, 'w' },
                { "think", 1, NULL, 'O' },
                { "unicode", 1, NULL, 'u' },
                { "version", 0, NULL, 'v' },
                { "list-files", 0, NULL, 'l' },
                { "help", 0, NULL, 'h' },
                { NULL, 0, NULL, 0 }
            };



        int c = getopt_long(argc, argv, "D:f:W:e:T:hvObtdgpsylwnu",
                            long_options, &option_index);
#   else
#       define MOREINFO "Try `%s -h' for more information.\n"
        int c = getopt(argc, argv, "D:f:W:hvObtdgpsylwnu");
#   endif
        if(c == -1)
            break;

        switch(c)
        {
        case 'h': /* --help */
            usage();
            return 0;
        case 'v': /* --version */
            version();
            return 0;
        case 'f': /* --file*/
            cacamoo_file = optarg;
            break;
        case 'D': /* --directory */
            cacamoo_dir = optarg;
            break;
        case 'e': /* --eyes*/
            cacamoo_eyes = optarg;
            break;
        case 'b': /* --borg*/
            cacamoo_eyes = cacamoo_borg;
            break;
        case 't': /* --tired*/
            cacamoo_eyes = cacamoo_tired;
            break;
        case 'd': /* --dead*/
            cacamoo_eyes = cacamoo_dead;
            break;
        case 'g': /* --greedy*/
            cacamoo_eyes = cacamoo_greedy;
            break;
        case 'p': /* --parano*/
            cacamoo_eyes = cacamoo_parano;
            break;
        case 's': /* --stoned*/
            cacamoo_eyes = cacamoo_stoned;
            break;
        case 'y': /* --youth*/
            cacamoo_eyes = cacamoo_youth;
            break;
        case 'w': /* --wired*/
            cacamoo_eyes = cacamoo_wired;
            break;
        case 'T': /* --tongue */
            cacamoo_tongue = optarg;
            break;
        case 'O': /* --thoughts */
            think = 1;
            break;
        case 'W': /* --width */
            term_width = strtol(optarg, NULL, 10);
            if(term_width && (term_width != 1)) term_width--;
            break;
        case 'l': /* --list-files */
            list_files(".");
            list_files(cacamoo_dir);
            return 0;
            break;
        case 'u': /* --unicode */
            unicode = 1;
            break;
        case 'n': /* --no-wrap */
            no_wrap = 1;
            break;
        case '?':
            printf(MOREINFO, argv[0]);
            return 1;
        default:
            printf("%s: invalid option -- %c\n", argv[0], c);
            printf(MOREINFO, argv[0]);
            return 1;
        }
    }
#else
#   define MOREINFO "Usage: %s message...\n"
    int optind = 1;
#endif

    if(think)
        cacamoo_thoughts = cacamoo_think;

    /* Load rest of commandline */
    for(i = optind, length = 0; i < argc; i++)
    {
        unsigned int k, guessed_len, real_len = 0;

        guessed_len = strlen(argv[i]);

        if(i > optind)
            string[length++] = ' ';

        string = realloc(string, (length + guessed_len + 1));

        for(k = 0, real_len = 0; k < guessed_len; real_len++)
        {
            string[length + real_len] = *(argv[i]+k);
            k ++;
        }
        length += real_len;
    }

    if(string == NULL) {
        usage();
        return -1;
    }

    string[length] = 0;


    /* Eyes and tongue are 2 characters wide */
    memcpy(cacamoo_use_eyes, cacamoo_eyes, strlen(cacamoo_eyes)>2?2:strlen(cacamoo_eyes));
    memcpy(cacamoo_use_tongue, cacamoo_tongue, strlen(cacamoo_tongue)>2?2:strlen(cacamoo_tongue));

    initial = malloc(strlen(string)+1);
    memcpy(initial, string, strlen(string)+1);
    free(string);

    wrapped = wrap_string(initial, term_width, &new_width, no_wrap);
    string = wrapped;

    buffer = make_caca_from_file(&buffer_size);
    if(buffer == NULL)
    {
        if(string)
            free(string);
        return -1;
    }


    /* Import our buffer as an ansi (color) one */
    input_buffer = cucul_load_memory(buffer, buffer_size-1);
    if(input_buffer == NULL)
    {
        printf("Can't load file in libcucul !\n");
        return -1;
    }
    canvas = cucul_import_canvas (input_buffer, unicode?"utf8":"ansi");
    if(canvas == NULL)
    {
        printf("Can't load file in libcucul !\n");
        return -1;
    }
    /* Export given canvas to format we want */
    output_buffer = cucul_export_canvas(canvas, "ansi");
    if(output_buffer == NULL)
    {
        printf("Can't export file to text !\n");
        return -1;
    }

    buf_size = cucul_get_buffer_size(output_buffer);
    buf_data = cucul_get_buffer_data(output_buffer);

    for(i = 0; i < buf_size; i++)
        printf("%c", buf_data[i]);

    if(string)
        free(string);
    if(buffer)
        free(buffer);

    cucul_free_buffer(input_buffer);
    cucul_free_buffer(output_buffer);
    cucul_free_canvas(canvas);


    return 0;
}


void list_files(const char *directory)
{
    struct dirent * dp;
    int count = 0;
    DIR *dir = opendir(directory);


    for (dp = readdir(dir); dp != NULL; dp = readdir(dir))
    {
        if(!strncmp(&dp->d_name[strlen(dp->d_name)-4], ".cow", 4))
        {
            char name[256];
            memcpy(name, dp->d_name, strlen(dp->d_name)-4);
            name[strlen(dp->d_name)-4] = 0;
            printf("%s ", name);
            count++;
            if(!(count%6))
                printf("\n");
        }
    }
    closedir(dir);
    if(count)
        printf("\n");
    return;
}

char * make_caca_from_file(unsigned int *size)
{
    FILE *fp = NULL;
    char filepath[1024];
    unsigned int s = 0;
    char *temp = NULL;

    /* Try direct name */
    snprintf(filepath, 1023, "%s", cacamoo_file);
    fp = fopen(filepath, "r");
    if(fp == NULL)
    {
        /* Try direct file + .cow */
        snprintf(filepath, 1023, "%s.cow", cacamoo_file);
        fp = fopen(filepath, "r");
        if(fp == NULL)
        {
            /* Try with complete directory */
            snprintf(filepath, 1023, "%s/%s.cow", cacamoo_dir, cacamoo_file);
            fp = fopen(filepath, "r");
            if(fp == NULL)
            {
                printf("Can't open %s\n", filepath);
                perror("fopen");
                return NULL;
            }
        }
    }


    fseek(fp, 0, SEEK_END);
    s = ftell(fp);
    fseek(fp, 0, SEEK_SET);


    temp = malloc(s+1*sizeof(char));
    if(temp == NULL) {
        printf("Not enough memory.\n");
        return NULL;
    }

    if(fread(temp, 1, s, fp)!=s)
    {
        printf("Can't read %s\n", filepath);
        perror("fread");
        return NULL;
    }
    temp[s] = '\0';

    temp = remove_comments(temp);
    temp = remove_slashes(temp);


    /* AHAHAH, THAT'S A COOL PERL INTERPRETER ! */
    temp = replace(temp, " = <<\"EOC\";", "");
    temp = replace(temp, " = <<EOC;"    , "");
    temp = replace(temp, " = <<EOC"     , "");
    temp = replace(temp, " = << EOC"    , "");
    temp = replace(temp, "EOC"          , "");
    temp = replace(temp, "$eyes"        , cacamoo_use_eyes);
    temp = replace(temp, "${eyes}"      , cacamoo_use_eyes);
    temp = replace(temp, "$tongue"      , cacamoo_use_tongue);
    temp = replace(temp, "${tongue}"    , cacamoo_use_tongue);
    temp = replace(temp, "$thoughts"    , cacamoo_thoughts);
    temp = replace(temp, "${thoughts}"  , cacamoo_thoughts);
    temp = replace(temp, "$the_cow"     , (const char*)string);
    temp = replace(temp, "${the_cow}"   , (const char*)string);
    *size = strlen(temp)+1;

    fclose(fp);
    return temp;
}
char *remove_slashes(char *str)
{
    int i=0, size, r=0;

    if(str == NULL) return NULL;

    size = strlen(str);

    for(i=0; i<size-r; i++)
    {
        if(str[i]== '\\')
        {
            memmove(&str[i], &str[i+1], strlen(str) - i);
            str = realloc(str, (size-r)+1);
            r++;
        }
    }
    return str;
}


char *remove_comments(char *str)
{
    int size = 0, added=0;
    int i=0, j;

    if(str == NULL) return NULL;

    size = strlen(str) + 1;

    while(i < size)
    {
        if(str[i] == '#') {
            for(j = i; j < size; j++)

                if((str[j] == '\n') || (str[j] == '\r') || (str[j] == 0))
                    goto hop; // just because I like goto's, break sucks
        hop:
            j++;
            added += (i-j);
            memmove(&str[i], &str[j], size-j);
            i = j - added;
            size -= added;
            str = realloc(str, size);
            str[size-1] = 0;
            i = 0;
        }
        else
            i++;
    }
    return str;
}

char *replace(char *s1, char *oldpiece, const char *newpiece)
{
    unsigned int oldlen = strlen(oldpiece), newlen = strlen(newpiece);
    unsigned int i1 = 0, i2 = 0;
    char *s2 = oldlen < newlen ? NULL : s1;

    for(;;)
    {
        char *found = strstr(s1 + i1, oldpiece);
        unsigned int tocopy;

        if(!found)
        {
            tocopy = strlen(s1 + i1);
            if(oldlen < newlen)
                s2 = realloc(s2, i2 + tocopy + 1);
            memcpy(s2 + i2, s1 + i1, tocopy + 1);
            if(oldlen < newlen)
                free(s1);
            return s2;
        }

        tocopy = found - (s1 + i1);
        if(oldlen < newlen)
            s2 = realloc(s2, i2 + tocopy + newlen);
        memmove(s2 + i2, s1 + i1, tocopy);
        memcpy(s2 + i2 + tocopy, newpiece, newlen);
        i1 += tocopy + oldlen;
        i2 += tocopy + newlen;
    }
}


static void version(void)
{
    printf("cacamoo Copyright 2006 Jean-Yves Lamoureux %s\n", VERSION);
    printf("Internet: <jylam@lnscene.org> Version: 0, date: 30 Sep 2006\n");
    printf("\n");
}

#if defined(HAVE_GETOPT_H)
static void usage(void)
{
    printf("Usage: cacamoo [ -vh ] [ -d cowsdirectory ]\n");
    printf("               [ -f cowfile ] [ -w outputwidth ]\n");
    printf("               [-bdgpstwy]    [ message ]\n");
#   ifdef HAVE_GETOPT_LONG
    printf("  -f, --file <cowfile>     select the cow\n");
    printf("  -d, --directory <dir>    specify cows directory\n");
    printf("  -W, --width <width>      set output width\n");
    printf("  -n  --no-wrap            do not wrap string\n");
    printf("  -O, --think              think\n");
    printf("  -h                       display this help and exit\n");
    printf("  -v, --version            output version information and exit\n");
#   else
    printf("  -f <cowfile>     select the cow\n");
    printf("  -d <dir>         specify cows directory\n");
    printf("  -W <width>       set output width\n");
    printf("  -n  --no-wrap            do not wrap string\n");
    printf("  -O, --think      think\n");
    printf("  -h               display this help and exit\n");
    printf("  -v               output version information and exit\n");
#   endif
}
#endif


/* AHAHAHAHA please make no comment about this, I was in hurry \o/ */

char *wrap_string(char *buffer, unsigned int width, unsigned int *max_width, int no_wrap)
{
    unsigned int i = 0, j =0, o = 0, last_space = 0, line = 0, offset  = 0;
    unsigned int size = strlen(buffer) + 1, t = 0, rew = 0;
    char *ret = NULL;
    ret = malloc(2);
    *max_width = 0;

    /* Wrap string itself */
    if(size > width && !no_wrap)
    {
        while(i<size-1)
        {
            if(buffer[i] == ' ')
            {
                last_space = i;
                rew = o;
            }
            if(offset == width)
            {
                ret = realloc(ret, o+2);
                if(rew)
                    o = rew;
                ret[o++] = '\n';

                if(last_space) {

                    if(width - (i - last_space) >= *max_width)
                        *max_width = width - (i - last_space);

                    i = last_space + 1;
                    last_space = 0;
                    rew = 0;

                } else {
                    if(width>= *max_width)
                        *max_width = width;
                }


                offset = 0;
                line ++;
            }
            ret = realloc(ret, o+2);
            ret[o++] = buffer[i];

            i++;
            offset++;
        }
        if(offset>= *max_width)
            *max_width = offset;
        if(!(*max_width))
            *max_width = size-1;

        ret[o] = 0;
        line++;
    }
    else
    {
        *max_width = strlen(buffer);
        if(ret)
            free(ret);
        ret = buffer;
        line = 1;
    }

    /* String is wrapped, put spaces after each line */
    if(line != 1)
    {
        char *scaled = malloc(((*max_width+1) * line) + 1);
        int curx = 0;

        memset(scaled, ' ', (*max_width * line));
        o = 0;
        for(i = 0; i < strlen(ret); i ++)
        {
            if(ret[i] != '\n')
            {
                scaled[o] = ret[i];
                curx++;
            }
            else
            {
                for(j=o;j<o+(*max_width - curx);j++)
                {
                    scaled[j] = ' ';

                }
                o += ((*max_width) - curx) -1;
                curx = 0;
            }
            o++;
        }
        for(i = o; i <o+(*max_width - curx); i ++)
        {
            scaled[i] = ' ';
        }

        scaled[o+i] = 0;
        if(ret)
            free(ret);
        ret = scaled;


        /* Put balloon */
        o = 0;
        scaled = malloc((*max_width+5) * (line+2));

        scaled[t++] = ' ';
        for(i = 0; i < *max_width+2; i++)
            scaled[t++] = '_';
        scaled[t++] = ' ';
        scaled[t++] = '\n';


        for(j = 0; j < line ; j++)
        {
            if(think)
            {
                scaled[t++] = '(';
            }
            else
            {
                if(j == 0)
                    scaled[t++] = '/';
                else if (j == line -1)
                    scaled[t++] = '\\';
                else
                    scaled[t++] = '|';
            }
            scaled[t++] = ' ';

            for(i = 0; i < *max_width; i++)
            {
                scaled[t++] = ret[o++];
            }
            scaled[t++] = ' ';
            if(think)
            {
                scaled[t++] = ')';
            }
            else
            {
                if(j == 0)
                    scaled[t++] = '\\';
                else if (j == line -1)
                    scaled[t++] = '/';
                else
                    scaled[t++] = '|';
            }
            scaled[t++] = '\n';
        }

        scaled[t++] = ' ';
        for(i = 0; i < *max_width+2; i++)
            scaled[t++] = '-';
        scaled[t++] = ' ';
        scaled[t] = 0;

        free(ret);
        ret = NULL;
        ret = scaled;
    }
    else
    {
        /* Put ballon */
        char *scaled = malloc((size+4) * 3);
        t = 0;
        *max_width = size -1 ;
        o = 0;
        scaled[t++] = ' ';
        for(i = 0; i < *max_width+2; i++)
            scaled[t++] = '_';
        scaled[t++] = ' ';
        scaled[t++] = '\n';
        if(think)
            scaled[t++] = '(';
        else
            scaled[t++] = '<';
        scaled[t++] = ' ';
        for(i = 0; i < *max_width; i++)
        {
            scaled[t++] = ret[o++];
        }
        scaled[t++] = ' ';
        if(think)
            scaled[t++] = ')';
        else
            scaled[t++] = '>';

        scaled[t++] = '\n';
        scaled[t++] = ' ';
        for(i = 0; i < *max_width+2; i++)
            scaled[t++] = '-';
        scaled[t] = '\0';

        free(ret);
        ret = NULL;
        ret = scaled;
    }


    return ret;
}