/****************************************************
* Pacman For Console Level Editor                   *
* By: Mike Billars (michaelbillars@gmail.com)       *
* Date: 2014-04-26                                  *
*                                                   *
* Please see file COPYING for details on licensing  *
*       and redistribution of this program          *
*                                                   *
****************************************************/

/***********
* INCLUDES *
***********/
#include <stdio.h>
#include <curses.h>
#include <string.h>
#include <stdlib.h>

/*************
* PROTOTYPES *
*************/
void CheckScreenSize();                                 //Make sure resolution is at least 32x29
void CreateWindows(int y, int x, int y0, int x0);       //Make ncurses windows
void DrawWindow();                                      //Refresh display
void ExitProgram(char message[255]);                    //Exit gracefully
void GetInput();                                        //Get user input
void InitCurses();                                      //Start up ncurses
void LoadLevel();                                       //Load level into memory
void MainLoop();                                        //Main loop
void SaveLevel();                                       //Save level to specified file

/*******************
* GLOBAL VARIABLES *
*******************/
//I know global variables are bad, but it's just sooo easy!

//Windows used by ncurses
WINDOW * win;
WINDOW * status;

//For colors
enum { Wall = 1, Normal, Pellet, PowerUp, GhostWall, Ghost1, Ghost2, Ghost3, Ghost4, BlueGhost, Pacman, Cursor};

int Level[29][28] = { 0 };                              //Main level array
int Loc[6][2] = { 0 };                                  //Ghosts, Pacman, and cursor locations
int tleft = 0;                                          //How long left for invincibility
char filename[255] = "";                                //Name of file to load/save

/****************************************************************
* Function:    main()                                           *
* Parameters:  argc, argv (passed from command line)            *
* Returns:     Success                                          *
* Description: Initiate the program and call the subfunctions   *
****************************************************************/
int main(int argc, char *argv[100]) {

    //Make sure they entered a filename to open/save
    if((argc > 1) && (strlen(argv[1]) >= 1)) {

        strcpy(filename, argv[1]);                      //Copy command line parameter into string
        LoadLevel();                                    //Load the file

        InitCurses();                                   //Initialize ncurses
        CheckScreenSize();                              //Make sure the screen is big enough
        CreateWindows(29, 28, 1, 1);                    //

        DrawWindow();
        Loc[5][0]=13;
        Loc[5][1]=13;
        MainLoop();
    }
    else
        ExitProgram("You must specify a file to use (load and save)");

}

/****************************************************************
* Function:    CheckScreenSize()                                *
* Parameters:  none                                             *
* Returns:     none                                             *
* Description: Make sure the virtual console is big enough      *
****************************************************************/
void CheckScreenSize() {

    //Make sure the window is big enough
    int h, w; getmaxyx(stdscr, h, w);

    if((h < 32) || (w < 29)) {
        endwin();
        fprintf(stderr, "\nSorry.\n");
        fprintf(stderr, "To edit levels for Pacman for Console, your console window\n");
        fprintf(stderr, "must be at least 32x29 Please resize your window/resolution\n");
        fprintf(stderr, "and re-run the program.\n\n");
        ExitProgram("");
    }
}

/****************************************************************
* Function:    CreateWindows()                                  *
* Parameters:  y, x, y0, x0 (coords and size of window)         *
* Returns:     none                                             *
* Description: Create the main game windows                     *
****************************************************************/
void CreateWindows(int y, int x, int y0, int x0) {
    //Create two new windows, for status bar and for main level
    win = newwin(y, x, y0, x0);
    status = newwin(3, 28, 29, 1);
}

