/* newmenu.m,
 *
 * New main menu since someone keeps whinging about the old one.
 *
 * menu_1: Press START
 * menu_2: Play/Options/High Scores/Credits/Quit
 * PlayMenu: Single, Two, Custom (1), Custom (2), Demo
 * OptionsMenu: Controls/Volume/etc.
 */

#include <allegro.h>
#include <assert.h>
#include <math.h>
#include "common.h"
#include "debri.h"
#include "game.h"
#include "init.h"
#include "input.h"
#include "music.h"
#include "newmenu-controls.h"
#include "newmenu-credits.h"
#include "newmenu-custom.h"
#include "newmenu-dirty.h"
#include "newmenu-hiscore.h"
#include "newmenu-log.h"
#include "newmenu.h"
#include "player.h"
#include "projectiles/all-projs.h"
#include "seborrhea/container-lump.h"
#include "seborrhea/seborrhea-allegro.h"
#include "seborrhea/seborrhea-font.h"
#include "sound.h"
#include "units/all-units.h"


#define SOUND_SLIDER		0
#define MUSIC_SLIDER		1
#define DEBRI_SLIDER		2
#define NUM_CAMPAIGN_LEVELS	2


static const char *campaign_levels[] = {
    "maps/level1.map", "maps/level2.map", NULL
};

static BITMAP *dbuf;
static menu_t *current_menu;
static BOOL exit_raid = NO;
static SebFont *menu_font;
static Sebum<SebImage> *bg;

static enum {
    SHOW_HIGH_SCORES,
    SHOW_CREDITS,
    SHOW_DEMO    		/* XXX: not yet implemented */
} idle_todo;
BOOL emergency_exit = NO;
SebFile *menu_sebum;

/*--------------------------------------------------------------*/

volatile int counter;
static volatile int idle_counter;
static void ticker(void) { counter++; idle_counter++; }

/*--------------------------------------------------------------*/

static void activate_menu_2(void);
static void activate_play_menu(void);
static void activate_options_menu(void);
static void do_high_scores(void);
static void do_credits(void);
static void do_quit(void);
static void do_play_single(void);
static void do_play_two(void);
static void do_play_custom(void);
static void toggle_sound_level(void);
static void toggle_debri_level(void);
static void activate_customise_menu(void);
static void toggle_screen_mode(void);
static void do_customise_1(void);
static void do_customise_2(void);

static BOOL mainmenu_reload(void);
static void free_mainmenu_data(void);

/*--------------------------------------------------------------*/
/* The Menus.							*/
/*--------------------------------------------------------------*/

#define DUMMY_MENU_ITEM		{ "", 0, 0, 0, 0, 0, 0, NULL }
#define DUMMY_MENU_ITEM2	DUMMY_MENU_ITEM, DUMMY_MENU_ITEM
#define DUMMY_MENU_ITEM3	DUMMY_MENU_ITEM, DUMMY_MENU_ITEM2
#define DUMMY_MENU_ITEM4	DUMMY_MENU_ITEM, DUMMY_MENU_ITEM3
#define DUMMY_MENU_ITEM5	DUMMY_MENU_ITEM, DUMMY_MENU_ITEM4

static menu_t menu_1 = {
    { { "Press START", HOTKEY_MENU, 0, 0, 0, 0, 0, activate_menu_2 },
      DUMMY_MENU_ITEM5 },
    0, 1
};

static menu_t menu_2 = {
    { { "Play",			0, 0, 0, 0, 0, 0, activate_play_menu },
      { "Options",		0, 0, 0, 0, 0, 0, activate_options_menu },
      { "High Scores",		0, 0, 0, 0, 0, 0, do_high_scores },
      { "Credits",		0, 0, 0, 0, 0, 0, do_credits },
      { "Quit",	      HOTKEY_MENU, 0, 0, 0, 0, 0, do_quit },
      DUMMY_MENU_ITEM },
    0, 5
};

static menu_t play_menu = {
    { { "Single Player",	0, 0, 0, 0, 0, 0, do_play_single },
      { "Two Player",		0, 0, 0, 0, 0, 0, do_play_two },
      { "Custom",		0, 0, 0, 0, 0, 0, do_play_custom },
/*       { "Demo",			0, 0, 0, 0, 0, 0, NULL }, */
      { "Back",	      HOTKEY_MENU, 0, 0, 0, 0, 0, activate_menu_2 },
      DUMMY_MENU_ITEM },
    0, 4
};

