//  ----------------------------------------------------------
//
//  Copyright (C) 2002 Brad Wasson <bard@systemtoolbox.com>
//
//  This file is part of 3ddesktop.
//
//  3ddesktop is free software; you can redistribute it and/or modify it
//  under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2, or (at your option)
//  any later version.
//
//  3ddesktop is distributed in the hope that it will be useful, but
//  WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with 3ddesktop; see the file COPYING.   If not, write to
//  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
//

#ifndef _CONFIG_HPP
#define _CONFIG_HPP

#include <stdio.h>
#include <stdlib.h>

#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>  // for mkdir

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <dirent.h>  // for PATH_MAX?
#include <getopt.h>

#include "3ddesk.h"
#include "util.h"

// STL
using namespace std;
#include <list>


static void 
usage_and_bye (void)
{
    msgout(INFO, 
           "usage: 3ddeskd [ OPTIONS ]\n\n"
           "  where:\n"
           "    --acquire[=#]    Grab images for all the desktops by cycling thru\n"
           "                       (sleep for # millisecs at each screen for refresh)\n"
           "    --dir=/path      the install dir if different than /usr/share/3ddesktop\n"
           "    --texturesize=#  size of textures (larger for more detail) in powers of 2\n" 
           "    --wm=            Specify the Windowmanager type so all the virtual \n"
           "                     desktops are found correctly.  Available options are:\n"
           "                       kde2, kde3, gnome1, gnome2, ewmh, fluxbox, windowmaker,\n"
           "                       enlightenment, sawfishonly, workspaces\n"
           "    --fastest        hog the CPU for smoothest operation\n"
           "    -v               verbose/debug information\n\n");
    exit (1);
} // END usage_and_bye



// options by command line or config file 
class Options {
public:
    char name[50];

    // bits/bools on/off
    int show_fps : 1;
    int show_digits : 1;
    int do_fullscreen : 1;
    int entry_exit_movement : 1;
    int use_context_switch : 1;
    int use_breathing : 1;
    int daemonized : 1;
    int use_wireframe : 1;
    int glcompression : 1;

    int __early_desktop_switch : 1;
    int __disable_keys_in_goto : 1;

    int __verbose : 1;
    int __texture_size;

    int __use_kde : 1;
    int __use_ewmh : 1;
    int __sawfish_only : 1;
    int __use_workspaces : 1;
    int __use_viewareas : 1;

    int __autoacquire;

    int __priority;

    int acquire;

#if defined(__FreeBSD__)
    char base_dir[MAXNAMLEN + 1];
#else
    char base_dir[PATH_MAX];
#endif

    int random_fun_delay;    // 15

    float animation_speed;  // ms between animations

    // steps between face changes (40 * 10ms animation step = 400ms to
    // change faces)
    float face_change_steps;
    float zoom_steps;

    FaceType_t face_type;

    float default_z_offset_distance;

    int goto_column;
    int goto_row;

    float digit_size;
    int digit_color;

    int frame_color;

    float linear_spacing;

    int recalc;  // set if arrangement needs to be recalculated

    int disable_exit_after_goto;

    int reverse_mousewheel;
    int swap_mousebuttons;
    int alt_mousebuttons;

    // ---------
    //Arrangement *arrangement;

    Options(char *n = NULL) {

	if (n)
	    strncpy(name, n, sizeof(name));


        // defaults
        __verbose = 0;
        __texture_size = 1024;

	__use_kde = 0;
	__use_ewmh = 0;
        __sawfish_only = 0;
        __use_workspaces = 0;
        __use_viewareas = 0;
        __early_desktop_switch = 1;
        __disable_keys_in_goto = 1;
        __autoacquire = 2;
        __priority = 12;

        acquire = 0;
        glcompression = 0;
        show_fps = 0;
        show_digits = 1;
        do_fullscreen = 1;
        entry_exit_movement = 1;
        use_context_switch = 1;
        use_breathing = 1;
        random_fun_delay = 15;
        face_type = FACE_TYPE_CAROUSEL;
        default_z_offset_distance = 0;
        daemonized = 0;
        goto_column = NO_GOTO;
        goto_row = NO_GOTO;

        strcpy(base_dir, SHAREDIR);

        digit_size = 50;
        digit_color = COLOR_RED;

        frame_color = COLOR_GRAY;
        use_wireframe = false;

        linear_spacing = 2.0;

        recalc = 1;

        animation_speed = 10;

        face_change_steps = 40;
        zoom_steps = 45;

        disable_exit_after_goto = 0;

        reverse_mousewheel = 0;
        swap_mousebuttons = 0;
        alt_mousebuttons = 0;

	// ----
        //arrangement = NULL;

    };

