/* ANSI */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
/* POSIX */
#include <getopt.h>

#include "options.h"
#include "version.h"
#include "error.h"
#include "utils.h"
#include "types.h"
#include "menu.h"

#define MAXOPTIONS (20)

#ifndef ETCDIR
#define ETCDIR "/usr/local/etc"
#endif
#define DEFAULTPATH (ETCDIR ":.")

#define BLANKS " \t\f\r\n"
#define SPACES " \t\f"
#define ENDLINE "\r\n"

int TileXSize = 0 ;
int TileYSize = 0 ;

char MenuName [40] = "" ;

static char MyPixmapPath [1024] = "" ;
char * PixmapPath = MyPixmapPath ;

static char MyTilePath [FILENAME_MAX] = "" ;
char * TilePath = MyTilePath ;

static char MyHighlightPath [FILENAME_MAX] = "" ;
char * HighlightPath = MyHighlightPath ;

bool ClickOnly = false ;
bool AutoScale = true ;
bool HighlightBehind = false ;
int HideTimeout = 1 ;

int Options_Argc = 0 ;
char * Options_Argv [MAXOPTIONS] ;

static void Usage (void)
{
    printf("Usage: wmmenu [<options>...][-- <dockoptions>...]\n");
    printf("Normal options:\n");
    printf("-m MENUNAME    set the name of the menu file to load from ~/.wmmenu\n");
    printf("-g WxH         force width and height of tile\n");
    printf("-l XPMFILE     set the pixmap used to highlight icon under cursor\n");
    printf("-t XPMFILE     set the pixmap used as button bar background\n");
    printf("-O click       bar is only triggered by clicks on the tile, not moves\n");
    printf("-O noautoscale disable automatic pixmap scaling to tile size\n");
    printf("-O behind      draw highlight pixmap behind icon, not above\n");
    printf("-O hide=N      set bar hiding timeout to N ms (default one)\n");
    printf("-r ROWS        set number of menu rows (default one)\n");
    printf("-v             print version information\n");
    printf("-h             print this help message\n");
    printf("Note:\n");
    printf("  -t, -l and '-O behind' can also be specified with the defaults file.\n");
}

static void SetGeometry (const char * val)
{
    if (sscanf (val, "%dx%d", &TileXSize, &TileYSize) != 2)
        error ("bad tile geometry \"%s\"", val) ;
    else
    if (64 < TileXSize || TileXSize < 0)
        error ("incorrect tile width %d", TileXSize) ;
    else
    if (64 < TileYSize || TileYSize < 0)
        error ("incorrect tile height %d", TileYSize) ;
}

static void SetTilePath (const char * value)
{
    char *ptrToFree = NULL ;

    if (value != NULL && value[0] == '!')
    {
	ptrToFree = File_ReadOutputFromCommand (value+1) ;

	if ((value = ptrToFree) == NULL)
	    value = "" ;
    }

    sprintf (MyTilePath, "%.*s", (int)(sizeof MyTilePath)-1, value) ;

    if (ptrToFree != NULL)
	free (ptrToFree) ;
}

static void SetHighlightPath (const char * value)
{
    sprintf (MyHighlightPath, "%.*s", (int)(sizeof MyHighlightPath)-1, value) ;
}

static void SetHideTimeout (const char * value)
{
    HideTimeout = atoi (value) ;
    if (HideTimeout <= 0)
    {
	warn ("hide_timeout must be a strictly positive integer") ;
	HideTimeout = 1 ;
    }
    /* no maximum -- for players */
}

static void AddPixmapPath (const char * value)
{
    int curLen ;
    int szLeft ;

    curLen = strlen (MyPixmapPath) ;
    szLeft = (sizeof MyPixmapPath) - curLen ;

    /* don't forget to count space for ':' and ending EOS */
    sprintf (MyPixmapPath+curLen, ":%.*s", szLeft-2, value) ;
}

static void InitDefaults (void)
{
    char dftPixmapPath [FILENAME_MAX] ;
    const char * home ;

    if ((home = getenv ("HOME")) != NULL && home[0] != EOS)
    {
        sprintf (dftPixmapPath, "%.*s/.wmmenu",
            (int)(sizeof dftPixmapPath)-10, home) ;
    }
    else
    {
        strcpy (dftPixmapPath, ".") ;
    }

    strcpy (MyPixmapPath, dftPixmapPath) ;
}

