/**
 * @file tracker.cc
 *
 * @brief Legacy code from Hyrule Magic TrackerLogic.c
 *
 * @details Attemtping to extract the song banks and convert them into SPC
 * format for the snes_spc library.
 *
 */
#include "tracker.h"

#include <cstdint>
#include <cstdio>
#include <memory>
#include <string>
#include <vector>

#include "absl/status/status.h"
#include "app/core/constants.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_tile.h"
#include "app/rom.h"

namespace yaze {
namespace app {
namespace zelda3 {

namespace {

void AddSPCReloc(music::SongSPCBlock *sbl, short addr) {
  sbl->relocs[sbl->relnum++] = addr;
  if (sbl->relnum == sbl->relsz) {
    sbl->relsz += 16;
    sbl->relocs = (unsigned short *)realloc(sbl->relocs, sbl->relsz << 1);
  }
}

}  // namespace

namespace music {
SongSPCBlock *Tracker::AllocSPCBlock(int len, int bank) {
  SongSPCBlock *sbl;
  if (!len) {
    printf("warning zero length block allocated");
  }
  if (ss_num == ss_size) {
    ss_size += 512;
    ssblt = (SongSPCBlock **)realloc(ssblt, ss_size << 2);
  }
  ssblt[ss_num] = sbl = (SongSPCBlock *)malloc(sizeof(SongSPCBlock));
  ss_num++;
  sbl->start = ss_next;
  sbl->len = len;
  sbl->buf = (uchar *)malloc(len);
  sbl->relocs = (ushort *)malloc(32);
  sbl->relsz = 16;
  sbl->relnum = 0;
  sbl->bank = bank & 7;
  sbl->flag = bank >> 3;
  ss_next += len;
  return sbl;
}

// =============================================================================

unsigned char *Tracker::GetSPCAddr(Rom &rom, unsigned short addr, short bank) {
  unsigned char *rom_ptr;
  unsigned short a;
  unsigned short b;
  spcbank = bank + 1;

again:
  rom_ptr = rom.data() + sbank_ofs[spcbank];

  for (;;) {
    a = *(unsigned short *)rom_ptr;

    if (!a) {
      if (spcbank) {
        spcbank = 0;

        goto again;
      } else
        return nullptr;
    }

    b = *(unsigned short *)(rom_ptr + 2);
    rom_ptr += 4;

    if (addr >= b && addr - b < a) {
      spclen = a;

      return rom_ptr + addr - b;
    }

    rom_ptr += a;
  }
}

// =============================================================================

short Tracker::AllocSPCCommand() {
  int i = m_free;
  int j;
  int k;
  SPCCommand *spc_command;
  if (i == -1) {
    j = m_size;
    m_size += 1024;
    spc_command = current_spc_command_ = (SPCCommand *)realloc(
        current_spc_command_, m_size * sizeof(SPCCommand));
    k = 1023;
    while (k--) spc_command[j].next = j + 1, j++;
    spc_command[j].next = -1;
    k = 1023;
    while (k--) spc_command[j].prev = j - 1, j--;
    spc_command[j].prev = -1;
    i = j;
  } else
    spc_command = current_spc_command_;
  m_free = spc_command[m_free].next;
  if (m_free != -1) spc_command[m_free].prev = -1;
  return i;
}

// =============================================================================

short Tracker::GetBlockTime(Rom &rom, short num, short prevtime) {
  SPCCommand *spc_command = current_spc_command_;
  SPCCommand *spc_command2;

  int i = -1;
  int j = 0;
  int k = 0;
  int l;
  int m = 0;
  int n = prevtime;
  l = num;

  if (l == -1) return 0;

  for (;;) {
    if (spc_command[l].flag & 4) {
      j = spc_command[l].tim;
      m = spc_command[l].tim2;
      k = 1;
    }

    if (!k) i = l;

    if (spc_command[l].flag & 1) n = spc_command[l].b1;

    l = spc_command[l].next;

    if (l == -1) {
      if (!k) {
        m = 0;
        j = 0;
      }

      break;
    }
  }

  if (i != -1)
    for (;;) {
      if (i == -1) {
        printf("Error");
        m_modf = 1;
        return 0;
      }
      spc_command2 = spc_command + i;
      if (spc_command2->cmd == 0xef) {
        k = *(short *)&(spc_command2->p1);
        if (k >= m_size) {
          printf("Invalid music address\n");
          m_modf = 1;
          return 0;
        }
        if (spc_command2->flag & 1) {
          j += GetBlockTime(rom, k, 0) * spc_command2->p3;
          if (ss_lasttime) {
            j += ss_lasttime * m;
            j += spc_command[k].tim2 * spc_command2->b1;
            j += (spc_command2->p3 - 1) * spc_command[k].tim2 * ss_lasttime;
          } else {
            j +=
                spc_command2->b1 * (m + spc_command[k].tim2 * spc_command2->p3);
          }
          m = 0;
        } else {
          j += GetBlockTime(rom, k, 0) * spc_command2->p3;
          j += ss_lasttime * m;
          if (ss_lasttime)
            j += (spc_command2->p3 - 1) * ss_lasttime * spc_command[k].tim2,
                m = spc_command[k].tim2;
          else
            m += spc_command[k].tim2 * spc_command2->p3;
        }
      } else {
        if (spc_command2->cmd < 0xe0) m++;
        if (spc_command2->flag & 1) {
          j += m * spc_command[i].b1;
          m = 0;
        }
      }
      spc_command2->tim = j;
      spc_command2->tim2 = m;
      spc_command2->flag |= 4;

      if (i == num) break;

      i = spc_command2->prev;
    }

  ss_lasttime = n;
  return spc_command[num].tim + prevtime * spc_command[num].tim2;
}

// =============================================================================

short Tracker::LoadSPCCommand(Rom &rom, unsigned short addr, short bank,
                              int t) {
  int b = 0;
  int c = 0;
  int d = 0;
  int e = 0;
  int f = 0;
  int g = 0;
  int h = 0;
  int i = 0;
  int l = 0;
  int m = 0;
  int n = 0;
  int o = 0;

  unsigned char j = 0;
  unsigned char k = 0;

  unsigned char *a = nullptr;

  SongRange *sr;
  SPCCommand *spc_command = current_spc_command_;
  SPCCommand *spc_command2;
  if (!addr) return -1;

  a = GetSPCAddr(rom, addr, bank);
  d = spcbank;
  if (!a) {
    printf("Address not found when loading track");
    return -1;
  }
  sr = song_range_;
  e = srnum;
  f = 0x10000;

  for (c = 0; c < e; c++) {
    if (sr[c].bank == d)
      if (sr[c].start > addr) {
        if (sr[c].start < f) f = sr[c].start;
        n = c;
      } else if (sr[c].end > addr) {
        for (f = sr[c].first; f != -1; f = spc_command[f].next) {
          if (spc_command[f].flag & 4)
            m = spc_command[f].tim, o = spc_command[f].tim2;
          if (spc_command[f].addr == addr) {
            spc_command[f].tim = m;
            spc_command[f].tim2 = o;
            lastsr = c;
            return f;
          }
          if (spc_command[f].flag & 1) k = spc_command[f].b1;
          if (spc_command[f].cmd < 0xca)
            if (k)
              m -= k;
            else
              o--;
        }
        printf("Misaligned music pointer");
        return -1;
      }
  }

  c = n;
  i = h = m_free;
  a -= addr;
  m = 0;
  k = 0;
  o = 0;

  for (g = addr; g < f;) {
    spc_command2 = spc_command + i;
    if (spc_command2->next == -1) {
      l = m_size;
      spc_command = current_spc_command_ = (SPCCommand *)realloc(
          spc_command, sizeof(SPCCommand) * (m_size += 1024));
      spc_command2 = spc_command + i;
      n = l + 1023;
      while (l < n) spc_command[l].next = l + 1, l++;
      spc_command[l].next = -1;
      n -= 1023;
      while (l > n) spc_command[l].prev = l - 1, l--;
      spc_command[l].prev = i;
      spc_command2->next = l;
    }
    spc_command2->addr = g;
    b = a[g];
    if (!b) break;
    if (m >= t && b != 0xf9) break;
    g++;
    j = 0;
    if (b < 128) {
      j = 1;
      k = spc_command2->b1 = b;
      b = a[g++];
      if (b < 128) j = 3, spc_command2->b2 = b, b = a[g++];
    }
    if (b < 0xe0)
      if (k)
        m += k;
      else
        o++;
    spc_command2->cmd = b;
    spc_command2->flag = j;
    if (b >= 0xe0) {
      b -= 0xe0;
      if (op_len[b]) spc_command2->p1 = a[g++];
      if (op_len[b] > 1) spc_command2->p2 = a[g++];
      if (op_len[b] > 2) spc_command2->p3 = a[g++];
      if (b == 15) {
        m_free = spc_command2->next;
        spc_command[spc_command2->next].prev = -1;
        l = LoadSPCCommand(rom, *(short *)(&(spc_command2->p1)), bank, t - m);
        spc_command = current_spc_command_;
        spc_command2 = spc_command + i;
        *(short *)(&(spc_command2->p1)) = l;
        spc_command2->next = m_free;
        spc_command[spc_command2->next].prev = i;
        GetBlockTime(rom, l, 0);
        if (spc_command[l].flag & 4)
          m +=
              (spc_command[l].tim + spc_command[l].tim2 * k) * spc_command2->p3;
        else {
          i = spc_command2->next;
          break;
        }
        if (song_range_[lastsr].endtime) k = song_range_[lastsr].endtime;
      }
    }
    i = spc_command2->next;
  }

  spc_command[h].tim = m;
  spc_command[h].tim2 = o;
  spc_command[h].flag |= 4;

  if (f == g && m < t) {
    l = spc_command[i].prev;
    lastsr = c;
    spc_command[sr[lastsr].first].prev = l;
    l = spc_command[l].next = sr[lastsr].first;
    if (spc_command[l].flag & 4)
      spc_command[h].tim = spc_command[l].tim + m + spc_command[l].tim2 * k,
      spc_command[h].flag |= 4;
    sr[lastsr].first = h;
    sr[lastsr].start = addr;
    sr[lastsr].inst++;
  } else {
    if (srsize == srnum)
      song_range_ =
          (SongRange *)realloc(song_range_, (srsize += 16) * sizeof(SongRange));

    lastsr = srnum;
    sr = song_range_ + (srnum++);
    sr->start = addr;
    sr->end = g;
    sr->first = h;
    sr->endtime = k;
    sr->inst = 1;
    sr->editor = 0;
    sr->bank = d;
    spc_command[spc_command[i].prev].next = -1;
  }

  spc_command[i].prev = -1;
  m_free = i;

  return h;
}

// =============================================================================

void Tracker::LoadSongs(Rom &rom) {
  unsigned char *b;
  unsigned char *c;
  unsigned char *d;
  short *e;

  Song song;
  Song song2;
  SongPart *sp;
  SPCCommand *spc_command;
  ZeldaWave *zelda_wave;

  int i;
  int j;
  int k;
  int l = 0;
  int m;
  int n;
  int o;
  int p;
  int q;
  int r;
  int t;
  int u;
  int range;
  int filter;

  spc_command = current_spc_command_ =
      (SPCCommand *)malloc(1024 * sizeof(SPCCommand));
  m_free = 0;
  m_size = 1024;
  srnum = 0;
  srsize = 0;
  song_range_ = 0;
  sp_mark = 0;
  b = rom.data();

  sbank_ofs[1] = (b[0x91c] << 15) + ((b[0x918] & 127) << 8) + b[0x914];
  sbank_ofs[3] = (b[0x93a] << 15) + ((b[0x936] & 127) << 8) + b[0x932];

  for (i = 0; i < 1024; i++) {
    spc_command[i].next = i + 1;
    spc_command[i].prev = i - 1;
  }

  // Init blank songs.
  for (i = 0; i < 128; i++) {
    Song new_song;
    songs.emplace_back(new_song);
  }

  spc_command[1023].next = -1;
  for (i = 0; i < 3; i++) {
    // Extract the song banks.
    b = GetSPCAddr(rom, 0xd000, i);
    for (j = 0;; j++) {
      if ((r = ((unsigned short *)b)[j]) >= 0xd000) {
        r = (r - 0xd000) >> 1;
        break;
      }
    }

    numsong[i] = r;
    for (j = 0; j < r; j++) {
      k = ((unsigned short *)b)[j];
      if (!k)
        songs[l].in_use = false;
      else {
        c = GetSPCAddr(rom, k, i);

        // Init the bank index we are current loading.
        if (!spcbank)
          m = 0;
        else
          m = l - j;

        for (; m < l; m++)
          if (songs[m].in_use && songs[m].addr == k) {
            (songs[l] = songs[m]).inst++;

            break;
          }

        if (m == l) {
          // create a new song (Song *)malloc(sizeof(Song));
          songs[l] = song;
          song.inst = 1;
          song.addr = k;
          song.flag = !spcbank;

          for (m = 0;; m++)
            if ((n = ((unsigned short *)c)[m]) < 256) break;

          if (n > 0) {
            song.flag |= 2;
            song.lopst = (((unsigned short *)c)[m + 1] - k) >> 1;
          }

          song.numparts = m;
          song.tbl = (SongPart **)malloc(4 * m);

          for (m = 0; m < song.numparts; m++) {
            k = ((unsigned short *)c)[m];
            d = GetSPCAddr(rom, k, i);
            if (!spcbank)
              n = 0;
            else
              n = l - j;

            for (; n < l; n++) {
              song2 = songs[n];
              if (song2.in_use)
                for (o = 0; o < song2.numparts; o++)
                  if (song2.tbl[o]->addr == k) {
                    (song.tbl[m] = song2.tbl[o])->inst++;
                    goto foundpart;
                  }
            }

            for (o = 0; o < m; o++)
              if (song.tbl[o]->addr == k) {
                (song.tbl[m] = song.tbl[o])->inst++;
                goto foundpart;
              }

            sp = song.tbl[m] = (SongPart *)malloc(sizeof(SongPart));
            sp->flag = !spcbank;
            sp->inst = 1;
            sp->addr = k;
            p = 50000;
            for (o = 0; o < 8; o++) {
              q = sp->tbl[o] =
                  LoadSPCCommand(rom, ((unsigned short *)d)[o], i, p);
              spc_command = current_spc_command_ + q;
              if ((spc_command->flag & 4) && spc_command->tim < p)
                p = spc_command->tim;
            }
          foundpart:;
          }
        }
      }
      l++;
    }
  }

  b = GetSPCAddr(rom, 0x800, 0);
  snddat1 = (char *)malloc(spclen);
  sndlen1 = spclen;
  memcpy(snddat1, b, spclen);

  b = GetSPCAddr(rom, 0x17c0, 0);
  snddat2 = (char *)malloc(spclen);
  sndlen2 = spclen;
  memcpy(snddat2, b, spclen);

  b = GetSPCAddr(rom, 0x3d00, 0);
  insts = (ZeldaInstrument *)malloc(spclen);
  memcpy(insts, b, spclen);
  numinst = spclen / 6;

  b = GetSPCAddr(rom, 0x3e00, 0);
  m_ofs = b - rom.data() + spclen;
  sndinsts = (ZeldaSfxInstrument *)malloc(spclen);
  memcpy(sndinsts, b, spclen);
  numsndinst = spclen / 9;

  b = GetSPCAddr(rom, 0x3c00, 0);
  zelda_wave = waves = (ZeldaWave *)malloc(sizeof(ZeldaWave) * (spclen >> 2));
  p = spclen >> 1;

  for (i = 0; i < p; i += 2) {
    j = ((unsigned short *)b)[i];

    if (j == 65535) break;

    for (k = 0; k < i; k += 2) {
      if (((unsigned short *)b)[k] == j) {
        zelda_wave->copy = (short)(k >> 1);
        goto foundwave;
      }
    }

    zelda_wave->copy = -1;

  foundwave:

    d = GetSPCAddr(rom, j, 0);
    e = (short *)malloc(2048);

    k = 0;
    l = 1024;
    u = t = 0;

    for (;;) {
      m = *(d++);

      range = (m >> 4) + 8;
      filter = (m & 12) >> 2;

      for (n = 0; n < 8; n++) {
        o = (*d) >> 4;

        if (o > 7) o -= 16;

        o <<= range;

        if (filter)
          o += (t * fil1[filter] >> fil2[filter]) -
               ((u & -256) * fil3[filter] >> 4);

        if (o > 0x7fffff) o = 0x7fffff;

        if (o < -0x800000) o = -0x800000;

        u = o;

        // \code             if(t>0x7fffff) t=0x7fffff;
        // \code              if(t < -0x800000) t=-0x800000;

        e[k++] = o >> 8;

        o = *(d++) & 15;

        if (o > 7) o -= 16;

        o <<= range;

        if (filter)
          o += (u * fil1[filter] >> fil2[filter]) -
               ((t & -256) * fil3[filter] >> 4);

        if (o > 0x7fffff) o = 0x7fffff;

        if (o < -0x800000) o = -0x800000;

        t = o;
        // \code             if(u>0x7fffff) u=0x7fffff;
        // \code             if(u < -0x800000) u= -0x800000;
        e[k++] = o >> 8;
      }

      if (m & 1) {
        zelda_wave->lflag = (m & 2) >> 1;
        break;
      }
      if (k == l) {
        l += 1024;
        e = (short *)realloc(e, l << 1);
      }
    }

    e = zelda_wave->buf = (short *)realloc(e, (k + 1) << 1);

    zelda_wave->lopst = (((unsigned short *)b)[i + 1] - j) * 16 / 9;

    if (zelda_wave->lflag)
      e[k] = e[zelda_wave->lopst];
    else
      e[k] = 0;

    zelda_wave->end = k;

    zelda_wave++;
  }

  numwave = i >> 1;
  m_loaded = 1;
  w_modf = 0;
}

short Tracker::SaveSPCCommand(Rom &rom, short num, short songtime,
                              short endtr) {
  SPCCommand *spc_command = current_spc_command_;
  SPCCommand *spc_command2;
  SongRange *sr = song_range_;
  SongSPCBlock *sbl;

  text_buf_ty buf;

  unsigned char *b;
  int i = num;
  int j = 0;
  int k = 0;
  int l = 0;
  int m = 0;
  int n = 0;
  int o = 0;
  int p = 0;

  if (i == -1) return 0;

  if (i >= m_size) {
    printf("Error.\n");
    m_modf = 1;
    return 0;
  }

  if (spc_command[i].flag & 8) return spc_command[i].addr;

  for (;;) {
    j = spc_command[i].prev;
    if (j == -1) break;
    i = j;
  }

  for (j = 0; j < srnum; j++) {
    if (sr[j].first == i) {
      l = GetBlockTime(rom, i, 0);
      m = i;
      for (;;) {
        if (m == -1) break;
        k++;
        spc_command2 = spc_command + m;
        if (spc_command2->flag & 1) k++, n = spc_command2->b1;
        if (spc_command2->flag & 2) k++;
        if (spc_command2->cmd >= 0xe0) k += op_len[spc_command2->cmd - 0xe0];
        m = spc_command2->next;
      }
      songtime -= l;

      if (songtime > 0) {
        l = (songtime + 126) / 127;
        if (songtime % l) l += 2;
        l++;
        if (n && !songtime % n) {
          p = songtime / n;
          if (p < l) l = p;
        } else
          p = -1;
        k += l;
      }
      k++;
      sbl = AllocSPCBlock(k, sr[j].bank | ((!endtr) << 3) | 16);
      b = sbl->buf;

      for (;;) {
        if (i == -1) break;
        spc_command2 = spc_command + i;
        spc_command2->addr = b - sbl->buf + sbl->start;
        spc_command2->flag |= 8;
        if (spc_command2->flag & 1) *(b++) = spc_command2->b1;
        if (spc_command2->flag & 2) *(b++) = spc_command2->b2;
        *(b++) = spc_command2->cmd;
        if (spc_command2->cmd >= 0xe0) {
          o = op_len[spc_command2->cmd - 0xe0];
          if (spc_command2->cmd == 0xef) {
            *(short *)b =
                SaveSPCCommand(rom, *(short *)&(spc_command2->p1), 0, 1);
            if (b) AddSPCReloc(sbl, b - sbl->buf);
            b[2] = spc_command2->p3;
            b += 3;
          } else {
            if (o) *(b++) = spc_command2->p1;
            if (o > 1) *(b++) = spc_command2->p2;
            if (o > 2) *(b++) = spc_command2->p3;
          }
        }
        i = spc_command2->next;
      }

      if (songtime > 0) {
        if (l != p) {
          l = (songtime + 126) / 127;
          if (songtime % l)
            n = 127;
          else
            n = songtime / l;
          *(b++) = n;
        }

        for (; songtime >= n; songtime -= n) *(b++) = 0xc9;

        if (songtime) {
          *(b++) = (uint8_t)songtime;
          *(b++) = 0xc9;
        }
      }
      *(b++) = 0;
      return spc_command[num].addr;
    }
  }

  printf("Address %04X not found", num);

  printf("Error");

  m_modf = 1;

  return 0;
}

// =============================================================================

int Tracker::WriteSPCData(Rom &rom, void *buf, int len, int addr, int spc,
                          int limit) {
  unsigned char *rom_data = rom.data();

  if (!len) return addr;

  if (((addr + len + 4) & 0x7fff) > 0x7ffb) {
    if (addr + 5 > limit) goto error;
    *(int *)(rom_data + addr) = 0x00010140;
    rom_data[addr + 4] = 0xff;
    addr += 5;
  }

  if (addr + len + 4 > limit) {
  error:
    printf("Not enough space for sound data");
    m_modf = 1;
    return 0xc8000;
  }

  *(short *)(rom_data + addr) = len;
  *(short *)(rom_data + addr + 2) = spc;

  memcpy(rom_data + addr + 4, buf, len);

  return addr + len + 4;
}

// =============================================================================

void Tracker::SaveSongs(Rom &rom) {
  int i;
  int j;
  int k;
  int l = 0;
  int m;
  int n;
  int o;
  int p;
  int q;
  int r;
  int t;
  int u;
  int v;
  int w;
  int a;
  int e;
  int f;
  int g;
  unsigned short bank_next[4];
  unsigned short bank_lwr[4];
  short *c;
  short *d;
  unsigned char *rom_data;
  unsigned char *b;

  Song song;

  SPCCommand *spc_command;

  SongPart *sp;

  SongSPCBlock *stbl;
  SongSPCBlock *sptbl;
  SongSPCBlock *trtbl;
  SongSPCBlock *pstbl;

  ZeldaWave *zelda_wave;
  ZeldaWave *zelda_wave2;

  ZeldaInstrument *zi;

  SampleEdit *sed;

  short wtbl[128];
  short x[16];
  short y[18];
  unsigned char z[64];

  ss_num = 0;
  ss_size = 512;
  ss_next = 0;

  // if the music has not been modified, return.
  if (!(m_modf)) return;

  ssblt = (SongSPCBlock **)malloc(512 * sizeof(SongSPCBlock));

  // set it so the music has not been modified. (reset the status)
  m_modf = 0;
  rom_data = rom.data();

  // SetCursor(wait_cursor);

  for (i = 0; i < 3; i++) {
    k = numsong[i];

    for (j = 0; j < k; j++) {
      song = songs[l++];

      if (!song.in_use) continue;

      song.flag &= -5;

      for (m = 0; m < song.numparts; m++) {
        sp = song.tbl[m];
        sp->flag &= -3;
      }
    }
  }

  j = m_size;
  spc_command = current_spc_command_;

  for (i = 0; i < j; i++) {
    spc_command->flag &= -13;
    spc_command++;
  }

  l = 0;

  for (i = 0; i < 3; i++) {
    k = numsong[i];
    stbl = AllocSPCBlock(k << 1, i + 1);

    for (j = 0; j < k; j++) {
      song = songs[l++];

      if (!song.in_use) {
        ((short *)(stbl->buf))[j] = 0;

        continue;
      }

      if (song.flag & 4) goto alreadysaved;

      sptbl = AllocSPCBlock(((song.numparts + 1) << 1) + (song.flag & 2),
                            (song.flag & 1) ? 0 : (i + 1));

      for (m = 0; m < song.numparts; m++) {
        sp = song.tbl[m];

        if (sp->flag & 2) goto spsaved;

        trtbl = AllocSPCBlock(16, (sp->flag & 1) ? 0 : (i + 1));

        p = 0;

        for (n = 0; n < 8; n++) {
          o = GetBlockTime(rom, sp->tbl[n], 0);

          if (o > p) p = o;
        }

        q = 1;

        for (n = 0; n < 8; n++) {
          core::stle16b_i(trtbl->buf, n, SaveSPCCommand(rom, sp->tbl[n], p, q));

          if (core::ldle16b_i(trtbl->buf, n)) AddSPCReloc(trtbl, n << 1), q = 0;
        }

        sp->addr = trtbl->start;
        sp->flag |= 2;
      spsaved:
        ((short *)(sptbl->buf))[m] = sp->addr;

        AddSPCReloc(sptbl, m << 1);
      }

      if (song.flag & 2) {
        ((short *)(sptbl->buf))[m++] = 255;
        ((short *)(sptbl->buf))[m] = sptbl->start + (song.lopst << 1);

        AddSPCReloc(sptbl, m << 1);
      } else
        ((short *)(sptbl->buf))[m++] = 0;

      song.addr = sptbl->start;
      song.flag |= 4;
    alreadysaved:
      ((short *)(stbl->buf))[j] = song.addr;

      AddSPCReloc(stbl, j << 1);
    }
  }

  if (w_modf) {
    b = (uint8_t *)malloc(0xc000);
    j = 0;

    zelda_wave = waves;

    // if (mbanks[3])
    //   sed = (SampleEdit *)GetWindowLongPtr(mbanks[3], GWLP_USERDATA);
    // else
    //   sed = 0;

    for (i = 0; i < numwave; i++, zelda_wave++) {
      if (zelda_wave->copy != -1) continue;

      wtbl[i << 1] = j + 0x4000;

      if (zelda_wave->lflag) {
        l = zelda_wave->end - zelda_wave->lopst;

        if (l & 15) {
          k = (l << 15) / ((l + 15) & -16);
          p = (zelda_wave->end << 15) / k;
          c = (short *)malloc(p << 1);
          n = 0;
          d = zelda_wave->buf;

          for (m = 0;;) {
            c[n++] = (d[m >> 15] * ((m & 32767) ^ 32767) +
                      d[(m >> 15) + 1] * (m & 32767)) /
                     32767;

            m += k;

            if (n >= p) break;
          }

          zelda_wave->lopst = (zelda_wave->lopst << 15) / k;
          zelda_wave->end = p;
          zelda_wave->buf =
              (short *)realloc(zelda_wave->buf, (zelda_wave->end + 1) << 1);
          memcpy(zelda_wave->buf, c, zelda_wave->end << 1);
          free(c);
          zelda_wave->buf[zelda_wave->end] = zelda_wave->buf[zelda_wave->lopst];
          zelda_wave2 = waves;

          for (m = 0; m < numwave; m++, zelda_wave2++)
            if (zelda_wave2->copy == i)
              zelda_wave2->lopst = zelda_wave2->lopst << 15 / k;

          zi = insts;

          for (m = 0; m < numinst; m++) {
            n = zi->samp;

            if (n >= numwave) continue;

            if (n == i || waves[n].copy == i) {
              o = (zi->multhi << 8) + zi->multlo;
              o = (o << 15) / k;
              zi->multlo = o;
              zi->multhi = o >> 8;

              if (sed && sed->editinst == m) {
                sed->init = 1;
                // SetDlgItemInt(sed->dlg, 3014, o, 0);
                sed->init = 0;
              }
            }

            zi++;
          }

          // Modifywaves(rom, i);
        }
      }

      k = (-zelda_wave->end) & 15;
      d = zelda_wave->buf;
      n = 0;
      wtbl[(i << 1) + 1] = ((zelda_wave->lopst + k) >> 4) * 9 + wtbl[i << 1];
      y[0] = y[1] = 0;
      u = 4;

      for (;;) {
        for (o = 0; o < 16; o++) {
          if (k)
            k--, x[o] = 0;
          else
            x[o] = d[n++];
        }
        p = 0x7fffffff;
        a = 0;

        for (t = 0; t < 4; t++) {
          r = 0;
          for (o = 0; o < 16; o++) {
            l = x[o];
            y[o + 2] = l;
            l += (y[o] * fil3[t] >> 4) - (y[o + 1] * fil1[t] >> fil2[t]);
            if (l > r)
              r = l;
            else if (-l > r)
              r = -l;
          }
          r <<= 1;
          if (t)
            m = 14;
          else
            m = 15;
          for (q = 0; q < 12; q++, m += m)
            if (m >= r) break;
        tryagain:
          if (q && (q < 12 || m == r))
            v = (1 << (q - 1)) - 1;
          else
            v = 0;
          m = 0;
          for (o = 0; o < 16; o++) {
            l = (y[o + 1] * fil1[t] >> fil2[t]) - (y[o] * fil3[t] >> 4);
            w = x[o];
            r = (w - l + v) >> q;
            if ((r + 8) & 0xfff0) {
              q++;
              a -= o;
              goto tryagain;
            }
            z[a++] = r;
            l = (r << q) + l;
            y[o + 2] = l;
            l -= w;
            m += l * l;
          }
          if (u == 4) {
            u = 0, e = q, f = y[16], g = y[17];
            break;
          }
          if (m < p) p = m, u = t, e = q, f = y[16], g = y[17];
        }
        m = (e << 4) | (u << 2);
        if (n == zelda_wave->end) m |= 1;
        if (zelda_wave->lflag) m |= 2;
        b[j++] = m;
        m = 0;
        a = u << 4;
        for (o = 0; o < 16; o++) {
          m |= z[a++] & 15;
          if (o & 1)
            b[j++] = m, m = 0;
          else
            m <<= 4;
        }
        if (n == zelda_wave->end) break;
        y[0] = f;
        y[1] = g;
      }
    }

    // if (sed) {
    //   SetDlgItemInt(sed->dlg, ID_Samp_SampleLengthEdit, sed->zelda_wave->end,
    //   0); SetDlgItemInt(sed->dlg, ID_Samp_LoopPointEdit,
    //   sed->zelda_wave->lopst, 0);

    //   InvalidateRect(GetDlgItem(sed->dlg, ID_Samp_Display), 0, 1);
    // }

    zelda_wave = waves;

    for (i = 0; i < numwave; i++, zelda_wave++) {
      if (zelda_wave->copy != -1) {
        wtbl[i << 1] = wtbl[zelda_wave->copy << 1];
        wtbl[(i << 1) + 1] = (zelda_wave->lopst >> 4) * 9 + wtbl[i << 1];
      }
    }

    m = WriteSPCData(rom, wtbl, numwave << 2, 0xc8000, 0x3c00, 0xd74fc);
    m = WriteSPCData(rom, b, j, m, 0x4000, 0xd74fc);

    free(b);
    m = WriteSPCData(rom, insts, numinst * 6, m, 0x3d00, 0xd74fc);
    m = WriteSPCData(rom, snddat1, sndlen1, m, 0x800, 0xd74fc);
    m = WriteSPCData(rom, snddat2, sndlen2, m, 0x17c0, 0xd74fc);
    m = WriteSPCData(rom, sndinsts, numsndinst * 9, m, 0x3e00, 0xd74fc);
    m_ofs = m;
  } else {
    m = m_ofs;
  }

  bank_next[0] = 0x2880;
  bank_next[1] = 0xd000;
  bank_next[2] = 0xd000;
  bank_next[3] = 0xd000;
  bank_lwr[0] = 0x2880;

  for (k = 0; k < 4; k++) {
    pstbl = 0;
    for (i = 0; i < ss_num; i++) {
      stbl = ssblt[i];
      if (stbl->bank != k) continue;
      j = bank_next[k];
      if (j + stbl->len > 0xffc0) {
        if (k == 3)
          j = 0x2880;
        else
          j = bank_next[0];
        bank_lwr[k] = j;
        pstbl = 0;
      }
      if (j + stbl->len > 0x3c00 && j < 0xd000) {
        printf("Not enough space for music bank %d", k);
        m_modf = 1;
        return;
      }
      if (pstbl && (pstbl->flag & 1) && (stbl->flag & 2)) j--, pstbl->len--;
      stbl->addr = j;
      pstbl = stbl;
      bank_next[k] = j + stbl->len;
    }
  }
  for (i = 0; i < ss_num; i++) {
    stbl = ssblt[i];
    for (j = stbl->relnum - 1; j >= 0; j--) {
      k = *(unsigned short *)(stbl->buf + stbl->relocs[j]);
      for (l = 0; l < ss_num; l++) {
        sptbl = ssblt[l];
        if (sptbl->start <= k && sptbl->len > k - sptbl->start) goto noerror;
      }
      printf("Internal error");
      m_modf = 1;
      return;
    noerror:

      if (((!sptbl->bank) && stbl->bank < 3) || (sptbl->bank == stbl->bank)) {
        *(unsigned short *)(stbl->buf + stbl->relocs[j]) =
            sptbl->addr + k - sptbl->start;
      } else {
        printf("An address outside the bank was referenced.\n");
        m_modf = 1;
        return;
      }
    }
  }
  l = m;
  for (k = 0; k < 4; k++) {
    switch (k) {
      case 1:
        rom[0x914] = l;
        rom[0x918] = (l >> 8) | 128;
        rom[0x91c] = l >> 15;
        break;
      case 2:
        l = 0xd8000;
        break;
      case 3:
        l = m;
        rom[0x932] = l;
        rom[0x936] = (l >> 8) | 128;
        rom[0x93a] = l >> 15;
        break;
    }
    for (o = 0; o < 2; o++) {
      n = l + 4;
      for (i = 0; i < ss_num; i++) {
        stbl = ssblt[i];
        if (!stbl) continue;
        if ((stbl->addr < 0xd000) ^ o) continue;
        if (stbl->bank != k) continue;
        if (n + stbl->len > ((k == 2) ? 0xdb7fc : 0xd74fc)) {
          printf("Not enough space for music");
          m_modf = 1;
          return;
        }
        memcpy(rom.data() + n, stbl->buf, stbl->len);
        n += stbl->len;
        free(stbl->relocs);
        free(stbl->buf);
        free(stbl);
        ssblt[i] = 0;
      }
      if (n > l + 4) {
        *(short *)(rom.data() + l) = n - l - 4;
        *(short *)(rom.data() + l + 2) = o ? bank_lwr[k] : 0xd000;
        l = n;
      }
    }
    *(short *)(rom.data() + l) = 0;
    *(short *)(rom.data() + l + 2) = 0x800;
    if (k == 1) m = l + 4;
  }
  free(ssblt);
}

// =============================================================================

void Tracker::EditTrack(Rom &rom, short i) {
  int j, k, l;
  SongRange *sr = song_range_;
  SPCCommand *spc_command;

  text_buf_ty buf;

  // -----------------------------

  k = srnum;

  spc_command = current_spc_command_;

  if (i == -1) return;

  if (i >= m_size) {
    printf("Invalid address: %04X", i);
    goto error;
  }

  for (;;) {
    if ((j = spc_command[i].prev) != -1)
      i = j;
    else
      break;
  }

  for (l = 0; l < k; l++)
    if (sr->first == i)
      break;
    else
      sr++;

  if (l == k) {
    printf("Not found: %04X", i);
  error:
    printf("Error");
    return;
  }

  // if (sr->editor)
  //   HM_MDI_ActivateChild(clientwnd, sr->editor);
  // else
  //   Editwin(rom, "TRACKEDIT", "Song editor", l + (i << 16),
  //   sizeof(TRACKEDIT));
}

// CRITICAL_SECTION cs_song;
//  =============================================================================

void Tracker::NewSR(Rom &rom, int bank) {
  SPCCommand *spc_command;
  SongRange *sr;

  if (srnum == srsize) {
    srsize += 16;
    song_range_ = (SongRange *)realloc(song_range_, srsize * sizeof(SongRange));
  }

  sr = song_range_ + srnum;
  srnum++;
  sr->first = AllocSPCCommand();
  sr->bank = bank;
  sr->editor = 0;
  spc_command = current_spc_command_ + sr->first;
  spc_command->prev = -1;
  spc_command->next = -1;
  spc_command->cmd = 128;
  spc_command->flag = 0;
  EditTrack(rom, sr->first);
}

}  // namespace music
}  // namespace zelda3
}  // namespace app
}  // namespace yaze