    void set_color (int *v, char *color) {
        if (strcmp(color, "red") == 0) {
            *v = COLOR_RED;
        } else if (strcmp(color, "green") == 0) {
            *v = COLOR_GREEN;
        } else if (strcmp(color, "blue") == 0) {
            *v = COLOR_BLUE;
        } else if (strcmp(color, "lightblue") == 0) {
            *v = COLOR_LIGHTBLUE;
        } else if (strcmp(color, "white") == 0) {
            *v = COLOR_WHITE;
        } else if (strcmp(color, "gray") == 0) {
            *v = COLOR_GRAY;
        } else if (strcmp(color, "purple") == 0) {
            *v = COLOR_PURPLE;
        } else if (strcmp(color, "yellow") == 0) {
            *v = COLOR_YELLOW;
        } else {
            msgout (DEBUG, "conf file: invalid color '%s' (red,green,blue,yellow,white,purple,orange)\n", color);
            //*v = COLOR_WHITE;
        }

    }
    
    
    int get_boolean (char *value) {
        if (!value) return 0;

        if (!strcmp(value, "0") ||
            !strcmp(value, "false") ||
            !strcmp(value, "no") ||
            !strcmp(value, "off")) 
            return 0;
        
        if (!strcmp(value, "1") ||
            !strcmp(value, "true") ||
            !strcmp(value, "yes") ||
            !strcmp(value, "on")) 
            return 1;

        return 0;  // default false (?)
    }

