/*  
    menu.c

    Copyright (C) 2010-2019 Amf

    This program 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 of the License, or
    (at your option) any later version. 

    This program 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 this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <time.h>
#include <string.h>
#include <libintl.h>
#include <locale.h>

#include "chroma.h"
#include "menu.h"
#include "level.h"
#include "util.h"
#include "xmlparser.h"

int menu_levelentries(struct menu* pmenu, char *directory, int flags);
void level_play(struct level* plevelgame);

extern struct level* plevelcurrent;

extern int options_debug;
#ifdef XOR_COMPATIBILITY
extern int options_xor_options;
#endif
#ifdef ENIGMA_COMPATIBILITY
extern int options_enigma_options;
#endif

struct menu *menu_new(char *title)
{
    struct menu* pmenu;

    pmenu = (struct menu*)malloc(sizeof(struct menu));

    if(pmenu == NULL)
        fatal(gettext("Out of memory in menu_new()"));

    pmenu->title = (char *)malloc(strlen(title) + 1);

    if(pmenu->title == NULL)
        fatal(gettext("Out of memory in menu_new()"));
     
    strcpy(pmenu->title, title);

    pmenu->entry_first = NULL;
    pmenu->entry_last = NULL;
    pmenu->entry_selected = NULL;
    pmenu->display_first = NULL;
    pmenu->display_last = NULL;
    pmenu->offset = 0;
    pmenu->redraw = MENUREDRAW_ALL;
    pmenu->logo = 0;

    return pmenu;
}

void menu_delete(struct menu *pmenu)
{
    struct menuentry *pentry;
    struct menuentry *pentrytmp;

    pentry = pmenu->entry_first;

    while(pentry != NULL)
    {
        pentrytmp = pentry;
        pentry = pentry->next;

        if(pentrytmp->text != NULL)
            free(pentrytmp->text);
        if(pentrytmp->text2 != NULL)
            free(pentrytmp->text2);
        if(pentrytmp->text3 != NULL)
            free(pentrytmp->text3);
        if(pentrytmp->text4 != NULL)
            free(pentrytmp->text4);
        if(pentrytmp->value != NULL)
            free(pentrytmp->value);

        free(pentrytmp);
    }

    if(pmenu->title != NULL)
        free(pmenu->title);

    free(pmenu);
}

void menu_unsort(struct menu *pmenu)
{
    struct menuentry* pentry;

    if(pmenu == NULL)
        return;

    pentry = pmenu->entry_first;
    while(pentry != NULL)
    {
        pentry->flags &= ~(MENU_SORT | MENU_TOPSORT);
        pentry = pentry->next;
    }
}

void menu_assignletters(struct menu *pmenu)
{
    struct menuentry* pentry;
    struct menuentry* pentry_before;
    char c;

    if(pmenu == NULL)
        return;

    c = '1';

    pentry = pmenu->entry_first;
    while(pentry != NULL)
    {
        if(pentry->key == 0 && !(pentry->flags & (MENU_GREY | MENU_NOTE | MENU_SPACE)))
        {
            pentry_before = pmenu->entry_first;
            while(pentry_before != NULL && pentry_before != pentry)
            {
                if(pentry_before->key == c)
                {
                    c ++;

                    /* Valid keys are 1-9 A-P R-Z. Q is deliberately omitted. */
                    if(c == '9' + 1)
                        c = 'A';
                    if(c == 'Q')
                        c ++;
                    if(c == 'Z' + 1)
                        return;

                    pentry_before = pmenu->entry_first;
                }
                else
                    pentry_before = pentry_before ->next;
            }
            pentry->key = c;
        }
        pentry = pentry->next;
    }
}

struct menuentry* menuentry_new(struct menu *pmenu, char *text, char key, int flags)
{
    return menuentry_newwithvalue(pmenu, text, key, flags, NULL);
}

struct menuentry* menuentry_newwithvalue(struct menu *pmenu, char *text, char key, int flags, char *value)
{
    struct menuentry* pentry;
    struct menuentry* pentry_before;

