/*
 *  neercs        console-based window manager
 *  Copyright (c) 2006 Sam Hocevar <sam@zoy.org>
 *                2008 Jean-Yves Lamoureux <jylam@lnxscene.org>
 *                All Rights Reserved
 *
 *  $Id$
 *
 *  This program is free software. It comes without any warranty, to
 *  the extent permitted by applicable law. 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"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <time.h>

#if defined HAVE_PTY_H
#   include <pty.h>  /* for openpty and forkpty */
#else
#   include <util.h> /* for OS X */
#endif
#if !defined HAVE_GETOPT_LONG
#   include "mygetopt.h"
#elif defined HAVE_GETOPT_H
#   include <getopt.h>
#endif
#if defined HAVE_GETOPT_LONG
#   define mygetopt getopt_long
#   define myoptind optind
#   define myoptarg optarg
#   define myoption option
#endif
#include <errno.h>
#include <cucul.h>
#include <caca.h>

#include "neercs.h"


void version(void)
{
    printf("%s\n", PACKAGE_STRING);
    printf("Copyright (C) 2006, 2008 Sam Hocevar <sam@zoy.org>\n");
    printf("                         Jean-Yves Lamoureux <jylam@lnxscene.org>\n\n");
    printf("This is free software.  You may redistribute copies of it under the\n");
    printf("terms of the Do What The Fuck You Want To Public License, Version 2\n");
    printf("<http://sam.zoy.org/wtfpl/>.\n");
    printf("There is NO WARRANTY, to the extent permitted by law.\n");
    printf("\n");
    printf("For more informations, visit http://libcaca.zoy.org/wiki/neercs\n");
}