static menu_t options_menu = {
    { { "Sound Volume",		0, 0, 20, 0, 0, 0, toggle_sound_level },
      { "Music Volume",		0, 0, 20, 0, 0, 0, toggle_sound_level },/* Same proc */
      { "Debri Level",		0, 0,  4, 0, 0, 0, toggle_debri_level },
      { "Customise Controls",	0, 0,  0, 0, 0, 0, activate_customise_menu },
      { "Toggle Fullscreen",	0, 0,  0, 0, 0, 0, toggle_screen_mode },
      { "Back",	      HOTKEY_MENU, 0,  0, 0, 0, 0, activate_menu_2 } },
    0, 6
};

static menu_t customise_menu = {
    { { "Player 1",		0, 0, 0, 0, 0, 0, do_customise_1 },
      { "Player 2",		0, 0, 0, 0, 0, 0, do_customise_2 },
      { "Back",	      HOTKEY_MENU, 0, 0, 0, 0, 0, activate_options_menu },
      DUMMY_MENU_ITEM3 },
    0, 3
};

/*--------------------------------------------------------------*/
/* Useful tools, and for consistancy.				*/
/*--------------------------------------------------------------*/

void play_back_sound(void) { [(<SebSample>)[menu_sebum getSebumByName:"back"] playWithVolume:sound_vol/255.0]; }
void play_bing_sound(void) { [(<SebSample>)[menu_sebum getSebumByName:"bing"] playWithVolume:sound_vol/255.0]; }
void play_okay_sound(void) { [(<SebSample>)[menu_sebum getSebumByName:"okay"] playWithVolume:sound_vol/255.0]; }

BOOL menu_select_previous(void)
{
    if ([menu_keyboard_controls pressed:RAID_KEY_UP])
	return YES;

    if (joy_enabled) {
	int j;

	/* Check all joysticks (and hats). */
	for (j = 0; j < num_joysticks && joystick_controller[j]; j++) {
	    if ([joystick_controller[j] pressed:RAID_KEY_UP])
		return YES;
	}
    }

    return NO;
}

BOOL menu_select_next(void)
{
    if ([menu_keyboard_controls pressed:RAID_KEY_DOWN])
	return YES;

    if (joy_enabled) {
	int j;

	/* Check all joysticks (and hats). */
	for (j = 0; j < num_joysticks && joystick_controller[j]; j++) {
	    if ([joystick_controller[j] pressed:RAID_KEY_DOWN])
		return YES;
	}
    }

    return NO;
}

BOOL menu_select_left(void)
{
    if ([menu_keyboard_controls pressed:RAID_KEY_LEFT])
	return YES;

    if (joy_enabled) {
	int j;

	/* Check all joysticks (and hats). */
	for (j = 0; j < num_joysticks && joystick_controller[j]; j++) {
	    if ([joystick_controller[j] pressed:RAID_KEY_LEFT])
		return YES;
	}
    }

    return NO;
}

BOOL menu_select_right(void)
{
    if ([menu_keyboard_controls pressed:RAID_KEY_RIGHT])
	return YES;

    if (joy_enabled) {
	int j;

	/* Check all joysticks (and hats). */
	for (j = 0; j < num_joysticks && joystick_controller[j]; j++) {
	    if ([joystick_controller[j] pressed:RAID_KEY_RIGHT])
		return YES;
	}
    }

    return NO;
}

void selected_item_colour(int *r, int *g, int *b, int t)
{
    *r = 0xff;
    *g = 0xe0 - 0x1f*cos(deg2rad(t));
    *b = 0x80 - 0x1f*cos(deg2rad(t));
}

static void switch_menu(menu_t *new)
{
    if (new == &menu_1) {
	/* Idle -> PRESS START. */
	menu_2.selected_item = 0;
	play_menu.selected_item = 0;
	options_menu.selected_item = 0;

	/* Options menu (idle) -> PRESS START */
	if (current_menu == &options_menu) {
	    options_menu.selected_item = 0;
	    bg = [menu_sebum getSebumByName:"backdrop"];
	}
    }

    elif (new == &menu_2) {
	/* Play menu -> main menu. */
	if (current_menu == &play_menu)
	    play_menu.selected_item = 0;

	/* Options menu -> main menu. */
	elif (current_menu == &options_menu) {
	    options_menu.selected_item = 0;
	    bg = [menu_sebum getSebumByName:"backdrop"];
	}
    }

    elif (new == &options_menu) {
	/* Main menu -> options menu. */
	bg = [menu_sebum getSebumByName:"options"];
    }

    elif (new == &customise_menu) {
	/* Options -> Customise controls for player. */
	customise_menu.selected_item = 0;
    }

    current_menu = new;
    mark_entire_screen_dirty();
}

