/* ADCD - A Diminutive CD player for Linux/GNU
   Copyright (C) 2004-2026 Antonio Diaz Diaz.

   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, see <http://www.gnu.org/licenses/>.
*/

#include <algorithm>
#include <cctype>
#include <csignal>
#include <cstdlib>
#include <ctime>
#include <vector>
#include <unistd.h>
#include <ncurses.h>

#include "msf_time.h"
#include "cd.h"
#include "player.h"


namespace Player {

enum { maxsize = 50,		// maximun number of tracks in playlist
       pl_row = 8,		// screen line where playlist is shown
       help_row = 15 };		// screen line where help is shown
CD * cd_ptr;


void dummy_endwin() { endwin(); }

void initialize_ncurses()
  {
  std::atexit( dummy_endwin );
  initscr();			// initializes curses data structures
  cbreak();			// read from keyboard a char at a time
  keypad( stdscr, true );	// enables single value for function keys
  nodelay( stdscr, false );	// forces getch() to wait for key
  noecho();			// disables echoing of getch()
  nonl();			// disables CR LF translation
  scrollok( stdscr, false );	// disables automatic scrolling
  }


int kbhit()
  {
  nodelay( stdscr, true );
  int c = wgetch( stdscr );
  nodelay( stdscr, false );
  if( c == ERR ) return 0;
  else { ungetch( c ); return c; }
  }


void show_online_help( const bool toggle, const bool play )
  {
  static bool help = false;

  if( toggle ) help = !help;
  for( int i = 0; i < 5; ++i )
    { wmove( stdscr, help_row + i, 0 ); wclrtoeol( stdscr ); }
  if( help )
    {
    if( play )
      {
      mvaddstr( help_row + 0, 0, "1 - 9, 0, F1 - F12         Play track 1 - 22");
      mvaddstr( help_row + 1, 0, "Up / Pg Up / Home / Ins    Seek backward 10 sec / 30 sec / 1 min / 5 min");
      mvaddstr( help_row + 2, 0, "Dn / Pg Dn / End  / Del    Seek forward  10 sec / 30 sec / 1 min / 5 min");
      mvaddstr( help_row + 3, 0, "R                          Generate random playlist");
      }
    else
      {
      mvaddstr( help_row + 0, 0, "1 - 9, 0, F1 - F12         Insert track 1 - 22");
      mvaddstr( help_row + 1, 0, "Arrow keys                 Move the cursor");
      mvaddstr( help_row + 2, 0, "+ - * /                    Increase / decrease track by 1 / 10");
      mvaddstr( help_row + 3, 0, "Backspace / Del            Delete track preceding / at cursor");
      mvaddstr( help_row + 4, 0, "Enter / Q                  Store / Discard playlist and exit edit mode");
      }
    }
  }


void show_panel()
  {
  mvaddstr( 0, 0, "[P] Play    [O] Open        Status :" );
  mvaddstr( 1, 0, "[U] Pause   [C] Close   [L] Loop   :" );
  mvaddstr( 2, 0, "[S] Stop    [Q] Quit    [M] Mode   :" );

  mvaddstr( 4, 0, "[-/] Down   [+*] Up         Volume :" );
  mvaddstr( 5, 0, "[<-] Prev   [->] Next       Track  :" );
  mvaddstr( 6, 0, "[H] Help                [T] Time   :" );
  }


void show_playlist( const std::vector< int > * pl, int index = -1 )
  {
  const int size = ( pl ? pl->size() : 0 );
  for( int i = 0; i < size; ++i )
    {
    if( i == index ) attrset( A_REVERSE );
    mvprintw( pl_row + 1 + (i / 10), 3 * (i % 10), "%2d", (*pl)[i] );
    if( i == index ) attrset( A_NORMAL );
    addch(' ');
    }
  for( int i = size; i < maxsize; ++i )
    mvaddstr( pl_row + 1 + (i / 10), 3 * (i % 10), "   " );
  }


CD::Time_mode time_mode( bool next = false )
  {
  static CD::Time_mode mode = CD::relative;
  if( next ) switch( mode )
    {
    case CD::relative: mode = CD::rem_rel;  break;
    case CD::rem_rel : mode = CD::absolute; break;
    case CD::absolute: mode = CD::rem_abs;  break;
    case CD::rem_abs : mode = CD::relative; break;
    }
  return mode;
  }


void show_data( const CD & cd )
  {
  const int track = cd.track();
  mvaddstr( 1, 37, cd.loop_name() );
  mvaddstr( 2, 37, cd.linear() ? "Linear  " : "Playlist" );
  mvprintw( 4, 37, "%3d ", cd.volume() );
  mvprintw( 5, 37, "%2d / %d  ", track, cd.last_track() );
  CD::Time_mode tm = time_mode();
  Msf_time msf = cd.time( tm ), total;
  const char * s;
  if( tm == CD::relative || tm == CD::rem_rel )
    { total = cd.time_track( track ); s = "   "; }
  else { total = cd.time_disc(); s = "(d)"; }
  char c = ( tm == CD::rem_rel || tm == CD::rem_abs ) ? '-' : ' ';
  mvprintw( 6, 32, "%3s:%c%2d:%02d / %d:%02d  ",
            s, c, msf.minute(), msf.second(), total.minute(), total.second() );
  if( cd.linear() ) show_playlist( 0 );
  else show_playlist( &cd.playlist(), cd.index() );
  mvaddstr( 0, 37, cd.status_name() ); wclrtoeol( stdscr );
  wrefresh( stdscr );
  }


void edit_playlist( CD & cd )
  {
  static unsigned i = 0;
  std::vector< int > temp = cd.playlist();
  if( i > temp.size() ) i = temp.size();

  while( true )
    {
    show_playlist( &temp );
    wmove( stdscr, pl_row + 1 + (i / 10), (3 * (i % 10)) + 1 );
    int key = std::toupper( wgetch( stdscr ) );
    if( key == '\r' ) { cd.playlist( temp ); break; }
    if( key == 'Q' ) break;
    int track = cd.first_track() - 1;
    if( std::isdigit( key ) ) track = ( key != '0' ) ? key - '0' : 10;
    else if( key >= KEY_F(1) && key <= KEY_F(12) ) track = 10 + key - KEY_F(0);
    const unsigned tsize = temp.size();
    if( track >= cd.first_track() && track <= cd.last_track() )
      {
      if( tsize >= maxsize ) temp[i] = track;
      else { temp.insert( temp.begin() + i, track ); if( i < maxsize - 1 ) ++i; }
      }
    else switch( key )
      {
      case '-': if( i < tsize && temp[i] > cd.first_track() ) --temp[i]; break;
      case '+': if( i < tsize && temp[i] < cd.last_track() ) ++temp[i]; break;
      case '/': if( i < tsize ) temp[i] = std::max( temp[i] - 10, cd.first_track() );
                break;
      case '*': if( i < tsize ) temp[i] = std::min( temp[i] + 10, cd.last_track() );
                break;
      case 'H': show_online_help( true, false ); break;
      case KEY_HOME: i = 0; break;
      case KEY_END: i = std::min( tsize, maxsize - 1U ); break;
      case KEY_UP: if( i >= 10 ) i -= 10; break;
      case KEY_DOWN: if( i + 9 < tsize && i + 9 < maxsize - 1 ) i += 10; break;
      case KEY_LEFT: if( i > 0 ) --i; break;
      case KEY_RIGHT: if( i < tsize && i < maxsize - 1 ) ++i; break;
      case KEY_DC: if( i < tsize ) temp.erase( temp.begin() + i ); break;
      case KEY_BACKSPACE: if( i > 0 ) temp.erase( temp.begin() + --i ); break;
      }
    }
  }


void change_mode( CD & cd )
  {
  if( !cd.linear() ) { cd.linear( true ); return; }
  show_online_help( false, false );
  mvaddstr( pl_row, 0, "editing list" );
  edit_playlist( cd ); if( cd.playlist().size() > 0 ) cd.linear( false );
  mvaddstr( pl_row, 0, "              " );
  show_online_help( false, true );
  }


void random_playlist( CD & cd )
  {
  if( !cd.linear() )
    {
    mvaddstr( pl_row, 0, "overwrite list ?" );
    int ch = std::toupper( wgetch( stdscr ) );
    mvaddstr( pl_row, 0, "                  " );
    if( ch != 'Y' ) return;
    }
  std::vector< int > temp1, temp2;
  for( int i = cd.first_track(); i <= cd.last_track() && temp1.size() < maxsize; ++i )
    temp1.push_back( i );
  while( temp1.size() )
    {
    int i = std::rand() % temp1.size();
    temp2.push_back( temp1[i] ); temp1.erase( temp1.begin() + i );
    }
  cd.playlist( temp2 );
  if( cd.playlist().size() > 0 ) cd.linear( false );
  }


extern "C" void poll_cd_status( int )		// polls the cd drive
  {
  if( cd_ptr->read_status( false ) ) show_data( *cd_ptr );
  alarm( 1 );
  }


void restore_signals()
  {
  std::signal( SIGALRM, SIG_DFL );
  }


void set_signals()
  {
  std::atexit( restore_signals );
  std::signal( SIGALRM, poll_cd_status );
  }

} // end namespace Player