int main(int argc, char **argv)
{
    static cucul_canvas_t *cv;
    static caca_display_t *dp;
    struct screen_list *screen_list = NULL;
    struct recurrent_list *recurrent_list = NULL;
    char *default_shell = NULL;
    int i, w, h, args, s=0;
    int eof = 0, refresh = 1, command = 0;
    long long unsigned int last_key_time = 0;

    default_shell = getenv("SHELL");

    args = argc -1;
    if(default_shell == NULL  && args <= 0)
    {
        fprintf(stderr, "Environment variable SHELL not set and no arguments given. kthxbye.\n");
        return -1;
    }

    if(args==0)
        args = 1;

    /* Create main canvas and associated caca window */
    cv = cucul_create_canvas(0, 0);
    dp = caca_create_display(cv);
    if(!dp)
        return 1;
    caca_set_cursor(dp, 1);

    w = cucul_get_canvas_width(cv);
    h = cucul_get_canvas_height(cv);


    /* Create screen list */
    screen_list = (struct screen_list*)     malloc(sizeof(struct screen_list));
    if(!screen_list)
    {
        fprintf(stderr, "Can't allocate memory at %s:%d\n", __FUNCTION__, __LINE__);
        return -1;
    }
    screen_list->screen = (struct screen**) malloc(sizeof(sizeof(struct screen*)));
    if(!screen_list->screen)
    {
        fprintf(stderr, "Can't allocate memory at %s:%d\n", __FUNCTION__, __LINE__);
        return -1;
    }
    screen_list->count = 0;
    screen_list->width  = cucul_get_canvas_width(cv);
    screen_list->mini = 1;
    screen_list->help = 0;
    screen_list->status = 1;
    screen_list->height = cucul_get_canvas_height(cv) - ((screen_list->mini*6) + (screen_list->status));
    screen_list->wm_type = WM_VSPLIT;
    screen_list->in_bell = 0;
    screen_list->pty = screen_list->prevpty = 0;
    screen_list->dont_update_coords = 0;
    screen_list->screensaver_timeout = (60) * 1000000;
    screen_list->screensaver_data = NULL;
    screen_list->in_screensaver = 0;

    recurrent_list = (struct recurrent_list*) malloc(sizeof(struct recurrent_list));
    recurrent_list->recurrent = (struct recurrent**) malloc(sizeof(struct recurrent*));
    if(!recurrent_list->recurrent)
    {
        fprintf(stderr, "Can't allocate memory at %s:%d\n", __FUNCTION__, __LINE__);
        return -1;
    }
    recurrent_list->count = 0;

    for(;;)
    {
        int option_index = 0;
        static struct myoption long_options[] =
            {
#ifdef USE_GRAB
                { "pid",         1, NULL, 'P' },
#endif
                { "help",        0, NULL, 'h' },
                { "version",     0, NULL, 'v' },
            };
        int c = mygetopt(argc, argv, "P:hv", long_options, &option_index);
        if(c == -1)
            break;

        switch(c)
        {
#ifdef USE_GRAB
        case 'P': /* --pid */
            add_screen(screen_list,create_screen_grab(w, h, atoi(myoptarg)));
            s+=2;
            break;
#endif
        case 'h': /* --help */
            //   usage(argc, argv);
            return 0;
            break;
        case 'v': /* --version */
            version();
            goto end;
            break;
        default:
            fprintf(stderr, "Unknow argument #%d\n", myoptind);
            return -1;
            break;
        }
    }


    if(s == 0 && argc<2)
    {
        add_screen(screen_list, create_screen(w, h, default_shell));
    }

    /* Launch command line processes */
    for(i=0; i<(argc-1) - s; i++)
    {
        add_screen(screen_list, create_screen(w, h, argv[i+s+1]));
    }


    /* Windows are in a temporary state, resize them to the right dimensions */
    update_windows_props(cv, screen_list);

    last_key_time = get_ms();

    /* Refresh */
    caca_refresh_display(dp);

    for(;;)
    {
        caca_event_t ev;
        int ret;

        refresh |= update_screens_contents(screen_list);

        /* No more screens, exit */
        if(!screen_list->count) break;

        /* Update each screen canvas  */
        refresh |= update_terms(screen_list);

        /* Get events, if any */
        ret = caca_get_event(dp, CACA_EVENT_ANY, &ev, 0);
        if(ret && (caca_get_event_type(&ev) & CACA_EVENT_KEY_PRESS))
        {
            unsigned int c = caca_get_event_key_ch(&ev);

            if(command)
            {
                command = 0;

                switch(c)
                {
                case 0x01: //CACA_KEY_CTRL_A:
                    screen_list->pty ^= screen_list->prevpty;
                    screen_list->prevpty ^= screen_list->pty;
                    screen_list->pty ^= screen_list->prevpty;
                    refresh = 1;
                    break;
                case 'm':
                case 0x0d: //CACA_KEY_CTRL_M:
                    screen_list->mini = !screen_list->mini;
                    refresh = 1;
                    break;
                case 'n':
                case ' ':
                case '\0':
                case 0x0e: //CACA_KEY_CTRL_N:
                    screen_list->prevpty = screen_list->pty;
                    screen_list->pty = (screen_list->pty + 1) % screen_list->count;
                    refresh = 1;
                    break;
                case 'p':
                case 0x10: //CACA_KEY_CTRL_P:
                    screen_list->prevpty = screen_list->pty;
                    screen_list->pty = (screen_list->pty + screen_list->count - 1) % screen_list->count;
                    refresh = 1;
                    break;
                case 'c':
                case 0x03: //CACA_KEY_CTRL_C:
                    screen_list->prevpty = screen_list->pty;
                    screen_list->pty =
                        add_screen(screen_list, create_screen(w, h, default_shell));
                    refresh = 1;
                    break;
                case 'w':
                case 0x17: //CACA_KEY_CTRL_W:
                    screen_list->wm_type = (screen_list->wm_type==(WM_MAX-1)?
                                            screen_list->wm_type=0:
                                            screen_list->wm_type+1);
                    refresh = 1;
                    break;
                case 0x0b: //CACA_KEY_CTRL_K:
                    add_recurrent(recurrent_list, close_screen_recurrent, cv);
                    refresh = 1;
                    break;
                case 'h':
                case 0x08: //CACA_KEY_CTRL_H:
                    screen_list->help = !screen_list->help;
                    refresh = 1;
                    break;
                }
            }
            else
            {

                last_key_time = get_ms();
                caca_set_cursor(dp, 1);

                if(screen_list->in_screensaver)
                {
                    screensaver_kill(cv, dp, screen_list);
                    screen_list->in_screensaver = 0;
                    refresh = 1;
                    continue;
                }


                switch(c)
                {
                case 0x01: //CACA_KEY_CTRL_A:
                    command = 1; break;
                case CACA_KEY_UP:
                    write(screen_list->screen[screen_list->pty]->fd, "\x1b[A", 3); break;
                case CACA_KEY_DOWN:
                    write(screen_list->screen[screen_list->pty]->fd, "\x1b[B", 3); break;
                case CACA_KEY_RIGHT:
                    write(screen_list->screen[screen_list->pty]->fd, "\x1b[C", 3); break;
                case CACA_KEY_LEFT:
                    write(screen_list->screen[screen_list->pty]->fd, "\x1b[D", 3); break;
                case CACA_KEY_ESCAPE:
                    if(screen_list->help) screen_list->help = 0;
                default:
                    write(screen_list->screen[screen_list->pty]->fd, &c, 1); break;
                }
            }
        }
        else if(ret && (caca_get_event_type(&ev) & CACA_EVENT_RESIZE))
        {
            update_windows_props(cv, screen_list);
            cucul_clear_canvas(cv);
            refresh = 1;
        }
        else if(ret && (caca_get_event_type(&ev) & CACA_EVENT_QUIT))
        {
            break;
        }

        /* Recurrent functions */
        for(i=0; i<recurrent_list->count; i++)
        {
            if(recurrent_list->recurrent[i]->function)
            {
                refresh |= recurrent_list->recurrent[i]->function(screen_list,
                                                                  recurrent_list->recurrent[i],
                                                                  recurrent_list->recurrent[i]->user,
                                                                  get_ms());
            }
        }
       /* Delete recurrent functions */
        for(i=0; i<recurrent_list->count; i++)
        {
            if(recurrent_list->recurrent[i]->kill_me)
            {
                remove_recurrent(recurrent_list, i);
                i = 0;
            }
        }

        /* Resfresh screen */
        if((refresh || screen_list->in_bell) &&
           (get_ms() - last_key_time < screen_list->screensaver_timeout))
        {
            refresh = 0;
            refresh_screens(cv, dp, screen_list);
        }
        if((get_ms() - last_key_time > screen_list->screensaver_timeout))
        {
            if(!screen_list->in_screensaver)
                screensaver_init(cv, dp, screen_list);
            screen_list->in_screensaver = 1;

            caca_set_cursor(dp, 0);
            draw_screensaver(cv, dp, screen_list);
            caca_refresh_display(dp);
        }

        eof = 1;
        for(i = 0; i < screen_list->count; i++)
            if(screen_list->screen[i]->fd >= 0)
                eof = 0;
        if(eof)
            break;
    }

end:
    /* Clean up */
    caca_free_display(dp);
    cucul_free_canvas(cv);
    for(i = 0; i < screen_list->count; i++)
    {
        destroy_screen(screen_list->screen[i]);
    }

    free(screen_list->screen);
    free(screen_list);


    for(i=0; i<recurrent_list->count; i++)
    {
        remove_recurrent(recurrent_list, i);
        i = 0;
    }

    free(recurrent_list->recurrent);
    free(recurrent_list);


    return 0;
}



long long get_ms(void)
{
    struct timeval tv;
    gettimeofday(&tv, NULL);


    return (tv.tv_sec*(1000000) + tv.tv_usec);
}
