/*
 * main.c
 * ------
 * 15/9/2025
 */

#include "main.h"
#include "lib.h"
#include "kbd.h"
#include "choices.h"

const char appname[] = "MidiCon";

// iconbar menu
static menu_t iconbar_menu =
{
  "MidiCon",NULL,7, 7,2,7,0,0,44,0,
  {
    {256,-1,IC_DEF,"Info",       NULL,4},
    {  0,-1,IC_DEF,"Convert...", NULL,10},
    {  0,-1,IC_DEF,"Help...",    NULL,7},
    {128,-1,IC_DEF,"Quit",       NULL,4}
  }
};
#define OPTION_MAX 4

// sample rate menu
static menu_t sample_rate_menu =
{
  "Sample rate",NULL,11, 7,2,7,0,0,44,0,
  {{0},{0},{0}} // must declare NUM_SAMPLE_RATES items here
};
static const int sample_rate[] = {32000, 44100, 48000};
#define NUM_SAMPLE_RATES (sizeof(sample_rate)/sizeof(const int))

// bitrate menu
static menu_t bitrate_menu =
{
  "MP3 Bitrate",NULL,11, 7,2,7,0,0,44,0,
  {{0},{0},{0},{0},{0},{0},{0},{0},{0},{0}} // must declare NUM_BITRATES items here
};
static const int bitrate[] = {64,80,96,112,128,160,192,224,256,320};
#define NUM_BITRATES (sizeof(bitrate)/sizeof(const int))

// tempo menu
static char tempo_str[8];
static menu_t tempo_menu =
{
  "Tempo %",NULL,7, 7,2,7,0,0,44,0,
  {{0x184,-1,IC_DEF,tempo_str,NULL,8}}
};

// options menu
static menu_t options_menu =
{
  "Options",NULL,7, 7,2,7,0,0,44,0,
  {
    {256,(int)&tempo_menu, IC_DEF,"Tempo scaler",NULL,12},
    {  0,(int)&sample_rate_menu,IC_DEF,"Sample rate",NULL,11},
    {  0,(int)&bitrate_menu,    IC_DEF,"MP3 Bitrate",NULL,11},
    {  0, -1, IC_DEF, "Mono", NULL, 4},
    {  2, -1, IC_DEF, "Save Options", NULL,12},
    {  0, -1, IC_DEF, "Help...", NULL,7},
    {128, -1, IC_DEF, "Quit", NULL, 4}
  }
};

#define LEAF_LEN 256
static char leaf[LEAF_LEN]; // leaf name of loaded midi file
static const char
  messages_file[]  = "<MidiConRes$Dir>.Messages",
  templates_file[] = "<MidiConRes$Dir>.Templates",
  choices_file[]   = "<MidiCon$Dir>.Choices",
  help_file[]      = "Filer_Run <MidiConRes$Dir>.Help";

opt_t option =
{ // defaults
  44100, // sample rate
  128,   // mp3 bitrate
  0,     // stereo
  100    // tempo percent
};

ro_t ro;


extern void close_bit_stream(void); // bitstream.c


/*
 * check_disc_space
 * ----------------
 * Ensures that there is enough spare room on the disc
 * Returns 0 for ok or -ve for any error or 1 if user pressed cancel
 */
int check_disc_space(char *filename, unsigned int size)
{
  _kernel_swi_regs regs;

//  report_error(itoa(size),0);

  if(size >= 0x80000000)
    return -FILE_OVER_2GB;

  // get max file size that can be saved
  char root[64], *r = root, *s = filename;
  do *r++ = *s; while(*s++ != '$');
  *r = 0;
  regs.r[0] = 55;
  regs.r[1] = (int)root;
  _kernel_swi(OS_FSControl, &regs, &regs);
  int max = regs.r[2];

  // if file already exists, it would be overwritten so add its size to the max size
  regs.r[0] = 17;
  regs.r[1] = (int)filename;
  _kernel_swi(OS_File, &regs, &regs);
  if(regs.r[0] == 2)
    return -CANNOT_OVERWRITE_DIR;
  if(regs.r[0] == 3)
    return -CANNOT_OVERWRITE_IMAGE;
  if(regs.r[0] == 1)
    max += regs.r[4];

  if(max < size)
    return -NO_ROOM_ON_DISC;

  if(regs.r[0] != 0)
  {
    if(report_error_number(-FILE_EXISTS, FLG_CANCEL) != 1) // confirm overwriting the file
      return 1; // user pressed cancel, not an error
  }

  return NO_ERROR_;
}