/*--------------------------------------------------------------*/
/* Menu 1: Press START.						*/
/*--------------------------------------------------------------*/

static void activate_menu_2(void) { switch_menu(&menu_2); }

/*--------------------------------------------------------------*/
/* Menu 2: Play/Option/etc.					*/
/*--------------------------------------------------------------*/

static void activate_play_menu(void) { switch_menu(&play_menu); }
static void activate_options_menu(void) {switch_menu(&options_menu); }

static void do_high_scores(void)
{
    roll_high_scores(dbuf);
    switch_menu(&menu_1);
}

static void do_credits(void)
{
    roll_credits(dbuf);
    switch_menu(&menu_1);
}

static void do_quit(void) { exit_raid = YES; }

/*--------------------------------------------------------------*/
/* Play Menu.							*/
/*--------------------------------------------------------------*/

void play(const char *level_list[], unsigned int num_levels,
	  BOOL check_highscore, BOOL prefix_with_data_dir)
{
    assert(level_list && num_levels > 0);

    free_mainmenu_data();
    free_music();
    remove_int(ticker);

    raid_start_levels(level_list, num_levels,
		      check_highscore, prefix_with_data_dir);

    mainmenu_reload();
}

static void do_play_single(void) { num_players = 1; play(campaign_levels, NUM_CAMPAIGN_LEVELS, YES, YES); }
static void do_play_two(void)    { num_players = 2; play(campaign_levels, NUM_CAMPAIGN_LEVELS, YES, YES); }

static void do_play_custom(void)
{
    char *level_list[1];
    level_list[0] = select_custom_level(dbuf);

    if (level_list[0]) {
	play((const char **)level_list, 1, NO, NO);
	free(level_list[0]);
    }
}

/*--------------------------------------------------------------*/
/* Options Menu.						*/
/*--------------------------------------------------------------*/

static void toggle_sound_level(void)
{
    menuitem_t *sound_slider = &options_menu.menuitem[SOUND_SLIDER];
    menuitem_t *music_slider = &options_menu.menuitem[MUSIC_SLIDER];

    if ((sound_slider->slider_val == 0) &&
	(music_slider->slider_val == 0)) {
	sound_shutdown();
	music_shutdown();
    }
    else if (!sound_initialized) {
	if (sound_init() < 0) {
	    append_message("Error initializing sound. %s.", allegro_error);

	    sound_slider->slider_val = 0;
	    music_slider->slider_val = 0;
	    sound_vol = 0;
	    music_vol = 0;
	    return;
	}
	else
	    music_init();
    }

    sound_vol = 255 * sound_slider->slider_val/sound_slider->slider_max;
    music_vol = 255 * music_slider->slider_val/music_slider->slider_max;

    if (current_menu->selected_item == SOUND_SLIDER) {
	SebLump *lump = [base_sebum getSebumByName:"explosion-sounds"];
	[(<SebSample>)[lump getRandomSebum] playWithVolume:sound_vol/255.0];
    }
    else
	adjust_music_volume();
}

static void toggle_debri_level(void) { debri_amount = options_menu.menuitem[DEBRI_SLIDER].slider_val; }
static void activate_customise_menu(void) { switch_menu(&customise_menu); }

static void toggle_screen_mode(void)
{
    /* Unload all data in case we change colour depth. */
    mark_all_projectile_data_unnecessary();
    unload_unnecessary_projectile_data();
    mark_all_unit_data_unnecessary();
    unload_unnecessary_unit_data();

#ifndef NO_VIDEO_BITMAPS
    game_shutdown_video_bitmaps();
#endif

    if (fullscreen) {
	if (switch_gfx_mode(GFX_AUTODETECT_WINDOWED, SCREEN_W, SCREEN_H, 0) == 0)
	    fullscreen = NO;
	else
	    append_message("Error switching to windowed mode. %s.", allegro_error);
    }
    else {
	if (switch_gfx_mode(GFX_AUTODETECT_FULLSCREEN, SCREEN_W, SCREEN_H, 0) == 0)
	    fullscreen = YES;
	else
	    append_message("Error switching to fullscreen mode. %s.", allegro_error);
    }

#ifndef NO_VIDEO_BITMAPS
    game_init_video_bitmaps();
#endif

    mark_entire_screen_dirty(); 
}

