/* 
 *  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/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <time.h>
#include <pwd.h>

#include <errno.h>
#include <caca.h>

#include "neercs.h"

static void server_init(struct screen_list *screen_list);
static int handle_key(struct screen_list *screen_list, unsigned int c);
static int handle_attach(struct screen_list *screen_list, char *buf);

static int send_to_client(const char *msg, struct screen_list *screen_list)
{
    int ret;
    if (!screen_list->socket[SOCK_CLIENT])
        connect_socket(screen_list, SOCK_CLIENT);
    debug("Sending message (%.8s,%d) to client on socket %d", msg, strlen(msg),
          screen_list->socket[SOCK_CLIENT]);
    if (!screen_list->socket[SOCK_CLIENT])
        ret = -1;
    else
        ret = write(screen_list->socket[SOCK_CLIENT], msg, strlen(msg));
    if (ret < 0 && errno != EAGAIN)
    {
        fprintf(stderr, "Failed to send message to client: %s\n",
                strerror(errno));
        if (screen_list->attached)
            detach(screen_list);
    }
    return ret;
}

static int set_title(struct screen_list *screen_list)
{
    char buf[1024];
    int bytes;
    char *title;

    if (screen_list->attached)
    {
        if (screen_list->pty < screen_list->count &&
            screen_list->screen[screen_list->pty]->title)
            title = screen_list->screen[screen_list->pty]->title;
        else
            title = PACKAGE_STRING;
    }

    if (screen_list->title)
    {
        if (!strcmp(screen_list->title, title))
            return 0;
        free(screen_list->title);
    }

    screen_list->title = strdup(title);

    bytes = snprintf(buf, sizeof(buf) - 1, "TITLE %s", title);
    buf[bytes] = '\0';
    return send_to_client(buf, screen_list);
}

static int set_cursor(int state, struct screen_list *screen_list)
{
    char buf[16];
    int bytes;

    bytes = snprintf(buf, sizeof(buf) - 1, "CURSOR %d", state);
    buf[bytes] = '\0';

    return send_to_client(buf, screen_list);
}

static int request_refresh(struct screen_list *screen_list)
{
#if defined HAVE_CACA_DIRTY_RECTANGLES
    int ndirty = caca_get_dirty_rect_count(screen_list->cv);
    if (!ndirty)
        return 0;
#endif
    if (!screen_list->socket[SOCK_CLIENT])
        connect_socket(screen_list, SOCK_CLIENT);
    if (screen_list->socket[SOCK_CLIENT])
    {
        size_t bufsize, towrite;
        ssize_t written, ret;
        socklen_t optlen = sizeof(bufsize);
        size_t bytes;
        void *buf;
        char buf2[32];
        int x, y, i;

        getsockopt(screen_list->socket[SOCK_CLIENT], SOL_SOCKET, SO_SNDBUF,
                   &bufsize, &optlen);
        bufsize /= 2;
        debug("bufsize=%d", bufsize);

#if defined HAVE_CACA_DIRTY_RECTANGLES
        for (i = 0; i < ndirty; i++)
        {
            int w, h;
            caca_get_dirty_rect(screen_list->cv, i, &x, &y, &w, &h);
            debug("dirty @%d,%d %dx%d [%dx%d]", x, y, w, h,
                  caca_get_canvas_width(screen_list->cv),
                  caca_get_canvas_height(screen_list->cv));
            buf =
                caca_export_area_to_memory(screen_list->cv, x, y, w, h, "caca",
                                           &bytes);
#else
        {
            i = 0;
            x = 0;
            y = 0;
            buf = caca_export_memory(screen_list->cv, "caca", &bytes);
#endif
            debug("Requesting refresh for %d", bytes);
            towrite = bytes;
            written = 0;
            sprintf(buf2, "UPDATE %10d %10d", x, y);
            ret =
                write(screen_list->socket[SOCK_CLIENT], buf2,
                      strlen(buf2) + 1);
            if (ret < 29 && errno != EAGAIN)
            {
                free(buf);
                return -1;
            }
            while (towrite > 0)
            {
                ssize_t n;
                debug("Wrote %d, %d remaining", written, towrite);
                /* Block to write the end of the message */
                fcntl(screen_list->socket[SOCK_CLIENT], F_SETFL, 0);
                n = write(screen_list->socket[SOCK_CLIENT],
                          (char *)buf + written,
                          towrite > bufsize ? bufsize : towrite);
                if (n < 0)
                {
                    debug("Can't refresh (%s), with %d bytes (out of %d)",
                          strerror(errno),
                          towrite > bufsize ? bufsize : towrite, towrite);
                    return -1;
                }
                written += n;
                towrite -= n;
            }
            fcntl(screen_list->socket[SOCK_CLIENT], F_SETFL, O_NONBLOCK);
            free(buf);
        }
        sprintf(buf2, "REFRESH %10d %10d", caca_get_cursor_x(screen_list->cv),
                caca_get_cursor_y(screen_list->cv));
        write(screen_list->socket[SOCK_CLIENT], buf2, strlen(buf2) + 1);