    int set_option (char *option, char *value, bool from_cmdline) {
        int tmpint;
        float tmpfloat;

	if (!option)
	    return 1;

	if (strcmp(option, "mode") == 0) {

	    if (strcmp(value, "cylinder") == 0) {
		face_type = FACE_TYPE_CYLINDER;
	    } else if (strcmp(value, "linear") == 0) {
		face_type = FACE_TYPE_LINEAR;
	    } else if (strcmp(value, "viewmaster") == 0) {
		face_type = FACE_TYPE_VIEWMASTER;
	    } else if (strcmp(value, "carousel") == 0) {
		face_type = FACE_TYPE_CAROUSEL;
	    } else if (strcmp(value, "priceisright") == 0) {
		face_type = FACE_TYPE_PRICEISRIGHT;
	    } else if (strcmp(value, "flip") == 0) {
		face_type = FACE_TYPE_FLIP;
	    } else if (strcmp(value, "random") == 0) {
		face_type = FACE_TYPE_RANDOM;
	    } else {
		return 1;
	    }

	} else if (strcmp(option, "zoomspeed") == 0) {
            float tmp = atof(value);
            if (tmp >= 1.0 && tmp <= 300.0) {
                zoom_steps = tmp;
            }

	} else if (strcmp(option, "changespeed") == 0) {
            float tmp = atof(value);
            if (tmp >= 1.0 && tmp <= 300.0) {
                face_change_steps = tmp;
            }

	} else if (strcmp(option, "animation_speed") == 0) {
            float tmp = atof(value);
            if (tmp >= 1.0 && tmp <= 100.0) {
                animation_speed = tmp;
            }

	} else if (strcmp(option, "linear_spacing") == 0) {
            float tmp = atof(value);
            if (tmp >= 0.0 && tmp <= 20) {
                linear_spacing = tmp;
                recalc = 1;
            }
	} else if (strcmp(option, "use_breathing") == 0) {
            use_breathing = get_boolean (value);

	} else if (strcmp(option, "digit_size") == 0) {
	    digit_size = atoi(value);

	} else if (strcmp(option, "digit_color") == 0) {
            set_color (&digit_color, value);

	} else if (strcmp(option, "frame_color") == 0) {
            set_color (&frame_color, value);

	} else if (strcmp(option, "use_wireframe") == 0) {
            use_wireframe = get_boolean (value);

	} else if (strcmp(option, "fps") == 0) {
	    show_fps = get_boolean (value);

	} else if (strcmp(option, "show_digit") == 0) {
	    show_digits = get_boolean (value);;

	} else if (strcmp(option, "dir") == 0) {
	    strncpy(base_dir, value, sizeof(base_dir));

	} else if (strcmp(option, "randdelay") == 0) {
	    tmpint = atoi (value);
	    if (0 <= tmpint && tmpint < 60)
		random_fun_delay = tmpint;

	} else if (strcmp(option, "workspaces") == 0) {
            __use_workspaces = from_cmdline ? 1 : get_boolean (value);

	} else if (strcmp(option, "texturesize") == 0) {
	    __texture_size = atoi (value);

	    switch (__texture_size) {
	    case  16: case  32: case   64: case  128:
	    case 256: case 512: case 1024: case 2048:
		break;
	    default:
		// I now use gluBuild2DMipmaps so this isn't
		// really necessary because it converts it however
		// it is sooooo slow.  To use glTexImage2D it is
		// necessary.
		msgout (ERROR, "Texture size must be a power of 2 (such as 256, 512, 1024 ...)\n");
		end_program(-1);  // should I ignore this error?
		break;
	    }

	} else if (strcmp(option, "fastest") == 0) {
	    use_context_switch = get_boolean (value);

	} else if (strcmp(option, "depth") == 0) {
	    tmpfloat = (float)atof(value);
	    if (3.5 <= tmpfloat && tmpfloat < 40.0)
		default_z_offset_distance = tmpfloat;

	} else if (strcmp(option, "win") == 0) {
	    do_fullscreen = from_cmdline ? 0 : get_boolean (value);

	} else if (strcmp(option, "sawfish") == 0) {
	    __sawfish_only = from_cmdline ? 1 : get_boolean (value);

        } else if (strcmp(option, "kde") == 0) {
            __use_kde = from_cmdline ? 1 : get_boolean (value);

        } else if (strcmp(option, "kde2") == 0) {
            __use_kde = from_cmdline ? 1 : get_boolean (value);

        } else if (strcmp(option, "kde3") == 0) {
            __use_ewmh = from_cmdline ? 1 : get_boolean (value);

        } else if (strcmp(option, "gnome") == 0) {
            // "gnome off" will actaully enable in .conf ... but oh well
            __use_ewmh = 0;
            __use_workspaces = 1;

        } else if (strcmp(option, "gnome1") == 0) {
            __use_ewmh = 0;
            __use_workspaces = 1;

        } else if (strcmp(option, "gnome2") == 0) {
            __use_ewmh = from_cmdline ? 1 : get_boolean (value);

        } else if (strcmp(option, "ewmh") == 0) {
            __use_ewmh = from_cmdline ? 1 : get_boolean (value);

	} else if (strcmp(option, "acquire") == 0) {
	    acquire = 1;
	    if (value) {
		tmpint = atoi (value);
		// must be between 10ms and 3sec for now
		if (10 < tmpint && tmpint < 3000)
		    acquire = tmpint;
	    }

	} else if (strcmp(option, "wm") == 0) {
	    if (!value)
		return 1;

	    if (strcmp (value, "sawfishonly") == 0) {
		__sawfish_only = 1;
	    } else if (strcmp (value, "kde") == 0 ||
                       strcmp (value, "kde2") == 0) {
		__use_kde = 1;
		__use_ewmh = 0;
	    } else if (strcmp (value, "kde3") == 0) {
		__use_ewmh = 1;
	    } else if (strcmp (value, "gnome") == 0 ||
                       strcmp (value, "gnome1") == 0 ||
                       strcmp (value, "enlightenment") == 0) {
                __use_viewareas = 1;
		__use_workspaces = 1;
		__use_ewmh = 0;
	    } else if (strcmp (value, "gnome2") == 0 ||
                       strcmp (value, "ewmh") == 0) {
		__use_ewmh = 1;
	    } else if (strcmp (value, "gnomeworkspaces") == 0 ||
                       strcmp (value, "windowmaker") == 0 ||
                       strcmp (value, "workspaces") == 0) {
		__use_workspaces = 1;
		__use_ewmh = 0;
	    } else if (strcmp (value, "fluxbox") == 0) {
		__use_ewmh = 1;
                __early_desktop_switch = 0;
	    } else {
		return 1;
	    }
        } else if (strcmp(option, "nozoom") == 0) {
            entry_exit_movement = !get_boolean (value);

        } else if (strcmp(option, "zoom") == 0) {
            entry_exit_movement = get_boolean (value);

        } else if (strcmp(option, "gotocolumn") == 0) {
            if (!value)
                return 1;
            goto_column = atoi(value);
            if (goto_column < 1)
                return 1;
        } else if (strcmp(option, "gotorow") == 0) {
            if (!value)
                return 1;
            goto_row = atoi(value);
            if (goto_row < 1)
                return 1;

        } else if (strcmp(option, "gotoright") == 0) {
            goto_column = GOTO_FACE_RIGHT;
        } else if (strcmp(option, "gotoleft") == 0) {
            goto_column = GOTO_FACE_LEFT;
        } else if (strcmp(option, "gotoup") == 0) {
            goto_row = GOTO_FACE_UP;
        } else if (strcmp(option, "gotodown") == 0) {
            goto_row = GOTO_FACE_DOWN;

        } else if (strcmp(option, "verbose") == 0) {
            __verbose = get_boolean (value);

        } else if (strcmp(option, "early_desktop_switch") == 0) {
            __early_desktop_switch = get_boolean (value);

        } else if (strcmp(option, "disable_keys_in_goto") == 0) {
            __disable_keys_in_goto = get_boolean (value);

        } else if (strcmp(option, "autoacquire") == 0) {
            __autoacquire = atoi (value);
            
        } else if (strcmp(option, "dontexit") == 0) {
            disable_exit_after_goto = get_boolean (value);

        } else if (strcmp(option, "glcompression") == 0) {
            glcompression = get_boolean (value);

        } else if (strcmp(option, "priority") == 0) {
            __priority = atoi (value);

        } else if (strcmp(option, "reverse_mousewheel") == 0) {
            reverse_mousewheel = get_boolean (value);

        } else if (strcmp(option, "swap_mousebuttons") == 0) {
            swap_mousebuttons = get_boolean (value);

        } else if (strcmp(option, "alt_mousebuttons") == 0) {
            alt_mousebuttons = get_boolean (value);

	}

	return 0;  // success
    };


