/*
 * LoadTracker
 *
 * 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
 */

// =============================================================================
// screen display routines
// =============================================================================

#define DISPLAY_C

#include "display.h"

#include "common.h"
#include "channels.h"
#include "colors.h"
#include "console.h"
#include "instr.h"
#include "order.h"
#include "pattern.h"
#include "play.h"
#include "settings.h"
#include "song.h"
#include "timer.h"

#include "bme_main.h"
#include "bme_snd.h"
#include "bme_win.h"

#include <utility>
#include <string>

#include <cstdio>
#include <cstring>

const char *programname;

const char *notename[] =
{
  "C-0", "C#0", "D-0", "D#0", "E-0", "F-0", "F#0", "G-0", "G#0", "A-0", "A#0", "B-0",
  "C-1", "C#1", "D-1", "D#1", "E-1", "F-1", "F#1", "G-1", "G#1", "A-1", "A#1", "B-1",
  "C-2", "C#2", "D-2", "D#2", "E-2", "F-2", "F#2", "G-2", "G#2", "A-2", "A#2", "B-2",
  "C-3", "C#3", "D-3", "D#3", "E-3", "F-3", "F#3", "G-3", "G#3", "A-3", "A#3", "B-3",
  "C-4", "C#4", "D-4", "D#4", "E-4", "F-4", "F#4", "G-4", "G#4", "A-4", "A#4", "B-4",
  "C-5", "C#5", "D-5", "D#5", "E-5", "F-5", "F#5", "G-5", "G#5", "A-5", "A#5", "B-5",
  "C-6", "C#6", "D-6", "D#6", "E-6", "F-6", "F#6", "G-6", "G#6", "A-6", "A#6", "B-6",
  "C-7", "C#7", "D-7", "D#7", "E-7", "F-7", "F#7", "G-7", "G#7", "...", "---", "+++"
};

char textbuffer[MAX_PATHNAME];

Tables tables;

bool menu = false;
int editmode = EDIT_PATTERN;
bool recordmode = true;
bool followplay = false;
int hexnybble = -1;
bool exitprogram = false;
int eacolumn = 0;
int eamode = 0;
EditHdr ehmode = EditHdr::NONE;

std::string tooltip;

void printmainscreen()
{
  clearscreen();
  printstatus();
  fliptoscreen();
}

void displayupdate()
{
  flashCursor();
  printstatus();
  fliptoscreen();
}

