/* Copyright 2026 Kurt Nienhaus
 *
 * This 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 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 software.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include "main.h"

/* objcoll.c: object-collision functions */
extern void objcoll(void);

/* settings.c: load and save system-settings */
extern void settings_load(struct s_game *);
extern void settings_save(struct s_game *);
extern void settings_data_default(struct s_game *);

/* gamelevel.c: do actions according to game-level */
extern VG_BOOL gamelevel_before(struct s_game *);
extern VG_BOOL gamelevel_nextlevel(struct s_game *);
extern VG_BOOL gamelevel_start(struct s_game *);
extern int gamelevel_done(struct s_game *);
extern void gamelevel_stop(struct s_game *, int);
extern void gamelevel_after(struct s_game *, int);

void _my_assert(const char *, int, int);

static VG_BOOL cvexec_main_menu(struct s_game *);
static VG_BOOL show_help(void);
static VG_BOOL dgexec_sysmenu(void);
static const char * goldboy_name(struct s_game *);


/* assertion function */
void
_my_assert(const char *file, int line, int condit)
{
  if (condit) { return; }

  fprintf(stderr, "assert-condition failed at [%s:%d]\n", (file == NULL ? "(nofile)" : file), line);

  VG_dest();
  abort();
}


/* load and execute canvas main-menu; return: VG_TRUE=start-game, VG_FALSE=exit-request */
static VG_BOOL
cvexec_main_menu(struct s_game *sgame)
{
  const char *selname;
  struct VG_Canvas *cvas;
  int retw = VG_TRUE;

cvmm_start:
  vg4->window->clear();

  cvas = vg4->canvas->load("files/canvas/main-menu/top.cvas", NULL);
  if (cvas == NULL) { return VG_FALSE; }

  if (sgame->svdt.dold.gamelevel == 0) {
    vg4->canvas->disable(cvas, "bt_lastgame", VG_TRUE);
  }

cvmm_redo:
  if (!vg4->canvas->exec(cvas, NULL, &selname)) { retw = VG_FALSE; goto cvmm_end; }
  if (selname == NULL || *selname == '\0') { goto cvmm_redo; }

  if (strcmp(selname, "bt_help") == 0) {
    if (!show_help()) { retw = VG_FALSE; goto cvmm_end; }
  } else if (strcmp(selname, "bt_sysmenu") == 0) {
    if (!dgexec_sysmenu()) { retw = VG_FALSE; goto cvmm_end; }
  } else if (strcmp(selname, "bt_newgame") == 0) {
    settings_data_default(sgame);
    retw = VG_TRUE;
    goto cvmm_end;
  } else if (strcmp(selname, "bt_lastgame") == 0) {
    retw = VG_TRUE;
    goto cvmm_end;
  } else if (strcmp(selname, "bt_exit") == 0) {
    retw = VG_FALSE;
    goto cvmm_end;
  }

  vg4->canvas->destroy(cvas);
  goto cvmm_start;

cvmm_end:
  vg4->canvas->destroy(cvas);
  return retw;
}


/* show help */
static VG_BOOL
show_help(void)
{
  struct VG_Canvas *cvas;
  const char *selname;
  struct VG_Hash *hvar;
  VG_BOOL retw, asusp;

  hvar = vg4->hash->create();

  vg4->hash->setstr(hvar, "text", vg4->mlang->get("menu", "help-text"));

  /* load canvas */
  cvas = vg4->canvas->load("files/canvas/help/top.cvas", hvar);
  if (cvas == NULL) { return VG_FALSE; }

  asusp = vg4->audio->suspend(VG_TRUE);
  retw = VG_TRUE;

  for (;;) {
    /* execute canvas */
    if (!vg4->canvas->exec(cvas, NULL, &selname)) { retw = VG_FALSE; break; }
    if (selname == NULL || *selname == '\0') { break; }
    if (strcmp(selname, "close") == 0) { break; }
  }

  vg4->canvas->destroy(cvas);
  vg4->hash->destroy(hvar);
  vg4->audio->suspend(asusp);

  return retw;
}