    void init_from_command_line (int argc, char **argv) {

        int c;
        int option_index;

        // these are cmd line options to the daemon
        static struct option long_options[] = {
            //{"fps", 0, 0, 0},
            {"dir", 1, 0, 0},
            {"workspaces", 0, 0, 0},
            {"texturesize", 1, 0, 0},
            {"fastest", 0, 0, 0},
            {"win", 0, 0, 0},
            {"sawfish", 0, 0, 0},
            {"acquire", 2, 0, 0},
            {"wm", 1, 0, 0},
            {"kde", 0, 0, 0},
            {"gnome2", 0, 0, 0},
            {"ewmh", 0, 0, 0},
            {"kde2", 0, 0, 0},
            {"kde3", 0, 0, 0},
            {"gnome", 0, 0, 0},
            {"gnome1", 0, 0, 0},
            {0, 0, 0, 0}
        };

        while (1) {

            c = getopt_long (argc, argv, "v",
                             long_options, &option_index);
            if (c == -1)
                break;

            switch (c) {
            case 0: // long option
	      
                //msgout (DEBUG, "option %s", long_options[option_index].name);
                if (set_option ((char *)long_options[option_index].name, 
                                optarg, 1))
                    usage_and_bye();
                break;
            case 'v':
                __verbose = 1;
                msgout (INFO, "Verbose is ON\n");
                break;
            default:
                usage_and_bye();
                break;
            }
        }  // end while args
    };

};