    pentry = (struct menuentry*)malloc(sizeof(struct menuentry));

    if(pentry == NULL)
        fatal(gettext("Out of memory in menuentry_newwithvalue()"));
    
    if(flags & MENU_SORT)
    {
        pentry_before = pmenu->entry_first;
        while(pentry_before != NULL)
        {
            if((pentry_before->flags & MENU_SORT) && (!(pentry_before->flags & MENU_TOPSORT)) && pentry_before->value != NULL)
            {
                if(strcmp(pentry_before->value, value) > 0)
                    break;
            }
            pentry_before = pentry_before->next;
        }
    }
    else if(flags & MENU_TOPSORT)
    {
        pentry_before = pmenu->entry_first;
        while(pentry_before != NULL)
        {
            if((pentry_before->flags & MENU_TOPSORT) && pentry_before->value != NULL)
            {
                if(strcmp(pentry_before->value, value) > 0)
                    break;
            }

            if(pentry_before->flags & MENU_SORT && !(pentry_before->flags & MENU_TOPSORT))
                break;

            pentry_before = pentry_before->next;
        }
    }
    else
        pentry_before = NULL;

    /* If pentry_before is NULL, we add to the bottom of the list.
       Otherwise, we add before pentry_before.
     */
    if(pentry_before == NULL)
    {
        if(pmenu->entry_first == NULL)
            pmenu->entry_first = pentry;

        pentry->previous = pmenu->entry_last;
        pentry->next = NULL;

        if(pmenu->entry_last != NULL)
            pmenu->entry_last->next = pentry;

        pmenu->entry_last = pentry;
    }
    else
    {
        if(pentry_before == pmenu->entry_first)
            pmenu->entry_first = pentry;
        pentry->previous = pentry_before->previous;
        pentry->next = pentry_before;
        if(pentry_before->previous != NULL)
            pentry_before->previous->next = pentry;
        pentry_before->previous = pentry;
    }

    pentry->text = (char *)malloc(strlen(text) + 1);

    if(pentry->text == NULL)
        fatal(gettext("Out of memory in menuentry_newwithvalue()"));

    strcpy(pentry->text, text);

    pentry->redraw = 0;

    pentry->flags = flags;

    pentry->key = key;

    pentry->value = NULL;
    if(value != NULL)
    {
        pentry->value = (char *)malloc(strlen(value) + 1);

        if(pentry->value == NULL)
        fatal(gettext("Out of memory in menuentry_newwithvalue()"));

        strcpy(pentry->value, value);
    }

    pentry->text2 = NULL;
    pentry->text3 = NULL;
    pentry->text4 = NULL;

    return pentry;
}

void menuentry_value(struct menuentry* pentry, char *value)
{
    if(pentry->value != NULL)
        free(pentry->value);

    pentry->value = (char *)malloc(strlen(value) + 1);

    if(pentry->value == NULL)
        fatal(gettext("Out of memory in menuentry_value()"));

    strcpy(pentry->value, value);
}

void menuentry_text(struct menuentry* pentry, char *text)
{
    if(pentry->text != NULL)
        free(pentry->text);

    pentry->text = (char *)malloc(strlen(text) + 1);

    if(pentry->text == NULL)
        fatal(gettext("Out of memory in menuentry_text()"));

    strcpy(pentry->text, text);
}

void menuentry_extratext(struct menuentry* pentry, char *text2, char *text3, char *text4)
{
    if(pentry->text2 != NULL)
        free(pentry->text2);
    if(pentry->text3 != NULL)
        free(pentry->text3);
    if(pentry->text4 != NULL)
        free(pentry->text4);

    if(text2 != NULL)
    {
        pentry->text2 = (char *)malloc(strlen(text2) + 1);
        if(pentry->text2 == NULL)
            fatal(gettext("Out of memory in menuentry_extratext()"));

        strcpy(pentry->text2, text2);
    }
    else
        pentry->text2 = NULL;

    if(text3 != NULL)
    {
        pentry->text3 = (char *)malloc(strlen(text3) + 1);
        if(pentry->text3 == NULL)
            fatal(gettext("Out of memory in menuentry_extratext()"));

        strcpy(pentry->text3, text3);
    }
    else
        pentry->text3 = NULL;

    if(text4 != NULL)
    {
        pentry->text4 = (char *)malloc(strlen(text4) + 1);
        if(pentry->text4 == NULL)
            fatal(gettext("Out of memory in menuentry_extratext()"));

        strcpy(pentry->text4, text4);
    }
    else
        pentry->text4 = NULL;
}