static void do_customise_1(void) { customise_controls_for_player(dbuf, 0); }
static void do_customise_2(void) { customise_controls_for_player(dbuf, 1); }

/*--------------------------------------------------------------*/
/* Init/Shutdown.						*/
/*--------------------------------------------------------------*/

static void switch_into_menu(void)
{
    mark_entire_screen_dirty(); 
}

static BOOL load_mainmenu_data(void)
{
    menu_sebum = [SebFile new];
    if (not [menu_sebum loadSebumDirectory:"data/menu"]) {
	menu_sebum = nil;
	fprintf(stderr, "Error loading menu datafiles.\n");
	return NO;
    }
    else {
	bg = [menu_sebum getSebumByName:"backdrop"];
	menu_font = (SebFont *)[base_sebum getSebumByName:MENU_FONT_NAME];
	[menu_font setFontSize:MENU_FONT_SIZE];
	return YES;
    }
}

static BOOL mainmenu_reload(void)
{
    install_int_ex(ticker, BPS_TO_TIMER(MENU_SPEED));

    if (not load_mainmenu_data())
	return NO;

    {					/* Fix the sliders. */
	menuitem_t *sound_slider = &options_menu.menuitem[SOUND_SLIDER];
	menuitem_t *music_slider = &options_menu.menuitem[MUSIC_SLIDER];
	menuitem_t *debri_slider = &options_menu.menuitem[DEBRI_SLIDER];

	sound_slider->slider_val = sound_slider->slider_max * sound_vol/255;
	music_slider->slider_val = music_slider->slider_max * music_vol/255;
	debri_slider->slider_val = debri_amount;
    }

    set_display_switch_callback(SWITCH_IN, switch_into_menu);

    switch_menu(&menu_1);
    mark_entire_screen_dirty();
    play_music(MENU_MUSIC_DIRNAME);

    return YES;
}

static BOOL mainmenu_init(void)
{
    dbuf = create_bitmap(SCREEN_W, SCREEN_H);
    assert(dbuf);

    return mainmenu_reload();
}

static void free_mainmenu_data(void)
{
    FREE_SEBFILE(menu_sebum);
}

static void mainmenu_shutdown(void)
{
    free_mainmenu_data();
    FREE_BITMAP(dbuf);
    remove_int(ticker);
}

/*--------------------------------------------------------------*/

void menu_common_update(void)
{
    poll_raid_keys(0);
    poll_music();

    /* Alt-enter -> toggle full screen mode. */
    if ((key_shifts & KB_ALT_FLAG) &&
        [menu_keyboard_controls pressed:RAID_KEY_START]) {
	toggle_screen_mode();

	/* Note: if you want to be a tool and hold down alt-enter,
	   causing it to toggle screen mode, go ahead.  The problem
	   occurs (even tough I'm using 'pressed') because Allegro
	   clears key_shifts and key[].  These next lines are a hack
	   so enter released isn't detected by menu. */
	[menu_keyboard_controls poll];
	[menu_keyboard_controls poll];
    }
}