/*
 * read_menus
 * ----------
 * load menu text from Messages file if possible
 */
static void read_menus(void)
{
  static char menu_strings[256]; // holds all menu strings
  char *s = menu_strings;

  s = msg_load_menu("iconbar_m", s, &iconbar_menu);
  s = msg_load_menu("options_m", s, &options_menu);
  s = msg_load_menu("sample_rate_m", s, &sample_rate_menu);
  s = create_numeric_menu(sample_rate_menu.item, NUM_SAMPLE_RATES, sample_rate, s);
  s = msg_load_menu("bitrate_m", s, &bitrate_menu);
  s = msg_load_menu("tempo_m", s, &tempo_menu);
  s = create_numeric_menu(bitrate_menu.item, NUM_BITRATES, bitrate, s);

  iconbar_menu.item[0].sub = ro.win_data[WIN_INFO].win_handle;

  update_menu(sample_rate_menu.item, NUM_SAMPLE_RATES, 1); // 1 -> 44100
  update_menu(bitrate_menu.item, NUM_BITRATES, 4);  // 4 -> 128
  sprintf(tempo_str,"%d",option.tempo);
}


/*
 * file_icon_state
 * ---------------
 * Sets the file icons and stop button disabled states, 0 or 1. Anything else, no change
 */
void file_icon_state(int wave, int mp3, int text, int stop)
{
  int win = ro.win_data[WIN_MAIN].win_handle;
  icon_disabled_change(wave, win, ICON_TEXT3);
  icon_disabled_change(wave, win, ICON_SAVE_WAV);
  icon_disabled_change(mp3,  win, ICON_TEXT4);
  icon_disabled_change(mp3,  win, ICON_SAVE_MP3);
  icon_disabled_change(text, win, ICON_TEXT5);
  icon_disabled_change(text, win, ICON_SAVE_TEXT);
  icon_disabled_change(stop, win, ICON_STOP);
}


/*
 * update_option_display
 * ---------------------
 */
static void update_option_display(void)
{
  char s[40];
  int win = ro.win_data[WIN_MAIN].win_handle;
  sprintf(s, "tempo scaler: %d%%", option.tempo);
  icon_text_change(s, win, ICON_OPTION1);
  sprintf(s, "sample rate: %d sps", option.sample_rate);
  icon_text_change(s, win, ICON_OPTION2);
  sprintf(s, "MP3 bitrate: %d Kbps", option.bitrate);
  icon_text_change(s, win, ICON_OPTION3);
  sprintf(s, "format: %s", (option.mono) ? "mono" : "stereo");
  icon_text_change(s, win, ICON_OPTION4);
 }


/*
 * read_midicon_choices
 * --------------------
 */
static int read_midicon_choices(const char *filename)
{
  int i, err;

  if((err = choices_read(&syn, &option, filename)) < NO_ERROR_)
    return err;

  midiSynth_reinit(&option);
  midiPlayer_init(&option);
  mp3_init(&option);
  update_option_display();
  if(leaf[0] != 0)
    player_rescan();
  // update menu's
  for(i=0; i<NUM_SAMPLE_RATES; i++)
    if(sample_rate[i] == option.sample_rate)
    {
      update_menu(sample_rate_menu.item, NUM_SAMPLE_RATES, i);
      break;
    }
  for(i=0; i<NUM_BITRATES; i++)
    if(bitrate[i] == option.bitrate)
    {
      update_menu(bitrate_menu.item, NUM_BITRATES, i);
      break;
    }
  options_menu.item[3].item_flags = (options_menu.item[3].item_flags & ~IT_TICKED) | option.mono;
  sprintf(tempo_str,"%d",option.tempo);
  return 0;
}


/*
 * load_midi_file
 * --------------
 */
