local createVersion = require('/apis/libversion');

local command = ...;

local function printUsage()
  print('music usage:');
  print();
  print('\t\t\tmusic');
  print('\t\t\tmusic version');
  print('\t\t\tmusic help');
end

if command == 'version' or command == '-version' or command == '--version' then
  print('v' .. createVersion().forSelf());
  return;
end

if command == 'help' or command == '-help' or command == '--help' then
  printUsage();
  return;
end

if command ~= nil and command ~= '' then
  printUsage();
  return;
end

local createMusic = require('/apis/libmusic');
local music = createMusic();

local tracks = music.listTracks();
if #tracks == 0 then
  print('No music found in /data/musics');
  return;
end

if not peripheral.find('speaker') then
  print('No speaker attached');
  return;
end

local createEventLoop = require('/apis/eventloop');
local createTui = require('/apis/libtui');

local eventloop = createEventLoop();
local ui = createTui(eventloop);

local Text = ui.Text;
local Button = ui.Button;
local Box = ui.Box;
local List = ui.List;

local player = music.createPlayer(eventloop);

-- Rows of UI chrome around the track list (header, now-playing, controls,
-- footer, plus the track box border). Used to size the scroll window.
local LIST_CHROME = 6;

local selectedIndex = 1; -- list cursor
local scrollOffset = 0;  -- index of the first visible row - 1
local playingIndex = nil; -- track currently loaded in the player
local autoplay = false;

local function clampSelection()
  if selectedIndex < 1 then
    selectedIndex = 1;
  elseif selectedIndex > #tracks then
    selectedIndex = #tracks;
  end
end

-- Keeps the selected row inside the visible window.
local function ensureVisible()
  local _, height = term.getSize();
  local visible = math.max(1, height - LIST_CHROME);
  if selectedIndex <= scrollOffset then
    scrollOffset = selectedIndex - 1;
  elseif selectedIndex > scrollOffset + visible then
    scrollOffset = selectedIndex - visible;
  end
end

local function playIndex(index)
  if index < 1 or index > #tracks then
    return;
  end
  playingIndex = index;
  selectedIndex = index;
  player.play(tracks[index].path);
  ensureVisible();
  ui.rerender();
end

local function nextIndex()
  return (playingIndex or selectedIndex) % #tracks + 1;
end

local function previousIndex()
  return ((playingIndex or selectedIndex) - 2) % #tracks + 1;
end

player.onFinish(function()
  if autoplay then
    playIndex(nextIndex());
  else
    playingIndex = nil;
  end
  ui.rerender();
end);

local function Header()
  return Box({
    direction = 'row',
    bgColor = colors.gray,
    children = {
      Text('Music', {
        flex = 1,
        color = colors.white,
        bgColor = colors.gray,
      }),
      Button('X', {
        color = colors.white,
        bgColor = colors.red,
        onClick = function(tui)
          tui.exitUI('quit');
        end,
      }),
    },
  });
end

local function TrackRow(index)
  local track = tracks[index];
  local isCursor = index == selectedIndex;
  local isPlaying = index == playingIndex;
  local label = (isPlaying and '> ' or '  ') .. track.name;
  return Button(label, {
    color = isCursor and colors.black or colors.white,
    bgColor = isCursor and colors.white or colors.black,
    onClick = function()
      playIndex(index);
    end,
  });
end

local function TrackList()
  local _, height = term.getSize();
  local visible = math.max(1, height - LIST_CHROME);
  local items = {};
  for offset = 1, visible do
    local index = scrollOffset + offset;
    if index > #tracks then
      break;
    end
    items[#items + 1] = TrackRow(index);
  end
  return List({
    padding = 1,
    children = items,
  });
end

local function statusLabel()
  local s = player.getStatus();
  if s == 'playing' then return 'Playing'; end
  if s == 'paused' then return 'Paused'; end
  return 'Stopped';
end

local function NowPlaying()
  local name = playingIndex and tracks[playingIndex].name or '(none)';
  return Box({
    direction = 'row',
    children = {
      Text(statusLabel() .. ': ' .. name, { flex = 1 }),
    },
  });
end

local function Controls()
  return Box({
    direction = 'row',
    gap = 1,
    children = {
      Button('|<', {
        onClick = function()
          playIndex(previousIndex());
        end,
      }),
      Button(player.getStatus() == 'playing' and 'Pause' or 'Play', {
        onClick = function()
          if player.getStatus() == 'stopped' then
            playIndex(selectedIndex);
          else
            player.togglePause();
            ui.rerender();
          end
        end,
      }),
      Button('Stop', {
        onClick = function()
          player.stop();
          playingIndex = nil;
          ui.rerender();
        end,
      }),
      Button('>|', {
        onClick = function()
          playIndex(nextIndex());
        end,
      }),
      Button('Autoplay: ' .. (autoplay and 'on' or 'off'), {
        flex = 1,
        color = autoplay and colors.black or colors.white,
        bgColor = autoplay and colors.lime or colors.black,
        onClick = function()
          autoplay = not autoplay;
          ui.rerender();
        end,
      }),
    },
  });
end

local function Footer()
  return Text(
    'up/down select  enter play  space pause  s stop  n/p next/prev  q quit',
    { color = colors.lightGray }
  );
end

local function App()
  return Box({
    direction = 'column',
    children = {
      Header(),
      Box({
        flex = 1,
        border = true,
        title = 'Tracks',
        children = {
          TrackList(),
        },
      }),
      NowPlaying(),
      Controls(),
      Footer(),
    },
  });
end

eventloop.register('key', function(key)
  if key == keys.up then
    selectedIndex = selectedIndex - 1;
    clampSelection();
    ensureVisible();
    ui.rerender();
  elseif key == keys.down then
    selectedIndex = selectedIndex + 1;
    clampSelection();
    ensureVisible();
    ui.rerender();
  elseif key == keys.enter then
    playIndex(selectedIndex);
  elseif key == keys.space then
    if player.getStatus() == 'stopped' then
      playIndex(selectedIndex);
    else
      player.togglePause();
      ui.rerender();
    end
  elseif key == keys.s then
    player.stop();
    playingIndex = nil;
    ui.rerender();
  elseif key == keys.n then
    playIndex(nextIndex());
  elseif key == keys.p then
    playIndex(previousIndex());
  elseif key == keys.q then
    ui.exitUI('quit');
  end
end);

ui.render(App);

player.stop();