static BOOL menu_update(void)
{
    menuitem_t *sel = &current_menu->menuitem[current_menu->selected_item];
    int i;

    menu_common_update();

    if (menu_select_previous()) {
	if (current_menu->selected_item > 0) {
	    play_bing_sound();
	    current_menu->selected_item--;
	    return YES;
	}
    }

    if (menu_select_next()) {
	if (current_menu->selected_item < current_menu->n_menuitems-1) {
	    play_bing_sound();
	    current_menu->selected_item++;
	    return YES;
	}
    }

    if (sel->slider_max > 0) { /* Sliders. */
	if (menu_select_left()) {
	    sel->slider_val = MAX(sel->slider_val-1, 0);
	    if (sel->action)
		sel->action();
	    return YES;
	}
	if (menu_select_right()) {
	    sel->slider_val = MIN(sel->slider_val+1, sel->slider_max);
	    if (sel->action)
		sel->action();
	    return YES;
	}
    }

    else {				/* Non-slider. */
	if (okay_released()) {
	    if (sel->action) {
		play_okay_sound();
		sel->action();
		mark_entire_screen_dirty();
		return YES;
	    }
	}
    }

    /* Hotkeys. */
    for (i = 0; i < current_menu->n_menuitems; i++) {
	if (current_menu->menuitem[i].hotkey == HOTKEY_MENU) {
	    if (menu_released()) {
		if (current_menu->selected_item != i)
		    current_menu->selected_item = i;
		else if (sel->action) {
		    play_back_sound();
		    sel->action();
		    mark_entire_screen_dirty();
		}
		return YES;
	    }
	}
    }

    if (idle_counter >= 8*MENU_SECONDS) {
	if (current_menu != &menu_1) {
	    /* Go back to start menu due to inactivity. */
	    switch_menu(&menu_1);
	}
	else {
	    /* Already at start menu, show credits, high scores or
	       demo instead. */
	    if (idle_todo == SHOW_HIGH_SCORES) {
		idle_todo++;
		do_high_scores();
	    }
	    else if (idle_todo == SHOW_CREDITS) {
		idle_todo++;
		do_credits();
	    }
	    else			/* XXX: demo */
		idle_todo = SHOW_HIGH_SCORES;

	    mark_entire_screen_dirty();
	}

	return YES;
    }

    return NO;
}

void do_newmenu(void)
{
    BOOL redraw = YES;
    int t = 0;

    if (not mainmenu_init())
	exit_raid = YES;
    else
	mark_entire_screen_dirty();

    while (not (exit_raid || emergency_exit)) {
	while (counter) {
	    counter--;
	    redraw = YES;

	    t += 12;
	    if (t >= 360)
		t = 0;

	    if (menu_update())
		idle_counter = 0;

	    update_log();
	    rest(0);
	}

	if (redraw) {
	    int i, y, h;

	    redraw = NO;
	    clear_to_color(dbuf, makecol(0x40,0x40,0x40)); /* XXX */
	    [bg drawTo:dbuf X:(dbuf->w - [bg width])/2 Y:(dbuf->h - [bg height])/2 W:dbuf->w H:dbuf->h];

	    y = 250;
	    h = [menu_font textHeight];
	    for (i = 0; i < current_menu->n_menuitems; i++, y += h) {
		menuitem_t *m = &current_menu->menuitem[i];
		int l;

		if (i == current_menu->selected_item) {
		    selected_item_colour(&m->r, &m->g, &m->b, t);
		}
		else {
		    int delta = ABS(i - current_menu->selected_item);
		    int r = MAX(0xff - delta*0x08, 0);
		    int g = MAX(0xc0 - delta*0x10, 0);
		    int b = MAX(0x60 - delta*0x10, 0);

		    m->r = (m->r*2 + r) / 3;
		    m->g = (m->g*2 + g) / 3;
		    m->b = (m->b*2 + b) / 3;
		}

		[menu_font putString:m->text To:dbuf X:dbuf->w/2 Y:y Colour:m->r:m->g:m->b Alignment:ALIGN_CENTRE Decoration:DECORATION_OUTLINE];
		l = [menu_font textLength:m->text];

		/* Yes, yes.  I know the menu items don't always need
		   to be dirtied.  But I'm lazy. */
		mark_dirty_rectangle((dbuf->w-l)/2-1, y-1, l+2, h+2);

		if (m->slider_max > 0) {
		    int x = dbuf->w * 72/100, w = 106;

		    rect(dbuf, x,   y+8, x+w-1, y+h-6, makecol(0x00, 0x00, 0x00));
		    rect(dbuf, x+1, y+9, x+w-2, y+h-7, makecol(m->r, m->g, m->b));

		    if (m->slider_val > 0)
			rectfill(dbuf, x+3, y+11,
				 x+3 + (w-6)*m->slider_val/m->slider_max-1,
				 y+h-9, makecol(m->r, m->g, m->b));

		    mark_dirty_rectangle(x, y+8, w, h-8-6);
		}
	    }

	    /* XXX: don't use 'screen' when triple buffering. */
	    draw_log(dbuf);
	    blit_dirty_rectangles(dbuf, screen);
	}
    }

    mainmenu_shutdown();
}