static void load_midi_file(char *filename)
{
  int err;
  int win = ro.win_data[WIN_MAIN].win_handle;

  // extract leafname less any extension for output files
  strncpy(leaf, strrchr(filename, '.')+1, LEAF_LEN-1);
  char *p = strrchr(leaf, '/');
  if(p)
    *p = 0;
  icon_text_change(leaf, win, ICON_FILENAME);
  if((err = player_load(filename, 0)) < 0)
  {
    // on error, clear data, disable text display
    leaf[0] = 0;
    icon_disabled_change(1, win, ICON_TEXT1);
    icon_disabled_change(1, win, ICON_TEXT2);
    icon_text_change("", win, ICON_FILENAME);
    report_error_number(err, 0);
  }
  else // load ok, enable text display
  {
    icon_disabled_change(0, win, ICON_TEXT1);
    icon_disabled_change(0, win, ICON_TEXT2);
  }
}


/*
 * save_file
 * ---------
 */
static void save_file(char *filename)
{
  int err = 0;

  if(leaf[0] == 0)
    return; // no midi file loaded

  switch(ro.drag)
  {
    case ICON_SAVE_WAV:
      err = player_save_wav(filename);
      break;

    case ICON_SAVE_MP3:
      err = player_save_mp3(filename);
      break;

    case ICON_SAVE_TEXT:
      err = player_save_txt(filename);
      break;
  }

  if(err < 0)
    report_error_number(err, 0);

  ro.drag = 0;
}


/*
 * mouse_click
 * -----------
 */
static void mouse_click(int blk[])
{
  // iconbar
  if(blk[3] == ICONBAR)
  {
    switch(blk[2])
    {
      case MOUSE_SELECT:
        open_window(WIN_MAIN);
        break;

      case MOUSE_MENU:
        open_menu(blk[0] - 64, (44*OPTION_MAX)+96, MENU_ICONBAR, (int)&iconbar_menu);
        break;
    }
    return;
  }

  // convert window
  if(blk[3] == ro.win_data[WIN_MAIN].win_handle)
  {
    if(blk[2] == MOUSE_MENU)
    {
      if(!ro.converting)
        open_menu(blk[0] - 64, blk[1], MENU_OPTIONS, (int)&options_menu);
    }
    else
    {
      switch(blk[4])
      {
#ifdef TEST
        case WORKAREA:
          if(blk[2] == MOUSE_ADJUST)
            open_window(WIN_TEST); // open test window
          break;
#endif

        case ICON_SAVE_WAV:
        case ICON_SAVE_MP3:
          if(ro.converting)
            break; // else follow through
        case ICON_SAVE_TEXT:
          if(leaf[0] != 0)
            if(blk[2] == 4*16) // start a drag
              drag_start(blk[3], blk[4]);
          break;

        case ICON_STOP:
          player_stop();
          break;
      }
    }
  }
}


/*
 * menu_selection
 * --------------
 */
static void menu_selection(int blk[])
{
  int b[5];
  _kernel_swi_regs regs;

  regs.r[1] = (int)b;
  _kernel_swi(Wimp_GetPointerInfo, &regs, &regs);

  switch(ro.cur_menu)
  {
    case MENU_ICONBAR:
      switch(blk[0])
      {
        case 1: // open window
          open_window(WIN_MAIN);
          break;

        case 2: // help
          _kernel_oscli(help_file);
          break;

        case 3: // quit
          ro.flags |= 1<<QUIT;
          break;
      }
      if(b[2] == 1) // adjust
        open_menu(0, 0, MENU_ICONBAR, (int)&iconbar_menu);
      break;

    case MENU_OPTIONS:
      switch(blk[0])
      {
        case 0: // tempo
          if(blk[1] == 0)
          {
            option.tempo = atoi(tempo_str);
            LIMIT(10, option.tempo, 1000);
            sprintf(tempo_str,"%d",option.tempo);
            midiPlayer_init(&option);
            update_option_display();
            if(leaf[0] != 0)
              player_rescan();
          }
          break;

        case 1: // sample rate
          if(blk[1] != -1)
          {
            option.sample_rate = sample_rate[blk[1]];
            midiSynth_reinit(&option);
            mp3_init(&option);
            update_menu(sample_rate_menu.item, NUM_SAMPLE_RATES, blk[1]);
            update_option_display();
          }
          break;

        case 2: // bitrate
          if(blk[1] != -1)
          {
            option.bitrate = bitrate[blk[1]];
            mp3_init(&option);
            update_menu(bitrate_menu.item, NUM_BITRATES, blk[1]);
            update_option_display();
          }
          break;

        case 3: // mono
          option.mono ^= 1;
          options_menu.item[3].item_flags = (options_menu.item[3].item_flags & ~IT_TICKED) | option.mono;
          midiSynth_reinit(&option);
          midiPlayer_init(&option);
          mp3_init(&option);
          update_option_display();
          break;

        case 4: // Save
          choices_write(&syn, &option, choices_file);
          break;

        case 5: // help
          _kernel_oscli(help_file);
          break;

        case 6: // quit
          ro.flags |= 1<<QUIT;
          break;
      }
      if(b[2] == 1) // adjust
        open_menu(0, 0, MENU_OPTIONS, (int)&options_menu);
      break;

  }
}