class Config {
private:

    list <Options *> config_options;

    Options *cmd_line_options;


#if defined(__FreeBSD__)
    char config_file[MAXNAMLEN + 1];
#else
    char config_file[PATH_MAX];
#endif

    static const int use_tmp = 0;

public:

#if defined(__FreeBSD__)
    char working_dir[MAXNAMLEN + 1];
    char pidfile[MAXNAMLEN + 1];
#else
    char working_dir[PATH_MAX];
    char pidfile[PATH_MAX];
#endif

    int use_kde : 1;
    int use_ewmh : 1;
    int sawfish_only : 1;
    int use_workspaces : 1;
    int use_viewareas : 1;
    int early_desktop_switch : 1;
    int disable_keys_in_goto : 1;
    int verbose : 1;

    int texture_size;

    int autoacquire;

    int priority;

    // ptr to the current option set (either cmd line or a conf "view")
    Options *options;

    Config () {
        strcpy(working_dir, "/tmp/3ddesktop");
        pidfile[0] = 0;
        texture_size = 1024;

        cmd_line_options = new Options("cmdline");
        if (!cmd_line_options) {
            msgout(ERROR, "out of memory: Config\n");
            end_program(-1);
        }
        options = cmd_line_options;
    };

    ~Config () {
        if (pidfile[0])
            unlink(pidfile);
        delete cmd_line_options;
        list<Options *>::iterator k;
        for (k = config_options.begin(); k != config_options.end(); k++)
            delete *k;
        config_options.clear();
    }

    void sync_global_options (void) {
        
        verbose = cmd_line_options->__verbose;
        texture_size = cmd_line_options->__texture_size;
        use_kde = cmd_line_options->__use_kde;
        use_ewmh = cmd_line_options->__use_ewmh;
        use_workspaces = cmd_line_options->__use_workspaces;
        use_viewareas = cmd_line_options->__use_viewareas;
        sawfish_only = cmd_line_options->__sawfish_only;
        early_desktop_switch = cmd_line_options->__early_desktop_switch;
        disable_keys_in_goto = cmd_line_options->__disable_keys_in_goto;
        autoacquire = cmd_line_options->__autoacquire;
        priority = cmd_line_options->__priority;
    }

    void init (int argc, char **argv) {
        cmd_line_options->init_from_command_line(argc, argv);

        sync_global_options();

        create_working_dir_if_necessary();
    };


    void set_config_set (char *name) {
        if (!strcmp(name, "default") || !strcmp(name, "cmdline")) {
            options = cmd_line_options;
            return;
        }

        int do_random = -1;
        if (!strcmp(name, "random")) {
            msgout (DEBUG, "*** picking random view\n");
            do_random = get_randomi(0, config_options.size());
        }

        list<Options *>::iterator k;
        int cnt = 0;
        for (k = config_options.begin(); k != config_options.end(); k++, cnt++) {

            if ( (do_random != -1 && cnt == do_random) 
                 ||
                 (do_random == -1 && !strcmp ((*k)->name, name)) )
            {
                options = *k;
                msgout (DEBUG, "*** FOUND cfg set %s\n", (*k)->name);
                break;
            }
        }
    }

    void reload () {

        // FIXME: track .conf timestamp for reloading efficiency?

        list<Options *>::iterator k;
        for (k = config_options.begin(); k != config_options.end(); k++)
            delete *k;
        config_options.clear();
        options = cmd_line_options;
        load_conf();
    }