void menuentry_delete(struct menu* pmenu, struct menuentry* pentry)
{
    struct menuentry* pentry_replace;

    pentry_replace = pentry->next;
    if(pentry_replace == NULL)
        pentry_replace = pentry->previous;

    if(pmenu->entry_first == pentry)
        pmenu->entry_first = pentry_replace;
    if(pmenu->display_first == pentry)
        pmenu->display_first = pentry_replace;
    if(pmenu->entry_last == pentry)
        pmenu->entry_last = pentry_replace;
    if(pmenu->entry_selected == pentry)
        pmenu->entry_selected = pentry_replace;
    if(pentry->previous != NULL)
        pentry->previous->next = pentry->next;
    if(pentry->next != NULL)
        pentry->next->previous = pentry->previous;

    if(pentry->text != NULL)
        free(pentry->text);
    if(pentry->text2 != NULL)
        free(pentry->text2);
    if(pentry->text3 != NULL)
        free(pentry->text3);
    if(pentry->text4 != NULL)
        free(pentry->text4);
    if(pentry->value != NULL)
        free(pentry->value);

    free(pentry);
}

int menu_levelselector(char *directory_levels, char *chosen, char *title, int flags)
{
    struct menu* pmenu;
    struct level* plevel;
    int flags_child;
    int result;
    char directory[FILENAME_MAX];

    pmenu = menu_new(title);

    if(flags & LEVELMENU_RETURN)
        menuentry_new(pmenu, gettext("Return to previous menu"), 'Q', 0);

    if(flags & LEVELMENU_NEW)
        menuentry_new(pmenu, gettext("Save as new position"), 'S', 0);

    if(flags & LEVELMENU_CREATE)
        menuentry_new(pmenu, gettext("Create new level"), 'C', 0);

    if(flags & LEVELMENU_IMPORT)
    {
        getfilename("", directory, 0, LOCATION_DOCUMENTS);
        if(isdirectory(directory))
            menuentry_newwithvalue(pmenu, gettext("Import a position"), 'I', 0, directory);
    }

    if(flags & LEVELMENU_SAVED)
    {
        getfilename("saved", directory, 0, LOCATION_LOCAL);
        if(isdirectory(directory))
            menuentry_newwithvalue(pmenu, gettext("Saved Positions"), 'S', MENU_BOLD, directory);
    }

    if(flags & LEVELMENU_SOLVED)
    {
        getfilename("solved", directory, 0, LOCATION_LOCAL);
        if(isdirectory(directory))
            menuentry_newwithvalue(pmenu, gettext("Solved Positions"), 'O', MENU_BOLD, directory);
    }

    if(flags & LEVELMENU_MAIN)
    {
        getfilename("construct", directory, 0, LOCATION_LOCAL);
        if(isdirectory(directory))
            menuentry_newwithvalue(pmenu, gettext("Levels under Construction"), 'C', MENU_BOLD, directory);
    }

    /* Separator only if needed */
    if(pmenu->entry_first != NULL)
        menuentry_new(pmenu, "", 0, MENU_TOPSORT | MENU_SPACE);

    if(flags & LEVELMENU_DELETE)
        menuentry_new(pmenu, gettext("[DELETE] to delete a position"), 0, MENU_NOTE | MENU_CENTRE);

    if(flags & LEVELMENU_MAIN)
    {
        getfilename("levels", directory, 0, LOCATION_SYSTEM);
        if(menu_levelentries(pmenu, directory, flags))
            menuentry_newwithvalue(pmenu, "", 0, MENU_SORT | MENU_SPACE, "");

        menu_unsort(pmenu);

        /* Separator only if needed */
        if(pmenu->entry_last != NULL && !(pmenu->entry_last->flags & MENU_SPACE))
            menuentry_newwithvalue(pmenu, "", 0, MENU_SPACE, "");

            getfilename("levels", directory, 0, LOCATION_LOCAL);
        if(menu_levelentries(pmenu, directory, flags))
            menuentry_newwithvalue(pmenu, "", 0, MENU_SORT | MENU_SPACE, "");
    }
    else
    {
        if(menu_levelentries(pmenu, directory_levels, flags))
            menuentry_newwithvalue(pmenu, "", 0, MENU_SORT | MENU_SPACE, "");
    }

    menu_assignletters(pmenu);

    while(1)
    {
        result = menu_process(pmenu);

        if(result == MENU_QUIT)
        {
            if(chosen != NULL)
                strcpy(chosen, "");
        break;
        }

        if(result == MENU_SELECT)
        {
            if(pmenu->entry_selected->key == '+' && flags & LEVELMENU_FILTER)
            {
                result = menu_levelselector(directory_levels, chosen, title, flags & ~LEVELMENU_FILTER);
                if(result == MENU_SELECT)
                    break;
            }
            else if(pmenu->entry_selected->value != NULL)
            {
                if(isfile(pmenu->entry_selected->value))
                {
                    if(flags & LEVELMENU_GAME)
                    {
                        plevel = level_load(pmenu->entry_selected->value, 0);
                        if(plevel != NULL)
                            level_play(plevel);
                        /* plevel is deleted by level_play */
                    }
                    else
                    {
                        if(chosen != NULL)
                            strcpy(chosen, pmenu->entry_selected->value);
                        break;
                    }
                }
                else if(isdirectory(pmenu->entry_selected->value))
                {
                    flags_child = flags & (LEVELMENU_NEW | LEVELMENU_DELETE | LEVELMENU_FILENAMES | LEVELMENU_MOVES | LEVELMENU_GAME | LEVELMENU_FILTER);
                    flags_child |= LEVELMENU_RETURN;

                    /* Fix flags for "saved", "solved" and "construct" */
                    if(pmenu->entry_selected->key == 'S')
                        flags_child |= LEVELMENU_MOVES | LEVELMENU_DELETE;
                    if(pmenu->entry_selected->key == 'C')
                        flags_child |= LEVELMENU_MOVES | LEVELMENU_DELETE;
                    if(pmenu->entry_selected->key == 'O')
                        flags_child |= LEVELMENU_MOVES | LEVELMENU_DELETE;

                    result = menu_levelselector(pmenu->entry_selected->value, chosen, pmenu->entry_selected->text, flags_child);

                    if(result == MENU_SELECT)
                        break;

                    result = MENU_NULL;
                }
            }
            else
            {
                if(chosen != NULL)
                    strcpy(chosen, "");

                if(pmenu->entry_selected->key == 'Q')
                    result = MENU_QUIT;

                break;
            }
        }

        if(result == MENU_DELETE)
        {
            if(pmenu->entry_selected->value != NULL)
                unlink(pmenu->entry_selected->value);
            menuentry_delete(pmenu, pmenu->entry_selected);
        }
    }

    menu_delete(pmenu);

    return result;
}