/*
 * msg_help
 * --------
 * Supplies help text. Requires the HelpRequest message block and current menu tag.
 */
static void msg_help(int *b, int cur_menu)
{
  _kernel_swi_regs regs;
  int blk[10], ok = 0;

  // Main window
  if(b[8] == ro.win_data[WIN_MAIN].win_handle)
  {
    if((ok = msg_lookup("main_wh", b[9], (char *)&b[5])) == 0)
      ok = msg_lookup("main_wh", -1, (char *)&b[5]);
  }

  // Info window
  else if(b[8] == ro.win_data[WIN_INFO].win_handle)
    ok = msg_lookup("info_wh", -1, (char *)&b[5]);

  // unknown window, check for a menu
  else if(b[8] != -2)
  {
    regs.r[0] = 1;
    regs.r[1] = (int)blk;
    regs.r[2] = b[8]; // window handle
    regs.r[3] = b[9]; // icon handle
    _kernel_swi(Wimp_GetMenuState, &regs, &regs);
    switch(cur_menu)
    {
      case MENU_ICONBAR:
        ok = msg_lookup("iconbar_mh", blk[0], (char *)&b[5]);
        break;

      case MENU_OPTIONS:
        ok = msg_lookup("options_mh", blk[0], (char *)&b[5]);
        if(ok && (blk[1] != -1))
        {
          if(blk[0] == 0)
            ok = msg_lookup("tempo_mh", -1, (char *)&b[5]);
          else if(blk[0] == 1)
            ok = msg_lookup("sample_rate_mh", -1, (char *)&b[5]);
          else if(blk[0] == 2)
            ok = msg_lookup("bitrate_mh", -1, (char *)&b[5]);
        }
       break;
    }
  }

  if(!ok)
    return; // no help available

  // send help text to Help application
  regs.r[0] = 17;     // User message
  regs.r[1] = (int)b;
  regs.r[2] = b[1];   // sender task handle
  b[0] = 256; // buffer length
  b[3] = b[2];
  b[4] = MESSAGE_HELPREPLY;
  _kernel_swi(Wimp_SendMessage, &regs, &regs);
}


/*
 * wimp_msg
 * --------
 * Handles all messages
 */
static void wimp_msg(int blk[], int msg)
{
  _kernel_swi_regs regs;
  int err;

  switch (blk[4]) // message action
  {
    case MESSAGE_QUIT:
      ro.flags |= 1<<QUIT;
      break;

    case MESSAGE_DATALOAD: // file dropped on window or Iconbar icon
      if(ro.converting)
        break;

      if(blk[10] == MIDI) // midi file
      {
        if(blk[5] == ICONBAR)
          open_window(WIN_MAIN);
        load_midi_file((char *)&blk[11]);
      }
      else if(blk[10] == TEXT) // choices file
      {
        if((err = read_midicon_choices((char *)&blk[11])) < NO_ERROR_)
          report_error_number(err, 0);
      }

      if(msg == 18)
      {
        regs.r[0] = 19;                // user message acknowledge
        regs.r[1] = (int)blk;
        regs.r[2] = blk[1];            // task handle of sender
        blk[3] = blk[2];               // my ref
        blk[4] = MESSAGE_DATALOADACK;  // action
        _kernel_swi(Wimp_SendMessage, &regs, &regs);
      }
      break;

    case MESSAGE_DATASAVEACK: // end of drag
      save_file((char *)&blk[11]);
      break;

    case MESSAGE_HELPREQUEST:
      msg_help(blk, ro.cur_menu);
      break;
  }
}


/*
 * loadtemplates
 * -------------
 */