#if defined HAVE_CACA_DIRTY_RECTANGLES
        caca_clear_dirty_rect_list(screen_list->cv);
#endif
    }
    return 0;
}

int detach(struct screen_list *screen_list)
{
    screen_list->attached = 0;
    if (screen_list->lock_on_detach)
        screen_list->locked = 1;
    if (screen_list->socket[SOCK_CLIENT])
    {
        send_to_client("DETACH", screen_list);
        close(screen_list->socket[SOCK_CLIENT]);
        screen_list->socket[SOCK_CLIENT] = 0;
    }
    return 0;
}

static void server_main(struct screen_list *screen_list)
{
    int i;
    int eof = 0, refresh = 1, was_in_bell = 0;
    long long unsigned int last_refresh_time = 0;

    screen_list->last_key_time = 0;
    screen_list->attached = 0;
    screen_list->command = 0;

    server_init(screen_list);

    for (;;)
    {
        int quit = 0;
        ssize_t n;
        char buf[128];

        /* Read program output */
        refresh |= update_screens_contents(screen_list);

        /* Check if we got something from the client */
        while (screen_list->socket[SOCK_SERVER]
               && (n =
                   read(screen_list->socket[SOCK_SERVER], buf,
                        sizeof(buf) - 1)) > 0)
        {
            buf[n] = 0;
            debug("Received command %s", buf);
            if (!strncmp("ATTACH ", buf, 7))
            {
                refresh |= handle_attach(screen_list, buf);
            }
            else if (!strncmp("QUIT", buf, 4))
            {
                quit = 1;
            }
            else if (!strncmp("DELAY ", buf, 6))
            {
                /* FIXME check the length before calling atoi */
                screen_list->delay = atoi(buf + 6);
            }
            else if (!strncmp("RESIZE ", buf, 7))
            {
                caca_free_canvas(screen_list->cv);
                /* FIXME check the length before calling atoi */
                screen_list->cv =
                    caca_create_canvas(atoi(buf + 7), atoi(buf + 18));
                screen_list->changed = 1;
                refresh = 1;
            }
            else if (!strncmp("KEY ", buf, 4))
            {
                unsigned int c = atoi(buf + 4);
                refresh |= handle_key(screen_list, c);
            }
            else
            {
                fprintf(stderr, "Unknown command received: %s\n", buf);
            }
        }

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

        /* User requested to exit */
        if (quit)
            break;

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

        /* Launch recurrents if any */
        refresh |= handle_recurrents(screen_list);

        /* Refresh screen */
        if (!screen_list->attached)
        {
            /* No need to refresh Don't use the CPU too much Would be better
               to select on terms + socket */
            sleep(1);
        }
        else
        {
            long long unsigned int current_time = get_us();
            long long int tdiff = (current_time - last_refresh_time) / 1000;
            /* Draw lock window */
            if (screen_list->locked)
            {
                draw_lock(screen_list);
                refresh = 1;
            }
            else if ((current_time - screen_list->last_key_time >=
                      screen_list->autolock_timeout))
            {
                screen_list->locked = 1;
                refresh = 1;
            }
            else if ((current_time - screen_list->last_key_time >=
                      screen_list->screensaver_timeout))
            {
                if (!screen_list->in_screensaver)
                {
                    screensaver_init(screen_list);
                    screen_list->in_screensaver = 1;
                    set_cursor(0, screen_list);
                }
                draw_screensaver(screen_list);
                refresh = 1;
            }
            else if (refresh || was_in_bell)
            {
                if (tdiff >= screen_list->delay)
                {
                    was_in_bell = screen_list->in_bell;
                    refresh_screens(screen_list);
                    set_title(screen_list);
                    refresh = 1;
                }
            }
            if (refresh)
            {
                if (tdiff >= screen_list->delay)
                {
                    request_refresh(screen_list);
                    refresh = 0;
                    last_refresh_time = current_time;
                }
                else
                    debug("Skipping refresh (%lld < %d)", tdiff,
                          screen_list->delay);
            }
        }

        if (screen_list->force_refresh)
        {
            wm_refresh(screen_list);
            request_refresh(screen_list);
        }

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

    detach(screen_list);

    free_screen_list(screen_list);

    exit(0);
}


static void server_init(struct screen_list *screen_list)
{
    int i;
    /* Create socket and bind it */
    create_socket(screen_list, SOCK_SERVER);

    /* Connect to the client */
    connect_socket(screen_list, SOCK_CLIENT);

    screen_list->width = screen_list->height = 80;

    /* Create main canvas */
    screen_list->cv = caca_create_canvas(screen_list->width,
                                         screen_list->height
                                         + screen_list->mini * 6
                                         + screen_list->status);

    if (!screen_list->to_grab && !screen_list->to_start)
    {
        add_screen(screen_list,
                   create_screen(screen_list->width,
                                 screen_list->height,
                                 screen_list->default_shell));
    }

    /* Attach processes */
    if (screen_list->to_grab)
    {
        for (i = 0; screen_list->to_grab[i]; i++)
        {
            add_screen(screen_list,
                       create_screen_grab(screen_list->width,
                                          screen_list->height,
                                          screen_list->to_grab[i]));
        }
        free(screen_list->to_grab);
        screen_list->to_grab = NULL;
    }

    /* Launch command line processes */
    if (screen_list->to_start)
    {
        for (i = 0; screen_list->to_start[i]; i++)
        {
            add_screen(screen_list,
                       create_screen(screen_list->width,
                                     screen_list->height,
                                     screen_list->to_start[i]));
            free(screen_list->to_start[i]);
        }
        free(screen_list->to_start);
        screen_list->to_start = NULL;
    }

    screen_list->last_key_time = get_us();
}

static int handle_attach(struct screen_list *screen_list, char *buf)
{
    /* If we were attached to someone else, detach first */
    if (screen_list->attached)
        detach(screen_list);
    screen_list->attached = 1;
    caca_free_canvas(screen_list->cv);
    screen_list->cv = caca_create_canvas(atoi(buf + 7), atoi(buf + 18));
    screen_list->delay = atoi(buf + 29);
    screen_list->changed = 1;
    return 1;
}

static int handle_key(struct screen_list *screen_list, unsigned int c)
{
    int refresh = 0;
    char *str = NULL;
    int size = 0;
    /* CTRL-A has been pressed before, handle this as a command, except that
       CTRL-A a sends literal CTRL-A */
    if (screen_list->command && (c != 'a'))
    {
        screen_list->command = 0;
        refresh |= handle_command_input(screen_list, c);
    }
    else
    {
        /* Not in command mode */
        screen_list->last_key_time = get_us();
        set_cursor(1, screen_list);

        /* Kill screensaver */
        if (screen_list->in_screensaver)
        {
            screensaver_kill(screen_list);
            screen_list->in_screensaver = 0;
            screen_list->changed = 1;
            refresh = 1;
            return refresh;
        }
        /* Handle lock window */
        if (screen_list->locked)
        {
            refresh |= update_lock(c, screen_list);
            screen_list->changed = 1;
        }
        else if (screen_list->window_list)
        {
            refresh |= update_window_list(c, screen_list);
            screen_list->changed = 1;
        }
        else
        {
            switch (c)
            {
            case 0x01:         // CACA_KEY_CTRL_A:
                screen_list->command = 1;
                break;
            case CACA_KEY_ESCAPE:
                if (screen_list->help)
                {
                    screen_list->help = 0;
                    screen_list->changed = 1;
                    refresh = 1;
                    break;
                }
            default:
                /* CTRL-A a sends literal CTRL-A */
                if (screen_list->command && (c == 'a'))
                {
                    c = 0x01;
                }
                /* Normal key, convert it if needed */
                str = convert_input_ansi(&c, &size);
                write(screen_list->screen[screen_list->pty]->fd, str, size);
                break;
            }
        }
    }
    return refresh;
}


int start_server(struct screen_list *screen_list)
{
    pid_t pid;
    char *sess;

    pid = fork();
    if (pid < 0)
    {
        perror("Failed to create child process");
        return -1;
    }
    if (pid == 0)
    {
        int fd;
        close(0);
        close(1);
        close(2);
        fd = open("/dev/null", O_RDWR, 0);
        if (fd < 0)
        {
            perror("Failed to open /dev/null");
            return -2;
        }
        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/neercs-debug.txt", O_TRUNC | O_RDWR | O_CREAT,
                  S_IRUSR | S_IWUSR);
        dup2(fd, 1);
        dup2(fd, 2);
        if (fd > 2)
            close(fd);
#endif
        setsid();
        server_main(screen_list);
        /* Never returns */
    }
    create_socket(screen_list, SOCK_CLIENT);
    while ((sess = connect_socket(screen_list, SOCK_SERVER)) == NULL)
        usleep(100);
    free(sess);

    /* Create main canvas and associated caca window */
    screen_list->cv = caca_create_canvas(0, 0);
    screen_list->dp = caca_create_display(screen_list->cv);
    
    if (!screen_list->dp)
        return -3;
    
    caca_set_display_time(screen_list->dp, screen_list->delay * 1000);
    caca_set_cursor(screen_list->dp, 1);

    request_attach(screen_list);

    return 0;
}

long long get_us(void)
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (tv.tv_sec * (1000000) + tv.tv_usec);
}