int menu_levelentries(struct menu* pmenu, char *directory, int flags)
{
    DIR *pdir;
    struct dirent *dentry;
    struct menuentry* pentry;
    struct level* plevel;
    struct stat filestat;
    int flags_child;
    int submenus;
    int subfiles;
    int state;
    int hidden;
    int filtered;

    char text_date[256];
    char text_moves[256];
    char text_stars[256];
    char *text_title;
    char *text_subtitle;

    char filename[FILENAME_MAX];
    char titlefilename[FILENAME_MAX];

    struct parser* pparser;

    submenus = 0;
    subfiles = 0;
    filtered = 0;

    pdir = opendir(directory);

    if(pdir == NULL)
    return 0;

    while((dentry = readdir(pdir)) != NULL)
    {
        if(strcmp(dentry->d_name, ".") == 0)
            continue;
        if(strcmp(dentry->d_name, "..") == 0)
            continue; 

        if(dentry->d_name[0] == '.')
            continue;

        sprintf(filename, "%s%s%s", directory, "/", dentry->d_name);

        if(isfile(filename) && strlen(filename) > 7 && strcmp(filename + strlen(filename) - 7, ".chroma") == 0)
        {
            plevel = level_load(filename, 1);

            if(plevel != NULL)
            { 
            flags_child = MENU_SORT;
            if(flags & (LEVELMENU_MOVES | LEVELMENU_FILENAMES))
                    flags_child |= MENU_DOUBLE;
            if(flags & LEVELMENU_DELETE)
                flags_child |= MENU_DELETABLE;

            if(plevel->title == NULL || strcmp(plevel->title, "") == 0)
                pentry = menuentry_newwithvalue(pmenu, gettext("[untitled level]"), 0, flags_child, filename);
            else
            {
                if(strncmp(plevel->title, "chroma", 6) == 0)
                pentry = menuentry_newwithvalue(pmenu, gettext(plevel->title), 0, flags_child, filename);
                else
                pentry = menuentry_newwithvalue(pmenu, plevel->title, 0, flags_child, filename);
            }

            if(plevel->stars_exploded)
                sprintf(text_stars, gettext("%d lost"), plevel->stars_exploded);
            else
                sprintf(text_stars, "%d / %d", plevel->stars_caught, plevel->stars_total);
            if(plevel->flags & LEVELFLAG_SOLVED && !(plevel->flags & LEVELFLAG_FAILED))
                sprintf(text_stars, gettext("solved"));

            if(plevel->flags & LEVELFLAG_FAILED)
                sprintf(text_moves, gettext("failed"));
            else
                sprintf(text_moves, gettext("%d moves"), plevel->moves);

            if(!stat(filename, &filestat))
            {
                sprintf(text_date, "%s", ctime(&filestat.st_mtime));
                if(strlen(text_date) > 0)
                    text_date[strlen(text_date) - 1] = 0;
            }
            else
                strcpy(text_date, "");

            if(flags & LEVELMENU_FILENAMES)
                menuentry_extratext(pentry, NULL, text_date, dentry->d_name);
            else if(flags & LEVELMENU_MOVES)
                menuentry_extratext(pentry, text_stars, text_date, text_moves);
            else
            {
                /* Not really needed except for development */
                if(plevel->flags & LEVELFLAG_SOLVED && !(plevel->flags & LEVELFLAG_FAILED))
                    menuentry_extratext(pentry, gettext("solved"), NULL, NULL);
            }

            if((flags & LEVELMENU_FILTER) && plevelcurrent != NULL)
            {
                if((plevelcurrent->title != NULL && strcmp(plevelcurrent->title, pentry->text) != 0)
                        || (plevelcurrent->title == NULL && plevel->title != NULL))
                {
                    filtered ++;
                    menuentry_delete(pmenu, pentry);
                    continue;
                }
            }

#ifdef XOR_COMPATIBILITY
            if(plevel->mode == MODE_XOR)
                options_xor_options = 1;
#endif
#ifdef ENIGMA_COMPATIBILITY
            if(plevel->mode == MODE_ENIGMA)
                options_enigma_options = 1;
#endif
                subfiles ++;
            }

            level_delete(plevel);
        }
        else if(isdirectory(filename))
        {
            flags_child = MENU_TOPSORT | MENU_BOLD;
            if(flags & (LEVELMENU_MOVES | LEVELMENU_FILENAMES))
                    flags_child |= MENU_DOUBLE;

            /* Read the title information from set.chroma, if it exists */
            snprintf(titlefilename, FILENAME_MAX, "%s/%s", filename, "set.chroma");
            text_title = NULL;
            text_subtitle = NULL;

            hidden = 0;

            pparser = parser_new(titlefilename);

            enum {
                LEVELSETPARSER_END,
                LEVELSETPARSER_OUTSIDE,
                LEVELSETPARSER_CHROMA
            };

            state = LEVELSETPARSER_OUTSIDE;

            while(state != LEVELSETPARSER_END)
            {
                switch(parser_parse(pparser))
                {
                    case PARSER_END:
                        state = LEVELSETPARSER_END;
                        break;

                    case PARSER_ELEMENT_START:
                        break;

                    case PARSER_ELEMENT_END:
                        switch(state)
                        {
                            case LEVELSETPARSER_CHROMA:
                                /* We only read the file's header here */
                                if(parser_match(pparser, 0, "head"))
                                    state = LEVELSETPARSER_END;
                                break;
                        }
                        break;

                    case PARSER_CONTENT:
                        switch(state)
                        {   
                            case LEVELSETPARSER_CHROMA:
                                if(parser_match(pparser, 1, "title"))
                                {   
                                    text_title = malloc(strlen(parser_text(pparser, 0)) + 1);
                                    if(text_title == NULL)
                                        fatal(gettext("Out of memory in menu_levelentries()")); 
                                    strcpy(text_title, parser_text(pparser, 0));
                                }
                                if(parser_match(pparser, 1, "subtitle"))
                                {   
                                    text_subtitle = malloc(strlen(parser_text(pparser, 0)) + 1);
                                    if(text_title == NULL)
                                        fatal(gettext("Out of memory in menu_levelentries()")); 
                                    strcpy(text_subtitle, parser_text(pparser, 0));
                                }
                                break;
                        }
                        break;

                    case PARSER_ATTRIBUTE:
                        switch(state)
                        {   
                            case LEVELSETPARSER_OUTSIDE:
                                if(parser_match(pparser, 2, "chroma") && parser_match(pparser, 1, "type"))
                                {   
                                    if(parser_match(pparser, 0, "set"))
                                        state = LEVELSETPARSER_CHROMA;
                                }
                                break;

                            case LEVELSETPARSER_CHROMA:
                                if(parser_match(pparser, 2, "chroma") && parser_match(pparser, 1, "hidden"))
                                {   
                                    if(parser_match(pparser, 0, "yes"))
                                        hidden = 1;
                                }
                                break;
                        }
                        break;

                    case PARSER_ERROR:
                        state = LEVELSETPARSER_END;
                        break;
                }
            }

            parser_delete(pparser);

            if(!hidden || options_debug & DEBUG_HIDDEN)
            {
                if(text_title != NULL)
                {
                    if(strncmp(text_title, "chroma", 6) == 0)
                        pentry = menuentry_newwithvalue(pmenu, gettext(text_title), 0, flags_child | (text_subtitle != NULL ? MENU_DOUBLE : 0), filename);
                    else
                        pentry = menuentry_newwithvalue(pmenu, text_title, 0, flags_child | (text_subtitle != NULL ? MENU_DOUBLE : 0), filename);

                }
                else
                pentry = menuentry_newwithvalue(pmenu, dentry->d_name, 0, flags_child, filename);

                if(flags & LEVELMENU_FILENAMES)
                    menuentry_extratext(pentry, NULL, NULL, dentry->d_name);
                else
                {
                    if(text_title != NULL && strncmp(text_title, "chroma", 6) == 0)
                        menuentry_extratext(pentry, NULL, NULL, gettext(text_subtitle));
                    else
                        menuentry_extratext(pentry, NULL, NULL, text_subtitle);
                }
                if(text_title != NULL)
                    free(text_title);
                if(text_subtitle != NULL)
                    free(text_subtitle);

                submenus ++;
            }
        }
    }

    closedir(pdir);

    if(filtered != 0)
    {
        if(submenus != 0 || subfiles != 0)
            menuentry_new(pmenu, "", 0, MENU_SPACE);
        pentry = menuentry_new(pmenu, gettext("Show positions for other levels"), '+', 0);
        sprintf(text_date, filtered == 1 ? gettext("(%d other)") : gettext("(%d others)"), filtered);
        menuentry_extratext(pentry, text_date, NULL, NULL);
    }

    if(submenus !=0 && subfiles != 0)
        return 1;
    else
        return 0;

}