    void load_conf () {

        // FIXME: track .conf timestamp for reloading efficiency?

        // Try opening  ~/.3ddesktop/3ddesktop.conf
        //       then   /usr/share/3ddesktop/3ddesktop.conf

        sprintf(config_file, "%s/3ddesktop.conf", working_dir);
        
        FILE *fp = fopen(config_file, "r");
        if (!fp) {

            sprintf(config_file, SYSCONFDIR "/3ddesktop.conf");
            
            fp = fopen(config_file, "r");
            if (!fp) {
                
                // don't error on this - may not be there
                msgout (DEBUG, "Could not open config file: 3ddesktop.conf: %s\n", 
                        strerror(errno));
                return;
            }
        }
        
        msgout (DEBUG, "load_conf: opened %s\n", config_file);

        char var1[100];
        char var2[100];
        char var3[100];
        char var4[100];
        char var5[100];

        char buf[1000];
        int buf_size = 1000;

        char *p = NULL;
        int n, line = 0;;

	Options *o = NULL;

        while (fgets (buf, buf_size, fp) ) {
            
            line++;

            //msgout (DEBUG, "config load: line %d read -> %s\n", line, buf);

            // null terminate at comment
            p = strpbrk (buf, "#");
            if (p)
                *p = 0;
            
            // now see if blank line
            n = strspn(buf, " \t\n");
            if (n == (int)strlen (buf))
                continue;

            n = sscanf(buf, "%s %s %s %s %s", var1, var2, var3, var4, var5);
            
            if (n < 2) {
                msgout (DEBUG, "config load: see %d vars only (line %d)\n", n, line);
                continue;
            }

            // ok something meaningful(less?) in vars

            //msgout (DEBUG, "config load: line %d found: %s %s\n", line, var1, var2);

	    if (strcmp(var1, "view") == 0) {
                if (strcmp(var2, "default") == 0) {
                    o = cmd_line_options;

                } else {
                    o = new Options(var2);
                    if (!o) {
                        msgout (ERROR, "out of memory for Options\n");
                        end_program(-1);
                    }
                    msgout (DEBUG, "Found view: %s\n", var2);
                    config_options.push_back(o);
                }

                continue;
	    }

	    if (!o) {
		//msgout(DEBUG, "not in a option set\n");

                // Treat these as "global" options 
                cmd_line_options->set_option(var1, var2, 0);

                sync_global_options();

	    } else {

                o->set_option(var1, var2, 0);

            }

        }

        fclose (fp);
    };


    void create_working_dir_if_necessary (void) {
        // first formulat the full path to location
        char *home_dir = getenv("HOME");
        char *username = getenv("USER");
        char failsafe[] = "noname";

        if (username == NULL) {
            username = getenv("LOGNAME");
            if (username == NULL) {
                username = getlogin();
                if (username == NULL) 
                    username = failsafe;
            }
        }

        if (home_dir == NULL || use_tmp) {
            //msgout (DEBUG, "$HOME not set: using /tmp/.3ddesktop-user\n");
            sprintf(working_dir, "/tmp/.3ddesktop-%s", username);
        } else {
            sprintf(working_dir, "%s/.3ddesktop", home_dir);
        }


        DIR *dir_p = opendir (working_dir);
        if (dir_p == NULL) {
            if (errno == ENOENT) {
                // try to make the dir now

                //char cmd[100];
                //sprintf (cmd, "mkdir %s", working_dir);
                //system (cmd);
                if (mkdir (working_dir, S_IRWXU) < 0) {
                    msgout(ERROR, "Could not make directory %s: %s\n",
                           working_dir, strerror(errno));
                    end_program(1);
                }
            } else {
                msgout(ERROR, "Could not open directory %s: %s\n", 
                       working_dir, strerror(errno));
                end_program (1);
            }
        } else {
            closedir(dir_p);
        }
    }; // END create_working_dir_if_necessary

    int make_pidfile (void) {
        
        pid_t pid;

        sprintf (pidfile, "%s/pid", working_dir);

        FILE *fh = fopen (pidfile, "w");
        if (fh == NULL) {
            msgout (ERROR, "Could not open %s: %s\n", 
                    pidfile, strerror(errno));
            return -1;
        }

        pid = getpid();
    
        fprintf (fh, "%d", pid);
    
        fclose (fh);
    
        return 0;
    } // END make_pidfile 



};



#endif // _CONFIG_HPP
