#include <stdlib.h>
#include <unistd.h>

#ifdef OS_BSD
 #include <ncurses.h>
#else
 #include <curses.h>
#endif

#include "cd.h"

#define CD_CONSOLE_VERSION "2.4"

#define KEY_CTRL_L 12
#define KEY_CTRL_R 18

// different display modes
enum display_mode { SMALL, BIG };

// used for keeping different display mode coordinates for stuff
struct point {
    int x,y;
};

// implements the interface around the cd-class
class cd_console {
public:
    cd_console(display_mode mode);
    ~cd_console();
    void run(void);
    void play_track(int track);
    void handle_track(int track);
    void display(void);
    void display_update(void);
    void display_random(void);
    void display_loop(void);
    void display_programmed(void);
    void display_tracks(void);
    void display_volume(void);
    void display_status(void);
    void display_misc(void);
    void display_plus_20(void);
private:
    cd m_cd;
    display_mode m_mode; // display mode
    bool m_plus_20; // 20+ track selection

    // mode positions for stuff
    point m_random, m_prog, m_loop, m_extra, m_status, m_volume,
        m_track;
};

// initialize ncurses and set up mode
cd_console::cd_console(display_mode mode)
{
    m_plus_20=false;

    m_mode = mode;
    if (mode == SMALL) {
        m_status.x=8;
        m_status.y=5;
        m_track.x=17;
        m_track.y=6;
        m_volume.x=32;
        m_volume.y=5;
        m_random.x=1;
        m_random.y=0;
        m_prog.x=1;
        m_prog.y=1;
        m_loop.x=1;
        m_loop.y=2;
        m_extra.x=1;
        m_extra.y=3;
    } else {
        m_status.x=25;
        m_status.y=12;
        m_track.x=39;
        m_track.y=14;
        m_volume.x=59;
        m_volume.y=12;
        m_random.x=11;
        m_random.y=1;
        m_prog.x=11;
        m_prog.y=2;
        m_loop.x=11;
        m_loop.y=3;
        m_extra.x=11;
        m_extra.y=4;
    }

    initscr();
    cbreak();
    nonl();
    noecho();
    keypad(stdscr,true);
    curs_set(0);
    halfdelay(5);
    display();
}

// reset terminal at end
cd_console::~cd_console()
{
    clear();
    refresh();
    endwin();
}

// main program loop
void cd_console::run(void)
{
    int key;

    // true if volume was adjusted last time
    bool volume_adjust=false;
    
    while ( (key = getch()) != 'q' ) {
            
        switch (key) {
        case KEY_CTRL_L:
        case KEY_CTRL_R:
            clear();
            display();
            break;
        case 's':
        case 'S':
            m_cd.stop();
            break;
        case 'p':
        case 'P':
            switch (m_cd.get_status()) {
            case PAUSED:
                m_cd.resume();
                break;
            case NO_CD_INSIDE:
            case PLAYING:
                break;
            case STOPPED:
                m_cd.play();
                break;
            }
            break;
        case KEY_RIGHT:
            m_cd.next();
            break;
        case 'u':
        case 'U':
            if (m_cd.get_status() == PLAYING)
                m_cd.pause();
            break;
        case 'e':
        case 'E':
            m_cd.eject();
            break;
        case 'r':
        case 'R':
            m_cd.set_random(!m_cd.get_random());
            break;
        case 'l':
        case 'L':
            m_cd.set_looping(!m_cd.get_looping());
            break;
        case 'm':
        case 'M':
            if (m_cd.get_mode() == PROGRAMMED)
                m_cd.set_mode(NORMAL);
            else
                m_cd.set_mode(PROGRAMMED);
            break;
        case 't':
            m_plus_20=!m_plus_20;
            display_plus_20();
            break;
        case '-':
            if (volume_adjust == true)
                m_cd.set_volume(m_cd.get_volume()-10);
            else
                m_cd.set_volume(m_cd.get_volume()-1);
            volume_adjust=true;
            break;
        case '+':
            if (volume_adjust == true)
                m_cd.set_volume(m_cd.get_volume()+10);
            else
                m_cd.set_volume(m_cd.get_volume()+1);
            volume_adjust=true;
            break;
        case KEY_F(1):
            play_track(1);
            break;
        case KEY_F(2):
            play_track(2);
            break;
        case KEY_F(3):
            play_track(3);
            break;
        case KEY_F(4):
            play_track(4);
            break;
        case KEY_F(5):
            play_track(5);
            break;
        case KEY_F(6):
            play_track(6);
            break;
        case KEY_F(7):
            play_track(7);
            break;
        case KEY_F(8):
            play_track(8);
            break;
        case KEY_F(9):
            play_track(9);
            break;
        case KEY_F(10):
            play_track(10);
            break;
        case KEY_F(11):
            play_track(11);
            break;
        case KEY_F(12):
            play_track(12);
            break;
        case '1':
            play_track(11);
            break;
        case '2':
            play_track(12);
            break;
        case '3':
            play_track(13);
            break;
        case '4':
            play_track(14);
            break;
        case '5':
            play_track(15);
            break;
        case '6':
            play_track(16);
            break;
        case '7':
            play_track(17);
            break;
        case '8':
            play_track(18);
            break;
        case '9':
            play_track(19);
            break;
        case '0':
            play_track(20);
            break;
        default:
            volume_adjust=false;
            break;
        }

        m_cd.read_status();
        display_update();
    }
}