void Player::main_loop( CD & cd )
  {
  cd_ptr = &cd;
  std::srand( std::time( 0 ) );
  initialize_ncurses();
  show_panel(); show_data( cd );
  set_signals();

  int key = 0;
  while( key != 'Q' )
    {
    alarm( 1 );					// start polling cd drive
    while( kbhit() ) wgetch( stdscr );		// clear input buffer
    do key = wgetch( stdscr ); while( key < 0 );
    if( std::islower( key ) ) key = std::toupper( key );
    alarm( 0 );					// stop polling cd drive
    switch( key )
      {
      case '-': cd.volume( cd.volume() - 1 ); break;
      case '+': cd.volume( cd.volume() + 1 ); break;
      case '/': cd.volume( cd.volume() - 10 ); break;
      case '*': cd.volume( cd.volume() + 10 ); break;
      case 'C': cd.close(); break;
      case 'H': show_online_help( true, true ); break;
      case 'L': cd.loop( cd.loop() + 1 ); break;
      case 'M': if( cd.tracks() ) change_mode( cd ); break;
      case 'O': cd.open(); break;
      case 'P': cd.play(); break;
      case 'R': if( cd.tracks() ) random_playlist( cd ); break;
      case 'S': cd.stop(); break;
      case 'T': time_mode( true ); break;
      case ' ':
      case 'U': cd.pause(); break;
      case KEY_LEFT  : cd.prev_track(); break;
      case KEY_RIGHT : cd.next_track(); break;
      case KEY_UP    : cd.seek_backward( 10 ); break;
      case KEY_DOWN  : cd.seek_forward( 10 ); break;
      case KEY_PPAGE : cd.seek_backward( 30 ); break;
      case KEY_NPAGE : cd.seek_forward( 30 ); break;
      case KEY_HOME  : cd.seek_backward( 60 ); break;
      case KEY_END   : cd.seek_forward( 60 ); break;
      case KEY_IC    : cd.seek_backward( 300 ); break;
      case KEY_DC    : cd.seek_forward( 300 ); break;
      case '0': cd.track( 10, true ); break;
      default: if( std::isdigit( key ) ) cd.track( key - '0', true );
               else if( key >= KEY_F(1) && key <= KEY_F(12) )
                 cd.track( 10 + key - KEY_F(0), true );
               else continue;
      }
    show_data( cd );
    }
  }