/* execute system-menu dialog */
static VG_BOOL
dgexec_sysmenu(void)
{
  struct VG_Hash *hvar;
  VG_BOOL retw;

  hvar = vg4->hash->create();

  vg4->hash->setstr(hvar, "top:title", vg4->mlang->get("menu", "System-menu"));
  vg4->hash->setstr(hvar, "exit:top:title", vg4->mlang->get("menu", "Quit game?"));
  vg4->hash->setstr(hvar, "volume:top:title", vg4->mlang->get("menu", "Set audio volumes"));
  vg4->hash->setstr(hvar, "keydef:top:title", vg4->mlang->get("menu", "Key Redefinition"));
  vg4->hash->setstr(hvar, "keydef:press:title", vg4->mlang->get("menu", "Press key"));
  vg4->hash->setstr(hvar, "windowsize:top:title", vg4->mlang->get("menu", "Resize window"));
  vg4->hash->setstr(hvar, "winbright:top:title", vg4->mlang->get("menu", "Brightness"));

  vg4->audio->suspend(VG_TRUE);
  retw = vg4->dialog->sysmenu(NULL, NULL, hvar, NULL, NULL, NULL, NULL);
  vg4->audio->suspend(VG_FALSE);
  vg4->hash->destroy(hvar);

  return retw;
}


/* return goldboy-name */
static const char *
goldboy_name(struct s_game *sgame)
{
  static char btxt[64] = "";
  int boynr, divn;

  if (sgame == NULL) { return "NoName"; }

  divn = sgame->svdt.dnew.nr_ohard * 2 / 3;
  if (divn > 0) {
    boynr = sgame->svdt.dnew.gold / divn;
  } else {
    boynr = 0;
  }
  if (boynr > 6) { boynr = 6; }

  snprintf(btxt, sizeof(btxt), "GOLDBOY-%d", boynr);

  return btxt;
}