/****************************************************************
* Function:    DrawWindow()                                     *
* Parameters:  none                                             *
* Returns:     none                                             *
* Description: Redraw each window to update the screen          *
****************************************************************/
void DrawWindow() {
    int a = 0; int b = 0;
    char chr = ' ';
    int attr;

    //Display level array
    for(a = 0; a < 29; a++) for(b = 0; b < 28; b++) {
        switch(Level[a][b]) {
        case 0: chr = ' '; attr = A_NORMAL; wattron(win, COLOR_PAIR(Normal));    break;
        case 1: chr = ' '; attr = A_NORMAL; wattron(win, COLOR_PAIR(Wall));      break;
        case 2: chr = '.'; attr = A_NORMAL; wattron(win, COLOR_PAIR(Pellet));    break;
        case 3: chr = '*'; attr = A_BOLD;   wattron(win, COLOR_PAIR(PowerUp));   break;
        case 4: chr = ' '; attr = A_NORMAL; wattron(win, COLOR_PAIR(GhostWall)); break;
        case 5: chr = '&'; attr = A_NORMAL; wattron(win, COLOR_PAIR(Ghost1));    break;
        case 6: chr = '&'; attr = A_NORMAL; wattron(win, COLOR_PAIR(Ghost2));    break;
        case 7: chr = '&'; attr = A_NORMAL; wattron(win, COLOR_PAIR(Ghost3));    break;
        case 8: chr = '&'; attr = A_NORMAL; wattron(win, COLOR_PAIR(Ghost4));    break;
        case 9: chr = 'C'; attr = A_NORMAL; wattron(win, COLOR_PAIR(Pacman));    break;
        }
        mvwaddch(win, a, b, chr | attr);
    }

    //Display the cursor
    wattron(win,COLOR_PAIR(Cursor));
    mvwaddch(win, Loc[5][0], Loc[5][1], 'X');

    //Display info in status
    wmove(status, 1, 1);
    wattron(status, COLOR_PAIR(Normal));
    mvwprintw(status, 1,  0, "0)     1)       2)     3)");
    mvwprintw(status, 2,  0, "4)     5-8)     9)     F/C/Q");

    wattron(status, COLOR_PAIR(Normal));    mvwaddch(status, 1,  3, ' ');
    wattron(status, COLOR_PAIR(Wall));      mvwaddch(status, 1, 10, ' ');
    wattron(status, COLOR_PAIR(Pellet));    mvwaddch(status, 1, 19, '.');
    wattron(status, COLOR_PAIR(PowerUp));   mvwaddch(status, 1, 26 , '*' | A_BOLD);
    wattron(status, COLOR_PAIR(GhostWall)); mvwaddch(status, 2,  3, ' ');
    wattron(status, COLOR_PAIR(Ghost1));    mvwaddch(status, 2, 12, '&');
    wattron(status, COLOR_PAIR(Pacman));    mvwaddch(status, 2, 19, 'C');

    wrefresh(status);
    wrefresh(win);
}

/****************************************************************
* Function:    ExitProgram()                                    *
* Parameters:  message: text to display before exiting          *
* Returns:     none                                             *
* Description: Gracefully end program                           *
****************************************************************/
void ExitProgram(char message[255]) {

    endwin();

    //Must save file
    if(message[0]=='s') {
        printf("\n\nFile saved as: %s\n", filename);
    }

    else
        printf("%s\n\n", message);

    exit(0);
}

/****************************************************************
* Function:    GetInput()                                       *
* Parameters:  none                                             *
* Returns:     none                                             *
* Description: Get input from user and take appropriate action  *
****************************************************************/
void GetInput() {
    int ch;
    int a=0;
    int b=0;

    static int chtmp;

    ch = getch();

    //Buffer input
    do {
        DrawWindow();
        ch = getch();
    } while (ch == ERR);

    switch (ch) {
    case KEY_UP:    case 'w': case 'W':
        Loc[5][0]--;
        if(Loc[5][0]<0) { Loc[5][0]=28; }
        break;

    case KEY_DOWN:  case 's': case 'S':
        Loc[5][0]++;
        if(Loc[5][0]>28) { Loc[5][0]=0; }
        break;

    case KEY_LEFT:  case 'a': case 'A':
        Loc[5][1]--;
        if(Loc[5][1]<0) { Loc[5][1]=27; }
        break;

    case KEY_RIGHT: case 'd': case 'D':
        Loc[5][1]++;
        if(Loc[5][1]>27) { Loc[5][1]=0; }
        break;

    case 'q': case 'Q':
        //Save and exit
        SaveLevel();
        ExitProgram("s");
        break;

    //Draw a blank space
    case '0': case ' ':
        Level[Loc[5][0]][Loc[5][1]]=0;
        break;

    //Draw a wall
    case '1':
        Level[Loc[5][0]][Loc[5][1]]=1;
        break;

    //Draw a pellet
    case '2':
        Level[Loc[5][0]][Loc[5][1]]=2;
        break;

    //Draw a powerup
    case '3':
        Level[Loc[5][0]][Loc[5][1]]=3;
        break;

    //Draw a ghost/spawning wall
    case '4':
        Level[Loc[5][0]][Loc[5][1]]=4;
        break;

    //Draw a ghost/pacman
    case '5': case '6': case '7':
    case '8': case '9':
        //Move ghost/pacman to new location
        Level[Loc[5][0]][Loc[5][1]]=ch-'0';

        //Erase character from previous location and replace with BLANK
        if(Level[Loc[ch-'0'-5][0]][Loc[ch-'0'-5][1]]==ch-'0')
            Level[Loc[ch-'0'-5][0]][Loc[ch-'0'-5][1]]=0;

        //Set Loc variable to new location
        Loc[ch-'0'-5][0]=Loc[5][0];
        Loc[ch-'0'-5][1]=Loc[5][1];

        break;

    //Fill blank spaces with pellets
    case 'f': case 'F':
        for(a=0; a<29; a++)
        for(b=0; b<28; b++)
            if(Level[a][b]==0) Level[a][b]=2;
        break;

    //Fill pellet spaces with blanks
    case 'c': case 'C':
        for(a=0; a<29; a++)
        for(b=0; b<28; b++)
            if(Level[a][b]==2) Level[a][b]=0;
        break;

    }
}