void printstatus()
{
  int cc = getCursorColor();

  menu = (input.mouseb > MOUSEB_LEFT) && (input.mousey <= 1) && (ehmode == EditHdr::NONE);

  printblankc(0, 0, colors.CHEADER, MAX_COLUMNS);

  // Header
  if (!menu)
  {
    printtext(dpos.statusTopX+1,  dpos.statusTopY, colors.getRainbowColor(0), " \x9b\x9b\x9b");
    printtext(dpos.statusTopX+5,  dpos.statusTopY, colors.getRainbowColor(1), "\x9b\x9b\x9b");
    printtext(dpos.statusTopX+8,  dpos.statusTopY, colors.getRainbowColor(2), "\x9b\x9b\x9b");
    printtext(dpos.statusTopX+11, dpos.statusTopY, colors.getRainbowColor(3), "\x9b\x9b\x9b");
    printtext(dpos.statusTopX+14, dpos.statusTopY, colors.getRainbowColor(4), "\x9b\x9b\x9b ");

    if (!std::strlen(loadedsongfilename))
      std::snprintf(textbuffer, MAX_PATHNAME, "%s", programname);
    else
      std::snprintf(textbuffer, MAX_PATHNAME, "%s - %s", programname, loadedsongfilename);
    textbuffer[49] = 0;
    printtext(dpos.statusTopX+19, dpos.statusTopY, colors.CHEADER, textbuffer);

    int color;

    color = ((config.numsids == 2) && !config.monomode) ? colors.CHEADER : colors.CMUTE|(colors.CHDRBG<<4);
    printtext(dpos.statusTopFvX-3, dpos.statusTopY, color, "ST");

    color = config.usefinevib ? colors.CHEADER : colors.CMUTE|(colors.CHDRBG<<4);
    printtext(dpos.statusTopFvX, dpos.statusTopY, color, "FV");

    color = config.optimizepulse ? colors.CHEADER : colors.CMUTE|(colors.CHDRBG<<4);
    printtext(dpos.statusTopFvX+3, dpos.statusTopY, color, "PO");

    color = config.optimizerealtime ? colors.CHEADER : colors.CMUTE|(colors.CHDRBG<<4);
    printtext(dpos.statusTopFvX+6, dpos.statusTopY, color, "RO");

    const char *clk = config.ntsc ? "NTSC" : "PAL";
    printtext(dpos.statusTopFvX+9, dpos.statusTopY, colors.CHEADER, clk);

    const char *model = config.sidmodel ? "8580" : "6581";
    printtext(dpos.statusTopFvX+14, dpos.statusTopY, colors.CHEADER, model);

    std::snprintf(textbuffer, MAX_PATHNAME, "HR:%04X", config.adparam);
    printtext(dpos.statusTopFvX+19, dpos.statusTopY, colors.CHEADER, textbuffer);
    if (ehmode == EditHdr::ADSR) printbg(dpos.statusTopFvX+22+eacolumn, dpos.statusTopY, cc, 1);

    if (config.multiplier)
    {
      std::snprintf(textbuffer, MAX_PATHNAME, "%2dX", config.multiplier);
      printtext(dpos.statusTopFvX+27, dpos.statusTopY, colors.CHEADER, textbuffer);
    }
    else printtext(dpos.statusTopFvX+27, dpos.statusTopY, colors.CHEADER, "25Hz");

    if (config.multiplier == 1)
    {
      std::snprintf(textbuffer, MAX_PATHNAME, "%03dBPM", snd_bpmtempo);
      printtext(dpos.statusTopFvX+31, dpos.statusTopY, colors.CHEADER, textbuffer);

      if (ehmode == EditHdr::BPM) printbg(dpos.statusTopFvX+31+eacolumn, dpos.statusTopY, cc, 1);
    }

    printtext(dpos.statusTopEndX-8, dpos.statusTopY, colors.CHEADER, "F12=HELP");
  }
  else
  {
    printtext(0, dpos.statusTopY, colors.CHEADER, " PLAY | PLAYPOS | PLAYPATT | STOP | LOAD | SAVE | PACK/RL | HELP | CLEAR | QUIT |");
  }

  int visibleOrderlist = config.getVisibleOrderlist();
  int maxChns = config.getMaxChannels();

  if (followplay && isplaying())
  {
    for (int c = 0; c < maxChns; c++)
    {
      int currentSonglen = song.len[esnum][c];
      int newpos = chn[c].pattptr / 4;
      if (chn[c].advance) epnum[c] = chn[c].pattnum;

      if (newpos > getPattlen(epnum[c])) newpos = getPattlen(epnum[c]);

      if (c == epchn)
      {
        eppos = newpos;
      }
      epview[c] = newpos-VISIBLEPATTROWS/2;

      newpos = chn[c].songptr;
      newpos--;
      if (newpos < 0) newpos = 0;
      if (newpos > currentSonglen) newpos = currentSonglen;

      if (chn[c].advance)
      {
        if (c == eschn) eseditpos = newpos;
        if (newpos - esview[c] < 0)
        {
          esview[c] = newpos;
        }
        if (newpos - esview[c] >= visibleOrderlist)
        {
          esview[c] = newpos - visibleOrderlist + 1;
        }
      }
    }
  }

  // Pattern view
  for (int c = 0; c < maxChns; c++)
  {
    std::snprintf(textbuffer, MAX_PATHNAME, "CH.%d PATT.%02X ", c+1, epnum[c]);
    printtext(dpos.patternsX+c*13, dpos.patternsY, colors.CTITLE|(colors.CHDRBG<<4), textbuffer);

    for (int d = 0; d < VISIBLEPATTROWS; d++)
    {
      int p = epview[c]+d;
      int color = colors.CNORMAL;
      if ((p == eppos) && !(followplay && isplaying())) color = colors.CEDIT;
      if ((epnum[c] == chn[c].pattnum) && (isplaying()))
      {
        int chnrow = chn[c].pattptr / 4;
        if (chnrow > getPattlen(chn[c].pattnum)) chnrow = getPattlen(chn[c].pattnum);
        if (chnrow == p) color = colors.CPLAYING;
      }

      if (chn[c].mute) color = colors.CMUTE;

      if ((p < 0) || (p > getPattlen(epnum[c])))
      {
        std::snprintf(textbuffer, MAX_PATHNAME, "             ");
      }
      else
      {
        if (!(config.patterndispmode & 1))
        {
          if (p < 100)
            std::snprintf(textbuffer, MAX_PATHNAME, " %02d", p);
          else
            std::snprintf(textbuffer, MAX_PATHNAME, "%03d", p);
        }
        else
          std::snprintf(textbuffer, MAX_PATHNAME, " %02X", p);

        if (song.pattern[epnum[c]][p*4] == ENDPATT)
        {
          std::sprintf(&textbuffer[3], " PATT. END");
          if (color == colors.CNORMAL) color = colors.CCOMMAND;
        }
        else
        {
          std::sprintf(&textbuffer[3], " %s %02X%01X%02X",
            notename[song.pattern[epnum[c]][p*4]-FIRSTNOTE],
            song.pattern[epnum[c]][p*4+1],
            song.pattern[epnum[c]][p*4+2],
            song.pattern[epnum[c]][p*4+3]);
          if (config.patterndispmode & 2)
          {
            if (!song.pattern[epnum[c]][p*4+1])
              std::memset(&textbuffer[8], '.', 2);
            if (!song.pattern[epnum[c]][p*4+2])
              std::memset(&textbuffer[10], '.', 3);
          }
        }
      }
      textbuffer[3] = 0;

      int color2 = (p%config.stepsize) ? colors.CNORMAL : colors.CCOMMAND;
      printtext(dpos.patternsX-1+c*13, dpos.patternsY+1+d, color2, textbuffer);

      color2 = (color == colors.CNORMAL) ? colors.CCOMMAND : color;
      printtext(dpos.patternsX+3+c*13, dpos.patternsY+1+d, color2, &textbuffer[4]);
      printtext(dpos.patternsX+7+c*13, dpos.patternsY+1+d, color, &textbuffer[8]);
      printtext(dpos.patternsX+9+c*13, dpos.patternsY+1+d, color2, &textbuffer[10]);
      printtext(dpos.patternsX+10+c*13, dpos.patternsY+1+d, color, &textbuffer[11]);
      if (c == epmark.chn)
      {
        int markstart = epmark.start;
        int markend = epmark.end;
        if (markstart > markend)
        {
            std::swap(markstart, markend);
        }
        if ((p >= markstart) && (p <= markend))
          printbg(dpos.patternsX+c*13+3, dpos.patternsY+1+d, colors.CHDRBG, 9);
      }
      if ((color == colors.CEDIT) && (editmode == EDIT_PATTERN) && (epchn == c))
      {
        switch(epcolumn)
        {
          case 0:
          if (ehmode == EditHdr::NONE) printbg(dpos.patternsX+c*13+3, dpos.patternsY+1+d, cc, 3);
          break;

          default:
          if (ehmode == EditHdr::NONE) printbg(dpos.patternsX+c*13+6+epcolumn, dpos.patternsY+1+d, cc, 1);
          break;
        }
      }
    }
  }

  // Orderlist view
  std::snprintf(textbuffer, MAX_PATHNAME, "CHN ORDERLIST (SUBTUNE ");
  printtext(dpos.orderlistX, dpos.orderlistY, colors.CTITLE|(colors.CHDRBG<<4), textbuffer);
  std::snprintf(textbuffer, MAX_PATHNAME, "%02X", esnum);
  printtext(dpos.orderlistX+23, dpos.orderlistY, colors.CEDIT|(colors.CHDRBG<<4), textbuffer);
  std::snprintf(textbuffer, MAX_PATHNAME, ", POS ");
  printtext(dpos.orderlistX+25, dpos.orderlistY, colors.CTITLE|(colors.CHDRBG<<4), textbuffer);
  std::snprintf(textbuffer, MAX_PATHNAME, "%02X", eseditpos);
  printtext(dpos.orderlistX+31, dpos.orderlistY, colors.CEDIT|(colors.CHDRBG<<4), textbuffer);
  std::snprintf(textbuffer, MAX_PATHNAME, ")");
  printtext(dpos.orderlistX+33, dpos.orderlistY, colors.CTITLE|(colors.CHDRBG<<4), textbuffer);
  const char *olhdr = (config.numsids == 1) ? "                                      " : "            ";
  std::snprintf(textbuffer, MAX_PATHNAME, "%s", olhdr);
  printtext(dpos.orderlistX+34, dpos.orderlistY, colors.CTITLE|(colors.CHDRBG<<4), textbuffer);

  for (int c = 0; c < maxChns; c++)
  {
    std::snprintf(textbuffer, MAX_PATHNAME, " %d ", c+1);
    printtext(dpos.orderlistX, dpos.orderlistY+1+c, colors.CTITLE, textbuffer);
    for (int d = 0; d < visibleOrderlist; d++)
    {
      int p = esview[c]+d;
      unsigned char currentSongorder = song.order[esnum][c][p];
      int currentSonglen = song.len[esnum][c];
      int color = colors.CNORMAL;
      if (isplaying())
      {
        int chnpos = chn[c].songptr;
        chnpos--;
        if (chnpos < 0) chnpos = 0;
        if ((p == chnpos) && (chn[c].advance)) color = colors.CPLAYING;
      }
      if (p == espos[c]) color = colors.CEDIT;
      if ((esend[c]) && (p == esend[c])) color = colors.CEDIT;

      if ((p < 0) || (p > (currentSonglen+1)) || (p > MAX_SONGLEN+1))
      {
        std::snprintf(textbuffer, MAX_PATHNAME, "   ");
      }
      else
      {
        if (currentSongorder < LOOPSONG)
        {
          if ((currentSongorder < REPEAT) || (p >= currentSonglen))
          {
            std::snprintf(textbuffer, MAX_PATHNAME, "%02X ", currentSongorder);
            if ((p >= currentSonglen) && (color == colors.CNORMAL)) color = colors.CCOMMAND;
          }
          else
          {
            if (currentSongorder >= TRANSUP)
            {
              std::snprintf(textbuffer, MAX_PATHNAME, "+%01X ", currentSongorder&0xf);
              if (color == colors.CNORMAL) color = colors.CCOMMAND;
            }
            else
            {
              if (currentSongorder >= TRANSDOWN)
              {
                std::snprintf(textbuffer, MAX_PATHNAME, "-%01X ", 16-(currentSongorder & 0x0f));
                if (color == colors.CNORMAL) color = colors.CCOMMAND;
              }
              else
              {
                std::snprintf(textbuffer, MAX_PATHNAME, "R%01X ", (currentSongorder+1) & 0x0f);
                if (color == colors.CNORMAL) color = colors.CCOMMAND;
              }
            }
          }
        }
        if (currentSongorder == LOOPSONG)
        {
          std::snprintf(textbuffer, MAX_PATHNAME, "RST");
          if (color == colors.CNORMAL) color = colors.CCOMMAND;
        }
      }
      if (chn[c].mute) color = colors.CMUTE;
      printtext(dpos.orderlistX+4+d*3, dpos.orderlistY+1+c, color, textbuffer);
      if (c == esmark.chn)
      {
        int markstart = esmark.start;
        int markend = esmark.end;
        if (markstart > markend)
        {
          std::swap(markstart, markend);
        }
        if ((p >= esmark.start) && (p <= esmark.end))
        {
          printbg(dpos.orderlistX+4+d*3, dpos.orderlistY+1+c, colors.CHDRBG, (p != esmark.end) ? 3 : 2);
        }
      }
      if ((p == eseditpos) && (editmode == EDIT_ORDERLIST) && (eschn == c))
      {
        if (ehmode == EditHdr::NONE) printbg(dpos.orderlistX+4+d*3+escolumn, dpos.orderlistY+1+c, cc, 1);
      }
    }
  }

  // Instruments view
  std::snprintf(textbuffer, MAX_PATHNAME, "INSTRUMENTS         AD SR WT PT FT VT VD GT FW");
  printtext(dpos.instrumentsX, dpos.instrumentsY, colors.CTITLE|(colors.CHDRBG<<4), textbuffer);

  for (int i=0; i<5; i++)
  {
    int insnum = eirow + i;
    int color = (insnum == einum) ? colors.CEDIT : colors.CNORMAL;
    std::snprintf(textbuffer, MAX_PATHNAME, "%2d %-16s %02X %02X %02X %02X %02X %02X %02X %02X %02X",
                insnum,
                song.instr[insnum].name,
                song.instr[insnum].ad,
                song.instr[insnum].sr,
                song.instr[insnum].ptr[WTBL],
                song.instr[insnum].ptr[PTBL],
                song.instr[insnum].ptr[FTBL],
                song.instr[insnum].ptr[STBL],
                song.instr[insnum].vibdelay,
                song.instr[insnum].gatetimer,
                song.instr[insnum].firstwave
                );
    printtext(dpos.instrumentsX, dpos.instrumentsY+1+i, color, textbuffer);
  }

  int selpos = einum - eirow;
  if ((editmode == EDIT_INSTRUMENT) && (selpos >= 0) && (selpos < 5))
  {
    if (eipos < 9)
    {
      // Instr param
      if (ehmode == EditHdr::NONE) printbg(dpos.instrumentsX+20+3*eipos+eicolumn, dpos.instrumentsY+1+selpos, cc, 1);
    }
    else
    {
      // Instr name
      if (ehmode == EditHdr::NONE) printbg(dpos.instrumentsX+3+std::strlen(song.instr[einum].name), dpos.instrumentsY+1+selpos, cc, 1);
    }
  }

  // Tables view
  std::snprintf(textbuffer, MAX_PATHNAME, "WAVE TBL    PULSE TBL   FILTER TBL  SPEED TBL ");
  printtext(dpos.instrumentsX, dpos.instrumentsY+7, colors.CTITLE|(colors.CHDRBG<<4), textbuffer);

  for (int c = 0; c < MAX_TABLES; c++)
  {
    for (int d = 0; d < VISIBLETABLEROWS; d++)
    {
      int p = tables.view(c)+d;

      int color = colors.CNORMAL;
      switch (c)
      {
        case WTBL:
        if (song.ltable[c][p] >= WAVECMD) color = colors.CCOMMAND;
        break;

        case PTBL:
        if (song.ltable[c][p] >= 0x80) color = colors.CCOMMAND;
        break;

        case FTBL:
        if ((song.ltable[c][p] >= 0x80) || ((!song.ltable[c][p]) && (song.rtable[c][p]))) color = colors.CCOMMAND;
        break;
      }
      if ((p == tables.pos()) && (tables.num() == c)) color = colors.CEDIT;
      std::snprintf(textbuffer, MAX_PATHNAME, "%02X:%02X %02X", p+1, song.ltable[c][p], song.rtable[c][p]);
      if (config.patterndispmode & 2)
      {
        if (!song.ltable[c][p] && !song.rtable[c][p] && !song.ltable[c][p+1] && !song.rtable[c][p+1])
        {
          std::memset(&textbuffer[3], '.', 2);
          std::memset(&textbuffer[6], '.', 2);
        }
      }
      printtext(dpos.instrumentsX+12*c, dpos.instrumentsY+8+d, color, textbuffer);

      if (tables.marknum() == c)
      {
        int markstart = tables.markstart();
        int markend = tables.markend();
        if (markstart > markend)
        {
            std::swap(markstart, markend);
        }
        if ((p >= markstart) && (p <= markend))
          printbg(dpos.instrumentsX+3+12*c, dpos.instrumentsY+8+d, colors.CHDRBG, 5);
      }
    }
  }

  if (editmode == EDIT_TABLES)
  {
    if (ehmode == EditHdr::NONE) printbg(dpos.instrumentsX+3+tables.num()*12+tables.columnpos(), dpos.instrumentsY+8+tables.pos()-tables.curview(), cc, 1);
  }

  // Info view
  printtext(dpos.instrumentsX, dpos.instrumentsY+8+VISIBLETABLEROWS+1, colors.CTITLE, "NAME     ");
  std::snprintf(textbuffer, MAX_PATHNAME, "%-32s", song.title);
  printtext(dpos.instrumentsX+9, dpos.instrumentsY+8+VISIBLETABLEROWS+1, colors.CEDIT, textbuffer);

  printtext(dpos.instrumentsX, dpos.instrumentsY+8+VISIBLETABLEROWS+2, colors.CTITLE, "AUTHOR   ");
  std::snprintf(textbuffer, MAX_PATHNAME, "%-32s", song.author);
  printtext(dpos.instrumentsX+9, dpos.instrumentsY+8+VISIBLETABLEROWS+2, colors.CEDIT, textbuffer);

  printtext(dpos.instrumentsX, dpos.instrumentsY+8+VISIBLETABLEROWS+3, colors.CTITLE, "RELEASED ");
  std::snprintf(textbuffer, MAX_PATHNAME, "%-32s", song.released);
  printtext(dpos.instrumentsX+9, dpos.instrumentsY+8+VISIBLETABLEROWS+3, colors.CEDIT, textbuffer);

  if ((editmode == EDIT_NAMES) && (ehmode == EditHdr::NONE))
  {
    switch(enpos)
    {
      case 0:
      printbg(dpos.instrumentsX+9+std::strlen(song.title), dpos.instrumentsY+8+VISIBLETABLEROWS+1, cc, 1);
      break;
      case 1:
      printbg(dpos.instrumentsX+9+std::strlen(song.author), dpos.instrumentsY+8+VISIBLETABLEROWS+2, cc, 1);
      break;
      case 2:
      printbg(dpos.instrumentsX+9+std::strlen(song.released), dpos.instrumentsY+8+VISIBLETABLEROWS+3, cc, 1);
      break;
    }
  }

  // Footer
  std::snprintf(textbuffer, MAX_PATHNAME, "OCTAVE %d", epoctave);
  printtext(dpos.octaveX, dpos.octaveY, colors.CTITLE, textbuffer);

  const char *edtmode = recordmode ? "EDITMODE" : "JAM MODE";
  printtext(dpos.octaveX, dpos.octaveY+1, colors.getAutoadvanceColor(autoadvance), edtmode);

  const char *playmode = isplaying() ? "PLAYING" : "STOPPED";
  printtext(dpos.octaveX+10, dpos.octaveY, colors.CTITLE, playmode);

  timer.get(textbuffer);
  int color = isplaying() ? colors.CEDIT : colors.CMUTE;
  printtext(dpos.octaveX+10, dpos.octaveY+1, color, textbuffer);

  const char *cmds = isplaying() ? " \x80 " : " \x81 ";
  printtext(dpos.octaveX+20, dpos.octaveY, colors.CTITLE|(colors.CHDRBG<<4), cmds);

  const char *chnls = (config.numsids == 1) ? " CHN1   CHN2   CHN3 " : " CHN1   CHN2   CHN3   CHN4   CHN5   CHN6 ";
  printtext(dpos.channelsX, dpos.channelsY, colors.CTITLE, chnls);
  for (int c = 0; c < maxChns; c++)
  {
    int chnpos = chn[c].songptr;
    int chnrow = chn[c].pattptr/4;
    chnpos--;
    if (chnpos < 0) chnpos = 0;
    if (chnrow > getPattlen(chn[c].pattnum)) chnrow = getPattlen(chn[c].pattnum);
    if (chnrow >= 100) chnrow -= 100;

    std::snprintf(textbuffer, MAX_PATHNAME, "%03X/%02X", chnpos, chnrow);
    printtext(dpos.channelsX+7*c, dpos.channelsY+1, chn[c].mute ? colors.CMUTE : colors.CEDIT, textbuffer);
  }

  color = tables.islocked() ? colors.CTITLE : colors.CMUTE;
  printtext(dpos.channelsX-5, dpos.channelsY+1, color, "Lck");

  // pad with spaces
  tooltip.append(40 - tooltip.length(), ' ');
  printtext(dpos.octaveX+35, dpos.octaveY+1, colors.CTOOLTIP, tooltip.c_str());
}

void settooltip(const char *msg)
{
    tooltip = msg;
}