static void ParseDefaults (char * text)
{
    char * p ;
    const char * name ;
    const char * value ;

    assert (text != NULL) ;
    p = text ;
    p += strspn (p, BLANKS) ;

    while (*p != EOS) switch (*p++)
    {
        case '#' :
            p += strcspn (p, ENDLINE) ;
            p += strspn (p, BLANKS) ;
            break ;

        default :
            name = p-1 ;
            p += strcspn (p, SPACES) ;
            *p++ = EOS ;
            p += strspn (p, SPACES) ;
            value = p ;
            p += strcspn (p, ENDLINE) ;
            *p++ = EOS ;
            p += strspn (p, BLANKS) ;

            if (streq (name, "geometry")) SetGeometry (value) ;
            else
            if (streq (name, "xpmpath"))
            {
                AddPixmapPath (value) ;
            }
            else
            if (streq (name, "tile"))
            {
                SetTilePath (value) ;
            }
	    else
	    if (streq (name, "highlight"))
	    {
		SetHighlightPath (value) ;
	    }
	    else
	    if (streq (name, "highlight_behind"))
	    {
		HighlightBehind = true ;
	    }
	    else
	    if (streq (name, "hide_timeout"))
	    {
		SetHideTimeout (value) ;
	    }
            else error ("unknown setting \"%s\"", name) ;

            break ;
    }
}

extern void Options_ParseDefaults (void)
{
    char pathList [1000] ;
    char path [FILENAME_MAX] ;
    const char * home ;
    FILE * f ;

    if ((home = getenv ("HOME")) != NULL && *home != EOS)
    {
        sprintf (pathList, "%s/.wmmenu:%s", home, DEFAULTPATH) ;
    }
    else
    {
        strcpy (pathList, DEFAULTPATH) ;
    }

    if (File_FindInPath (path, sizeof path, pathList, "defaults") &&
        (f = fopen (path, "r")) != NULL)
    {
        char * text ;

        text = File_ReadAll (f) ;
        fclose (f) ;

        InitDefaults () ;
        ParseDefaults (text) ;
        free (text) ;
    }
}

extern void Options_Parse (int argc, char ** argv)
{
    int opt ;

    assert (argv != NULL) ;
    assert (argc > 0) ;

    Options_Argc = 0 ;
    Options_Argv[Options_Argc++] = MenuName ;

    while ((opt = getopt (argc, argv, "hg:l:m:r:t:vO:")) != EOF) switch (opt)
    {
        case 'g' :
            SetGeometry (optarg) ;
            break ;

        case 'm' :
            sprintf (MenuName, "%.*s", (int)(sizeof MenuName)-1, optarg) ;
            break ;

        case 'r' :
            Menu_SetNbRows (optarg) ;
            break ;

        case 't' :
	    SetTilePath (optarg) ;
            break ;

	case 'l' :
	    sprintf (MyHighlightPath, "%.*s",
		(int)(sizeof MyHighlightPath)-1, optarg) ;
	    break ;

        case 'O' :
            if (streq (optarg, "click")) ClickOnly = true ;
            else if (streq (optarg, "noautoscale")) AutoScale = false ;
	    else if (streq (optarg, "behind")) HighlightBehind = true ;
	    else if (streql (optarg, "hide=", 5)) SetHideTimeout (optarg+5) ;
            else printf ("ignoring unknown feature \"%s\"\n", optarg) ;
            break ;

        case 'v' :
            printf ("wmmenu (c) F.COUTANT v%s\n", VERSION) ;
            exit (EXIT_SUCCESS) ;
            break ;

        case 'h' :
            Usage () ;
            exit (EXIT_SUCCESS) ;
            break ;

        case '?' :
            Usage () ;
            exit (EXIT_FAILURE) ;
            break ;
    }

    if (MenuName[0] == EOS)
        error ("no menu file name specified") ;
    if (MyTilePath[0] == EOS)
        warn ("no tile pixmap specified, using internal default") ;

    while (optind < argc && Options_Argc < MAXOPTIONS)
        Options_Argv[Options_Argc++] = argv[optind++] ;
}

extern void Options_SetMenuName (const char * name)
{
    sprintf (MenuName, "%.*s", (int)(sizeof MenuName)-1, name) ;
}
