/*
 *  neercs        console-based window manager
 *  Copyright (c) 2006 Sam Hocevar <sam@zoy.org>
 *                2008 Jean-Yves Lamoureux <jylam@lnxscene.org>
 *                2008 Pascal Terjan <pterjan@linuxfr.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>
#include <pwd.h>


#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");
}

void usage(int argc, char **argv)
{
    printf("%s\n", PACKAGE_STRING);
    printf("Usage : %s [command1] [command2] ... [commandN]\n", argv[0]);
    printf("Example : %s zsh top \n\n", argv[0]);
    printf("Options :\n");
    printf("\t--config\t-c <file>\t\tuse given config file\n");
    printf("\t--pid\t\t-P <pid>\t\tgrab <pid>\n");
    printf("\t\t\t-r [session]\t\treattach to a detached neercs\n");
    printf("\t\t\t-R [session]\t\treattach if possible, otherwise start a new session\n");
    printf("\t\t\t-S <name>\t\tname this session <name> instead of <pid>\n");
    printf("\t--version\t-v \t\t\tdisplay version and exit\n");
    printf("\t--help\t\t-h \t\t\tthis help\n");
}

int main(int argc, char **argv)
{
    struct screen_list *screen_list = NULL;
    struct passwd *user_info;
    char *user_path = NULL, *user_dir = NULL;
    int i, args, s=0;
    int refresh = 1;
    long long unsigned int last_key_time = 0;
    int mainret = 0;
    int attach = 0, forceattach = 0;
    int *to_grab = NULL;
    char **to_start = NULL;
    int nb_to_grab = 0;

    screen_list = create_screen_list();
    screen_list->default_shell = getenv("SHELL");

    args = argc -1;
    if(screen_list->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;

    /* Build local config file path */
    user_dir = getenv("HOME");
    if(!user_dir)
    {
        user_info = getpwuid(getuid());
        if(user_info)
        {
            user_dir = user_info->pw_dir;
        }
    }
    if(user_dir)
    {
        user_path = malloc(strlen(user_dir) + strlen("/.neercsrc") + 1);
        sprintf(user_path, "%s/%s", user_dir, ".neercsrc");
    }


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

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

        switch(c)
        {
        case 'c': /* --config */
            if(user_path)
                free(user_path);
            user_path = strdup(myoptarg);
            s+=2;
            break;
        case 'S':
            if(!screen_list->session_name)
                screen_list->session_name = strdup(myoptarg);
            s+=2;
            break;
        case 'P': /* --pid */
            pidopt = atoi(myoptarg);
            if(pidopt <= 0)
            {
                fprintf(stderr, "Invalid pid %d\n", pidopt);
                if(to_grab)
                    free(to_grab);
                return -1;
            }
            if(!to_grab)
            {
                /* At most argc-1-s times -P <pid> + final 0 */
                to_grab = (int *)malloc(((argc-1-s)/2+1)*sizeof(int));
                if(!to_grab)
                {
                    fprintf(stderr, "Can't allocate memory at %s:%d\n", __FUNCTION__, __LINE__);
                    return -1;
                }
            }
            to_grab[nb_to_grab++] = pidopt;
            to_grab[nb_to_grab] = 0;
            s+=2;
            break;
        case 'r':
            forceattach = 1;
        case 'R':
            if(attach)
            {
                fprintf(stderr, "Attaching can only be requested once\n");
                return -1;
            }
            if(myoptarg)
            {
                if(screen_list->session_name)
                    free(screen_list->session_name);
                screen_list->session_name = strdup(myoptarg);
                s+=1;
            }
            attach = 1;
            s+=1;
            break;
        case 'h': /* --help */
            usage(argc, argv);
            return 0;
            break;
        case 'v': /* --version */
            version();
            return 0;
            break;
        default:
            fprintf(stderr, "Unknown argument #%d\n", myoptind);
            return -1;
            break;
        }
    }

    if(s < argc - 1)
    {
        to_start = (char**)malloc((argc-s)*sizeof(char*));
        if(!to_start)
        {
            fprintf(stderr, "Can't allocate memory at %s:%d\n", __FUNCTION__, __LINE__);
            return -1;
        }
        for(i=0; i<(argc-1) - s; i++)
        {
            to_start[i] = strdup(argv[i+s+1]);
        }
        to_start[argc-1-s] = NULL;
    }

    /* Read global configuration first */
    read_configuration_file("/etc/neercsrc", screen_list);

    /* Then local one  */
    if(user_path)
    {
        read_configuration_file(user_path, screen_list);
        free(user_path);
    }

    if(attach)
    {
        char **sockets;
        if(nb_to_grab || (argc-1 > s))
        {
            fprintf(stderr, "-R can not be associated with commands or pids!\n");
            return -1;
        }
        sockets = list_sockets(screen_list->socket_dir, screen_list->session_name);
        if(sockets && sockets[0])
        {
            char *session;
            for(i=0; sockets[i]; i++);
            i--;
            session = connect_server(sockets[i], screen_list);
            if(session)
            {
                request_attach(screen_list);
                if(screen_list->session_name)
                    free(screen_list->session_name);
                screen_list->session_name = session;
            }
            else
            {
                fprintf(stderr, "Failed to attach!\n");
                attach = 0;
            }
            for(i=0; sockets[i]; i++)
                free(sockets[i]);
            free(sockets);
        }
        else
        {
            fprintf(stderr, "No socket found!\n");
            attach = 0;
        }
        if(forceattach && !attach)
            return -1;
    }

    /* Build default session name */
    if(!screen_list->session_name)
    {
        char mypid[32]; /* FIXME Compute the length of PID_MAX ? */
        snprintf(mypid, 31, "%d", getpid());
        mypid[31]= '\0';
        screen_list->session_name = strdup(mypid);
        if(!screen_list->session_name)
        {
            fprintf(stderr, "Can't allocate memory at %s:%d\n", __FUNCTION__, __LINE__);
            return -1;
        }
    }

    /* Fork the server if needed */
    if(!attach)
    {
        pid_t pid;

        screen_list->socket_path = build_socket_path(screen_list->socket_path, screen_list->session_name);

        pid = fork();
        if(pid < 0)
        {
            fprintf(stderr, "Failed to create child process\n");
            return -1;
        }
        if(pid == 0)
        {
            int fd;
            close(0);
            close(1);
            close(2);
            fd = open("/dev/null", O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
            dup2(fd, 0);
#ifndef DEBUG
            dup2(fd, 1);
            dup2(fd, 2);
            if (fd > 2)
                close(fd);
#else
            if(fd != 0)
                close(fd);
            fd = open("/tmp/log.txt", O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
            dup2(fd, 1);
            dup2(fd, 2);
            if (fd > 2)
                close(fd);
#endif
            setsid();
            return server_main(to_grab, to_start, screen_list);
        }

        free(connect_server(screen_list->socket_path, screen_list));
    }

    /* Create main canvas and associated caca window */
    screen_list->cv = cucul_create_canvas(0, 0);
    screen_list->dp = caca_create_display(screen_list->cv);
    if(!screen_list->dp)
        return 1;
    caca_set_cursor(screen_list->dp, 1);
    screen_list->width  = cucul_get_canvas_width(screen_list->cv);
    screen_list->height = cucul_get_canvas_height(screen_list->cv) - ((screen_list->mini*6) + (screen_list->status));

    last_key_time = get_us();

    for(;;)
    {
        caca_event_t ev;
        int ret = 0;
        ssize_t n;
        char buf[4097];

        while (screen_list->socket && (n = read(screen_list->socket, buf, sizeof(buf)-1)) > 0)
        {
            buf[n] = 0;
            debug("Received from server: %s", buf);
            if(!strncmp("DETACH", buf, 6))
            {
                ret = 1;
                break;
            }
            else if(!strncmp("REFRESH", buf, 7))
            {
                /* FIXME update the canvas first */
                caca_refresh_display(screen_list->dp);
            }
            else if(!strncmp("CURSOR ", buf, 7))
            {
                caca_set_cursor(screen_list->dp, atoi(buf+7));
            }
            else if(!strncmp("TITLE ", buf, 6))
            {
                caca_set_display_title(screen_list->dp, buf+6);
                caca_refresh_display(screen_list->dp);
            }
        }
        if(ret)
            break;

        ret = caca_get_event(screen_list->dp, CACA_EVENT_ANY, &ev, 100000);
        if(ret)
            ret = send_event(ev, screen_list->socket);

        if(ret)
            break;
    }

    /* Clean up */
    if(screen_list->dp)
        caca_free_display(screen_list->dp);

    cucul_free_canvas(screen_list->cv);

    for(i = 0; i < screen_list->count; i++)
    {
        destroy_screen(screen_list->screen[i]);
    }

    if(screen_list->socket_path) {
        free(screen_list->socket_path);
    }

    if(screen_list->socket)
        close(screen_list->socket);

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


    struct option *option = screen_list->config;

    while(option)
    {
        struct option *kromeugnon = option;
        option = option->next;
        if(kromeugnon->key)   free(kromeugnon->key);
        if(kromeugnon->value) free(kromeugnon->value);
        free(kromeugnon);
    }

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

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

    if(screen_list->session_name)
        free(screen_list->session_name);

    if(screen_list)
        free(screen_list);

    return mainret;
}


struct screen_list *create_screen_list(void)
{

    struct screen_list *screen_list = NULL;

    /* 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 NULL;
    }
    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__);
        free(screen_list);
        return NULL;
    }

    screen_list->count = 0;
    screen_list->mini = 1;
    screen_list->help = 0;
    screen_list->status = 1;
    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;
    screen_list->locked = 0;
    screen_list->lock_offset = 0;
    screen_list->attached = 1;
    screen_list->socket = 0;
    screen_list->socket_dir    = NULL;
    screen_list->socket_path   = NULL;
    screen_list->session_name  = NULL;
    screen_list->default_shell = NULL;

    screen_list->autolock_timeout = -1;


    screen_list->recurrent_list = NULL;
    screen_list->cv = NULL;
    screen_list->dp = NULL;

    memset(screen_list->lockmsg, 0, 1024);
    memset(screen_list->lockpass, 0, 1024);

    return screen_list;
}