int main(int argc, char **argv) {
  struct s_game sgame;
  int gplay;
  VG_BOOL filmskip;

  (void)argc; (void)argv;

  /* initializations */

  memset(&sgame, 0, sizeof(sgame));

  if (!VG_init("Bomberboy")) { exit(1); }
  if (!vg4->window->open(VG_WINDOW_SIZE_LOW, VG_WINDOW_SCALE_BEST)) { VG_dest(); exit(1); }
  vg4->window->getsize(&sgame.winw, &sgame.winh);
  if (!vg4->audio->open(VG_AUDIO_FREQ_MEDIUM, VG_TRUE)) { VG_dest(); exit(1); }
  vg4->input->mouse_grabbing(VG_FALSE);

  vg4->mlang->fb_locale("en");
  vg4->mlang->add("files/mlang");

  objcoll();

  /* setting keys */

  /* quit with ALT+Q, not changeable, local key */
  if ((sgame.kref.k_quit_lalt = vg4->input->key_insert("Quit-LALT", VG_FALSE, VG_FALSE)) == 0) { VG_dest(); exit(1); }
  vg4->input->key_setkbd(sgame.kref.k_quit_lalt, VG_INPUT_KBDCODE_LALT);
  if ((sgame.kref.k_quit_q = vg4->input->key_insert("Quit-Q", VG_FALSE, VG_FALSE)) == 0) { VG_dest(); exit(1); }
  vg4->input->key_setkbd(sgame.kref.k_quit_q, VG_INPUT_KBDCODE_Q);
  /* system-menu with ESC, not changeable, local key */
  if ((sgame.kref.k_sysmenu = vg4->input->key_insert("System-menu", VG_FALSE, VG_FALSE)) == 0) { VG_dest(); exit(1); }
  vg4->input->key_setkbd(sgame.kref.k_sysmenu, VG_INPUT_KBDCODE_ESCAPE);
  /* help with H, changeable, local key */
  if ((sgame.kref.k_help = vg4->input->key_insert(vg4->mlang->get("menu", "Help"), VG_TRUE, VG_FALSE)) == 0) { VG_dest(); exit(1); }
  vg4->input->key_setkbd(sgame.kref.k_help, VG_INPUT_KBDCODE_H);
  /* pause with P, changeable, local key */
  if ((sgame.kref.k_pause = vg4->input->key_insert(vg4->mlang->get("menu", "Pause"), VG_TRUE, VG_FALSE)) == 0) { VG_dest(); exit(1); }
  vg4->input->key_setkbd(sgame.kref.k_pause, VG_INPUT_KBDCODE_P);
  /* go-forward with cursor-key RIGHT, changeable, local key */
  if ((sgame.kref.k_right = vg4->input->key_insert(vg4->mlang->get("menu", "Go forward"), VG_TRUE, VG_FALSE)) == 0) { VG_dest(); exit(1); }
  vg4->input->key_setkbd(sgame.kref.k_right, VG_INPUT_KBDCODE_RCURS);
  vg4->input->key_setgc(sgame.kref.k_right, 0, VG_INPUT_GCAXIS_LEFTX_RIGHT);
  /* go-backward with cursor-key LEFT, changeable, local key */
  if ((sgame.kref.k_left = vg4->input->key_insert(vg4->mlang->get("menu", "Go backward"), VG_TRUE, VG_FALSE)) == 0) { VG_dest(); exit(1); }
  vg4->input->key_setkbd(sgame.kref.k_left, VG_INPUT_KBDCODE_LCURS);
  vg4->input->key_setgc(sgame.kref.k_left, 0, VG_INPUT_GCAXIS_LEFTX_LEFT);
  /* go-up with cursor-key UP, changeable, local key */
  if ((sgame.kref.k_up = vg4->input->key_insert(vg4->mlang->get("menu", "Go up"), VG_TRUE, VG_FALSE)) == 0) { VG_dest(); exit(1); }
  vg4->input->key_setkbd(sgame.kref.k_up, VG_INPUT_KBDCODE_UCURS);
  vg4->input->key_setgc(sgame.kref.k_up, 0, VG_INPUT_GCAXIS_LEFTY_UP);
  /* go-down with cursor-key DOWN, changeable, local key */
  if ((sgame.kref.k_down = vg4->input->key_insert(vg4->mlang->get("menu", "Go down"), VG_TRUE, VG_FALSE)) == 0) { VG_dest(); exit(1); }
  vg4->input->key_setkbd(sgame.kref.k_down, VG_INPUT_KBDCODE_DCURS);
  vg4->input->key_setgc(sgame.kref.k_down, 0, VG_INPUT_GCAXIS_LEFTY_DOWN);
  /* drop-bomb with Space, changeable, local key */
  if ((sgame.kref.k_drop_bomb = vg4->input->key_insert(vg4->mlang->get("menu", "Drop bomb"), VG_TRUE, VG_FALSE)) == 0) { VG_dest(); exit(1); }
  vg4->input->key_setkbd(sgame.kref.k_drop_bomb, VG_INPUT_KBDCODE_SPACE);
  vg4->input->key_setgc(sgame.kref.k_drop_bomb, 0, VG_INPUT_GCBUTTON_RIGHTSHOULDER);

  /* load settings */
  settings_load(&sgame);

  /* play start */
  if (!vg4->film->play("files/film/start", NULL, &filmskip, NULL)) { VG_dest(); exit(0); }

  /* play intro */
  if (!vg4->film->play("files/film/intro", NULL, &filmskip, NULL)) { VG_dest(); exit(0); }

main_menu:
  { int audm = vg4->audio->load("files/audio/music/menu.wav", 100, VG_AUDIO_VOLUME_MUSIC);
    vg4->audio->play(audm, VG_TRUE, VG_FALSE);
    if (!cvexec_main_menu(&sgame)) { goto endgame; }
    vg4->audio->stop(audm, VG_FALSE);
    vg4->audio->unload(audm);
  }

  /* create level-global object-instances */
  if (!gamelevel_before(&sgame)) { goto endgame; }

  /* level loop */
  gplay = GAMEPLAY_NOOP;
  for (;; sgame.svdt.dnew.gamelevel++) {
    /* actualize settings */
    sgame.svdt.dold = sgame.svdt.dnew;

    /* test for next level */
    if (!gamelevel_nextlevel(&sgame)) { gplay = GAMEPLAY_DONE; break; }

    { char btxt[64];
      struct VG_Hash *hvar = vg4->hash->create();
      vg4->window->clear();
      vg4->window->flush();
      sleep(1);
      snprintf(btxt, sizeof(btxt), "%d", sgame.svdt.dnew.gold);
      vg4->hash->setstr(hvar, "goldcoins", btxt);
      snprintf(btxt, sizeof(btxt), "%d", sgame.svdt.dnew.gamelevel + 1);
      vg4->hash->setstr(hvar, "LEVEL", btxt);
      vg4->hash->setstr(hvar, "goldboy", vg4->mlang->get("film", goldboy_name(&sgame)));
      if (!vg4->film->play("files/film/zwseq", NULL, &filmskip, hvar)) { goto endgame; }
      vg4->hash->destroy(hvar);
      vg4->window->clear();
      vg4->window->flush();
      sleep(1);
    }

    /* do anything before level */
    if (!gamelevel_start(&sgame)) { goto endgame; }

    /* game loop */
    for (;;) {
      if (!vg4->input->update(VG_TRUE)) { goto endgame; }

      /* quit to main-menu? */
      if (vg4->input->key_newpressed(sgame.kref.k_quit_q) && vg4->input->key_pressed(sgame.kref.k_quit_lalt)) {
        gplay = GAMEPLAY_MENU;
        break;
      }

      /* help? */
      if (vg4->input->key_newpressed(sgame.kref.k_help)) {
        if (!show_help()) { goto endgame; }
      }

      /* pause? */
      if (vg4->input->key_newpressed(sgame.kref.k_pause)) {
        if (!vg4->misc->pause()) { goto endgame; }
      }

      /* system-menu? */
      if (vg4->input->key_newpressed(sgame.kref.k_sysmenu)) {
        if (!dgexec_sysmenu()) { goto endgame; }
      }

      if (!sgame.suspend) {
        /* call object-management-function f_run() for all object-managements */
        vg4->object->call_mgmt_run(&sgame);

        /* call f_run() of all object-instances */
        if (!vg4->object->call_run(&sgame)) {
          /* player is dead */
          gplay = GAMEPLAY_DEAD;
        }
      }

      /* call f_draw() of all object-instances */
      vg4->window->clear();
      vg4->object->call_draw(&sgame);

      /* flush contents to window and wait */
      vg4->window->flush();
      vg4->misc->wait_time(LOOP_TIME);

      /* player is dead? */
      if (gplay == GAMEPLAY_DEAD) { break; }

      /* level clear? */
      gplay = gamelevel_done(&sgame);
      if (gplay == GAMEPLAY_ERROR) { goto endgame; }
      if (gplay == GAMEPLAY_REDO) { sgame.svdt.dnew.gamelevel--; }
      if (gplay != GAMEPLAY_NOOP) { break; }
    }

    /* do anything after level */
    gamelevel_stop(&sgame, gplay);

    if (gplay == GAMEPLAY_DONE || gplay == GAMEPLAY_REDO) { gplay = GAMEPLAY_NOOP; }
    if (gplay != GAMEPLAY_NOOP) { break; }
  }

  /* reset window parameters */
  vg4->window->setattr(NULL);

  /* actualize settings */
  sgame.svdt.dnew = sgame.svdt.dold;

  /* do anything after level-loop */
  gamelevel_after(&sgame, gplay);

  if (gplay == GAMEPLAY_DONE) {  /* winner */
    char btxt[64];
    struct VG_Hash *hvar = vg4->hash->create();
    snprintf(btxt, sizeof(btxt), "%d", sgame.svdt.dnew.gold);
    vg4->hash->setstr(hvar, "goldcoins", btxt);
    vg4->hash->setstr(hvar, "goldboy", vg4->mlang->get("film", goldboy_name(&sgame)));
    if (!vg4->film->play("files/film/extro/succ", NULL, &filmskip, hvar)) { goto endgame; }
    vg4->hash->destroy(hvar);
    settings_data_default(&sgame);
    goto main_menu;
  } else if (gplay == GAMEPLAY_DEAD) {  /* dead */
    if (!vg4->misc->fadeout(0)) { goto endgame; }
    if (!vg4->film->play("files/film/extro/fail", NULL, &filmskip, NULL)) { goto endgame; }
  }

  goto main_menu;

endgame:
  /* save settings */
  settings_save(&sgame);

  /* destroy and exit */
  VG_dest();
  exit(0);
}