void cd_console::play_track(int track)
{
    if (m_plus_20)
        handle_track(track+20);
    else
        handle_track(track);

    m_plus_20=false;
    display_plus_20();
}

void cd_console::handle_track(int track)
{
    if (m_cd.get_mode() == PROGRAMMED)
        m_cd.push_track(track);
    else
        m_cd.play_one(track);
}
    
// print everything
void cd_console::display(void)
{
    display_misc();
    display_status();
    display_tracks();
    display_random();
    display_loop();
    display_programmed();
    display_plus_20();
}

// print only stuff that can change
void cd_console::display_update(void)
{
    display_status();
    display_tracks();
    display_random();
    display_volume();
    display_loop();
    display_programmed();
}

// print random play indicator
void cd_console::display_random(void)
{
    if (m_cd.get_random() == true)
        mvaddch(m_random.y, m_random.x, '*');
    else 
        mvaddch(m_random.y, m_random.x, ' ');
}

void cd_console::display_loop(void)
{
   if (m_cd.get_looping() == true)
        mvaddch(m_loop.y, m_loop.x, '*');
    else 
        mvaddch(m_loop.y, m_loop.x, ' ');
}

void cd_console::display_programmed(void)
{
   if (m_cd.get_mode() == PROGRAMMED)
        mvaddch(m_prog.y, m_prog.x, '*');
   else 
        mvaddch(m_prog.y, m_prog.x ,' ');
}

void cd_console::display_volume(void)
{
    mvprintw(m_volume.y, m_volume.x, "%3d", m_cd.get_volume() );
}
    
// print track info
void cd_console::display_tracks(void)
{
    mvprintw(m_track.y, m_track.x, "%2d/%-2d", m_cd.get_current(),
             m_cd.get_tracks() );
}

// print extra track selection status
void cd_console::display_plus_20(void)
{
    mvprintw(m_extra.y, m_extra.x, "%c", m_plus_20 ? '*' : ' ');
}

// print status
void cd_console::display_status(void)
{
    switch (m_cd.get_status()) {
    case PLAYING:
        mvprintw(m_status.y, m_status.x, "Playing     ");
        break;
    case PAUSED:
        mvprintw(m_status.y, m_status.x, "Paused      ");
        break;
    case STOPPED:
        mvprintw(m_status.y, m_status.x, "Stopped     ");
        break;
    case NO_CD_INSIDE:
        mvprintw(m_status.y, m_status.x, "No cd inside");
    }
}

// print stuff that doesn't change
void cd_console::display_misc(void)
{
    if (m_mode == BIG) {
        mvprintw(0,0,"   --------------------------------------------------------------------------");
        mvprintw(1,0,"   |      (*) [R]  Random           [P]  Play        [Q]  Quit              |");
        mvprintw(2,0,"   |      (*) [M]  Programmed       [S]  Stop        [E]  Eject             |");
        mvprintw(3,0,"   |      (*) [L]  Looping          [U]  Pause       [+]  Increase volume   |");
        mvprintw(4,0,"   |      (*) [T]  Extra tracks     [->] Next        [-]  Decrease volume   |");
        mvprintw(5,0,"   |                                                                        |");
        mvprintw(6,0,"   |                                                                        |");
        mvprintw(7,0,"   |                                                                        |");
        mvprintw(8,0,"   |                                                                        |");
        mvprintw(9,0,"   |                                                                        |");
        mvprintw(10,0,"   |                                                                        |");
        mvprintw(11,0,"   --------------------------------------------------------------------------");
        mvprintw(12,0,"               | Status: Stopped                 | Volume:  40 |");
        mvprintw(13,0,"               -------------------------------------------------");
        mvprintw(14,0,"                              | Track:  0/17 |");
        mvprintw(15,0,"                              ----------------");
    } else {
        mvprintw(0,0,"(*) [R] Random     [P]  Play  [Q]  Quit");
        mvprintw(1,0,"(*) [M] Programmed [S]  Stop  [E]  Eject");
        mvprintw(2,0,"(*) [L] Looping    [U]  Pause [+-] Volume");
        mvprintw(3,0,"(*) [T] 20+ tracks [->] Next");
        mvprintw(5,0,"Status: Stopped         Volume:  40");
        mvprintw(6,0,"          Track:  0/17");
    }
}

void print_help(void)
{
    printf("Command line options:\n\n");
    printf("--big                     Use the big display mode\n");
    printf("-h, --help                Display this help\n");
    printf("--version                 Display version information\n");
}

void print_version(void)
{
    printf("cd-console version %s, compiled %s %s\n", CD_CONSOLE_VERSION,
           __DATE__, __TIME__);
    printf("Author:   Osku Salerma, osku@iki.fi\n");
    printf("Homepage: http://www.iki.fi/osku/cd-console/\n");
}

int main(int argc, char** argv)
{
    display_mode mode = SMALL;
    
    for (int i=1; i < argc; i++) {
        if (strcmp(argv[i], "--big") == 0)
            mode = BIG;
        else if ( (strcmp(argv[i], "-h") == 0) ||
             (strcmp(argv[i], "--help") == 0) ) {
            print_help();
            exit(0);
        } else if (strcmp(argv[i], "--version") == 0) {
            print_version();
            exit(0);
        } else {
            printf("Unknown option '%s'.\n", argv[i]);
            exit(1);
        }
    }

    cd_console cd_cons(mode);
    cd_cons.run();
    
    return 0;
};            