static void loadtemplates(const char * filename)
{
  _kernel_swi_regs regs;

  read_vdu_vars();

  regs.r[0] = READ_CATINFO;
  regs.r[1] = (int)filename;
  _kernel_swi(OS_FILE,&regs,&regs);
  if(regs.r[0] != IS_FILE)
    report_error_number(-CANNOT_OPEN_TEMPLATES_FILE, -1);

  regs.r[1] = (int)filename;
  _kernel_swi(Wimp_OpenTemplate, &regs, &regs);

  if(loadtemplate("Main", &ro.win_data[WIN_MAIN], 0) == 0)
    report_error_number(-MAIN_TEMPLATE_NOT_FOUND, -1);

  if(loadtemplate("Info", &ro.win_data[WIN_INFO], 0) == 0)
    report_error_number(-INFO_TEMPLATE_NOT_FOUND, -1);

#ifdef TEST
  if(loadtemplate("Test", &ro.win_data[WIN_TEST], 0) == 0)
    report_error_number(-TEST_TEMPLATE_NOT_FOUND, -1);
#endif

  _kernel_swi(Wimp_CloseTemplate, &regs, &regs);
  slider_value(WIN_MAIN, ICON_BAR_BACK, 0);
}


/*
 * terminate_app
 * -------------
 */
static void terminate_app(void)
{
  _kernel_swi_regs regs;

  if(ro.flags & (1<<MESSAGES_OPEN))
    msg_close();

  if(ro.converting == AMPEG)
    close_bit_stream();
  else if(ro.converting == WAVE)
    player_close();

  midiPlayer_term();
  midiSynth_term();

  regs.r[0] = ro.task_handle;
  regs.r[1] = *(int *)"TASK";
  _kernel_swi(Wimp_CloseDown, &regs, &regs);
}


/*
 * main
 * ----
 * Main Wimp Poll loop.
 */
int main(void)
{
  _kernel_swi_regs regs;
  int blk[64];
  int msglist[] = {0};

  // exit if we are already running
  regs.r[0] = 0;
  while(regs.r[0] >= 0)
  {
    regs.r[1] = (int)blk;
    regs.r[2] = 4*sizeof(int);
    _kernel_swi(TaskManager_EnumerateTasks, &regs, &regs);
    if(strcmp((char *)blk[1],appname) == 0)
      return 0;
  }

  regs.r[0] = 350;
  regs.r[1] = *(int *)"TASK";
  regs.r[2] = (int)appname;
  regs.r[3] = (int)msglist;
  _kernel_swi(Wimp_Initialise, &regs, &regs);
  ro.task_handle = regs.r[1];

  // create iconbar icon
  blk[0] = -1; // r/h icon
  blk[1] = 0;
  blk[2] = 0;
  blk[3] = 68;
  blk[4] = 68;
  blk[5] = 0x0000301a;
  strcpy((char *)&blk[6], "!midicon");
  regs.r[0] = 0;
  regs.r[1] = (int)blk;
  _kernel_swi(Wimp_CreateIcon, &regs, &regs);

  atexit(terminate_app);

  if(!msg_open(messages_file))
    report_error("Cannot open messages file", -1);
  loadtemplates(templates_file);
  read_menus();
  update_option_display();
#ifdef TEST
  slider_value(WIN_TEST, ICON_TST_BACK, 0);
#endif
  midiSynth_init(&option);
  midiPlayer_init(&option);
  mp3_init(&option);
  read_midicon_choices(choices_file); // ignore any returned error

  while ((ro.flags & (1<<QUIT)) == 0)
  {
    regs.r[0] = (ro.converting) ? 0x3830 : 0x3831; // poll mask
    regs.r[1] = (int)blk;
    _kernel_swi(Wimp_Poll, &regs, &regs);
    switch (regs.r[0]) // result
    {
      case 0: // null reason
        if(ro.converting == AMPEG)
          mp3_recording();
        else if(ro.converting == WAVE)
          midiPlayer_recording();
        break;

      case 2: _kernel_swi(Wimp_OpenWindow, &regs, &regs); break;
      case 3: _kernel_swi(Wimp_CloseWindow, &regs, &regs); break;
      case 6: mouse_click(blk); break;
      case 7: drag_return(leaf); break; // use leafname for output files
      case 9: menu_selection(blk); break;
      case 17:
      case 18:
      case 19: wimp_msg(blk, regs.r[0]); break;
    }
  }

  return 0;
}