/****************************************************************
* Function:    InitCurses()                                     *
* Parameters:  none                                             *
* Returns:     none                                             *
* Description: Initialize ncurses and set defined colors        *
****************************************************************/
void InitCurses() {

    initscr();                      //Needed for ncurses windows
    start_color();                  //Activate colors in console
    curs_set(0);                    //      Don't remember
    keypad(stdscr, TRUE);           //Activate arrow keys
    nodelay(stdscr, TRUE);          //Allow getch to work without pausing
    nonl();                         //      Don't remember
    cbreak();                       //      Don't remember
    noecho();                       //Don't display input key

    //Make custom text colors
    init_pair(Normal,    COLOR_WHITE,   COLOR_BLACK);
    init_pair(Wall,      COLOR_WHITE,   COLOR_WHITE);
    init_pair(Pellet,    COLOR_WHITE,   COLOR_BLACK);
    init_pair(PowerUp,   COLOR_BLUE,    COLOR_BLACK);
    init_pair(GhostWall, COLOR_WHITE,   COLOR_CYAN);
    init_pair(Ghost1,    COLOR_RED,     COLOR_BLACK);
    init_pair(Ghost2,    COLOR_CYAN,    COLOR_BLACK);
    init_pair(Ghost3,    COLOR_MAGENTA, COLOR_BLACK);
    init_pair(Ghost4,    COLOR_YELLOW,  COLOR_BLACK);
    init_pair(BlueGhost, COLOR_BLUE,    COLOR_RED);
    init_pair(Pacman,    COLOR_YELLOW,  COLOR_BLACK);
    init_pair(Cursor,    COLOR_BLUE,    COLOR_YELLOW);
}

/****************************************************************
* Function:    LoadLevel()                                      *
* Parameters:  none                                             *
* Returns:     none                                             *
* Description: Open level file and load it into memory          *
****************************************************************/
void LoadLevel() {

    int a = 0; int b = 0;
    FILE *fin;

    //Open file
    fin = fopen(filename, "r");

    //File doesn't exist, start new
    if(!(fin)) {
        for(a=0; a<28; a++) { Level[a][0]=1; Level[a][27]=1; }
        for(a=0; a<28; a++) { Level[0][a]=1; Level[28][a]=1; }
        Loc[0][0]=2; Loc[0][1]=2;
        Loc[1][0]=2; Loc[1][1]=4;
        Loc[2][0]=2; Loc[2][1]=6;
        Loc[3][0]=2; Loc[3][1]=8;
        Loc[4][0]=19; Loc[4][1]=19;
        Level[Loc[0][0]][Loc[0][1]]=5;
        Level[Loc[1][0]][Loc[1][1]]=6;
        Level[Loc[2][0]][Loc[2][1]]=7;
        Level[Loc[3][0]][Loc[3][1]]=8;
        Level[Loc[4][0]][Loc[4][1]]=9;
    }
    else {

        //Open file and load the level into the array
        for(a = 0; a < 29; a++)
        for(b = 0; b < 28; b++) {
            fscanf(fin, "%d", &Level[a][b]);
            if(Level[a][b] == 5) { Loc[0][0]=a; Loc[0][1]=b; }
            if(Level[a][b] == 6) { Loc[1][0]=a; Loc[1][1]=b; }
            if(Level[a][b] == 7) { Loc[2][0]=a; Loc[2][1]=b; }
            if(Level[a][b] == 8) { Loc[3][0]=a; Loc[3][1]=b; }
            if(Level[a][b] == 9) { Loc[4][0]=a; Loc[4][1]=b; }
        }
    }
}

/****************************************************************
* Function:    MainLoop()                                       *
* Parameters:  none                                             *
* Returns:     none                                             *
* Description: Control the main execution of the game           *
****************************************************************/
void MainLoop() {
    do {
        GetInput();
        DrawWindow();
    } while (1);
}

/****************************************************************
* Function:    SaveLevel()                                      *
* Parameters:  none                                             *
* Returns:     none                                             *
* Description: Save current level array to a specified file     *
****************************************************************/
void SaveLevel() {

    int a=0;
    int b=0;
    FILE *fout;

    //Open file
    fout = fopen(filename, "w");

    //Check for write permissions
    if(!(fout)) {
        ExitProgram("Cannot write file. :-(");
    }

    else {
        for(a = 0; a < 29; a++) {
            for(b = 0; b < 28; b++) {
                fprintf(fout, "%d ", Level[a][b]);
            }
            fprintf(fout, "\n");
        }
        fprintf(fout, "1");
        fclose(fout);
    }
}
