/*/////////
//
//  pong.c 
//
//      AUTHOR: Russell Lowke
//   CSCIE-215: Assignment Four
//
//       Usage: pong
//
//              use 'j' and 'k' keys to move paddle
//              use 'x' key to add more balls   (MAX 100)
//              use '<' and '>' keys to change gravity
//              use 'f' to speed game up
//              use 's' to slow game down
//              use 'q' to quit
//
// Description: Pong game!
//
//              A version of pong.
//              Objective is to get as many pong balls in play as possible.
//              Every four (4) times a ball is deflected by the paddle
//              a new pong ball is added.
//              How many pong balls can you keep in the air at one time?
//
//              If all balls get past the paddle 3 times, the game is over.
//
//
//      Method: Pong.c contains main, has all general fctns,
//              and also contains the parameters for the court (playing
//              area) in a static struct called mCourt.
//              key fctns are:
//
//                setUp     - establishes pong settings and vars
//                            sets an alarm to call update every [delay]
//                            milliseconds
//                main      - main loop simply reads keyboard input, letting
//                            alarm take care of the updates.
//                            on 'q' key pong wraps up all settings and
//                            memory via wrap_up.  ctrl 'c' is disabled to
//                            ensure wrap_up
//                wrap_up   - Cleans up all settings before exiting.
//                update    - calls updateBall and refresh
//                drawCourt - draws the playing court
//    
//              Ball.c, Paddle.c and alarmlib.c are modular segments of
//              code, each focusing on a specific task.
//
//              both ball.c and paddle.c use "get" functions to retrieve
//              information about the court from pong.c.   This way all vars
//              are protected and no globals used.
//
//              ball.c handles multiball, 
//              key fctns are:
//
//                updateball  - updates all balls in play
//                addBall     - adds a new ball to the field
//                removeBall  - removes ball from field
//                newBall     - mallocs memory for the ball
//                drawBall    - displays a ball
//                isBallOut   - checks if ball is out of the court
//
//
//              paddle.c handles the paddle control,
//              key fctns are:
//
//                init_paddle - prepares the paddle
//                up_paddle   - move paddle up
//                down_paddle - move paddle down
//                draw_paddle - displays the paddle
//                is_a_hit    - tells if the paddle hits a ball
//
//              alarmlib.c handles setting alarm, key fctns are:
//                set_ticker  - arranges for the interval timer to issue
//                              SIGALRM's at regular intervals
//
//       Notes: court size can easily be reconfigured by changing properties in mCourt
//              but if the court is too large, refresh issues may occur
*/


/* include header */
#include        "pong.h"

static int    delay;          /* delay  controls speed of game */
static struct court  mCourt;  /* mCourt struct (also used by paddle and ball) */
static double gravity;        /* strength of gravity on balls */


/*
//  set_up
//
//     initialization handlers for pong.
//     set all values for pong game
//     sets the alarm to trigger [update] every [delay] milliseconds
//
*/
void set_up()
{
    void update();
    
    /* gereral initialize */
    initscr();                        /* init screen for drawing */
    crmode();                         /* ctrlmode */
    noecho();                         /* no keyboard echo */
    clear();                          /* clear screen */
    srand(getpid());                  /* set random seed */
    signal( SIGINT , SIG_IGN   );     /* ignore ctrl C, - so pong
                                         always gets to cleanup */
    /* set pong values */
    gravity       = 0.01;             /* gravity is 0.01 per delay */
    delay         = 25;               /* 1000 ms = 1 second */
    mCourt.left   = 9;                /* top left at     9,4 */
    mCourt.top    = 4;
    mCourt.right  = 70;               /* bottom right at 70,41 */
    mCourt.bottom = 21;
    /* mCourt.bottom = LINES - 4;        make court screen size */
    /* mCourt.right  = COLS  - 10;       (sometimes has refresh problem) */
    
    /* setup game */
    drawCourt();                      /* draw the playing court */
    init_paddle();                    /* Init the paddle */
    addBall();                        /* throw a ball into the court */

    /* set alarm */
    signal(SIGALRM, update );         /* set alarm to trigger update...*/
    set_ticker( delay );              /* every delay milliseconds. */
}


/*
//  main
//
//     Calls setup - then goes into a main loop which 
//     checks for keys to respond to.
//     Screen refresh is handled by an alarm, 
//     which triggers every [delay] milliseconds.
//     On 'Q' key the main loop is exited - then wrapup called.
*/
int main()
{
    int key, 
        doQuit = 0;

    /* setup for pong */
    set_up(); 

    while(! doQuit)
    {
        /* keyboard control, get the key pressed */
        key = getch();
                
        switch (key) {
            case 'F': case 'f':                         /* Faster */
                if (delay > 2) {
                    delay = delay/2;
                    set_ticker(delay);
                }
                break;
            case 'S': case 's': case 'D': case 'd':     /* Slower */
                delay = delay * 2;
                set_ticker(delay);
                break;
            case 'J': case 'j': case 'A': case 'a':     /* Paddle Up */
                up_paddle(&mCourt);
                break;
            case 'K': case 'k': case 'Z': case 'z':     /* Paddle Down */
                down_paddle(&mCourt);
                break;
            case 'X': case 'x':                         /* Extra Ball */
                addBall(&mCourt);
                break;
            case ',': case '<': case 'G': case 'g':     /* More Gravity */
                gravity += 0.01;
                break;
            case '.': case '>': case 'L': case 'l':     /* Less Gravity */
                gravity -= 0.01;
                break;
            case 'Q': case 'q':                         /* Quit */
                doQuit = 1;
                break;
        }

        if (getLivesLeft() == 0)
            doQuit = 1;
    }
    
    /* wrapup pong */
    wrap_up();
    
    return (0);
                        
}


/*
//  wrap_up
//
//     Cleans up all settings before exiting.
//     fix memory for balls
//     and fix terminal settings.
//
*/
void wrap_up()
{    
    /* House cleaning */
    disposeBalls();                          /* clear memory of balls */
    set_ticker( 0 );                         /* reset ticker          */
    endwin();                                /* back to normal        */   
}


/*
//  update
//
//     Called via alarm "signal(SIGALRM, update)"
//     as found in function set_up()
//
//     This function is called every [delay] milliseconds.
//     All methods which need to be updated before refresh are here.
//     
*/
void update()
{
    
    signal(SIGALRM, SIG_IGN);                /* ignore signals during update */
    
    updateBall();                            /* update all balls */
    hideCursor();                            /* hide cursor before
                                                refresh */
    
    refresh();                               /* now show changes */
    signal(SIGALRM, update);	             /* reset alarm  */ 

}


/*
//  hideCursor
//
//     "hide" the cursor by moving it into the bottom right corner
//     Bottom right corner is least obtrusive place.
*/
void hideCursor()
{
    /* move cursor to bottom right corner */
    move(LINES-1,COLS-1);
    curs_set(0);
}


/*
//  drawCourt
//
//     Draw playing court defined by [mCourt]
//     Use vLine and hLine to display top, left and bottom of court
//
//     args:  *mCourt         --  pts to court data
*/
void drawCourt()
{
    /* left wall */
    vLine(mCourt.left,
          mCourt.top,
          mCourt.bottom, '|');

    /* top wall */
    hLine(mCourt.left,
          mCourt.top,
          mCourt.right,  '-');

    /* bottom wall */
    hLine(mCourt.left,
          mCourt.bottom,
          mCourt.right,  '-');
}

/*
//  tells the right of court
*/
int getCourtRight()
{
    return mCourt.right;
}

/*
//  tells the top limit of court
*/
int getTopLimit()
{
    return mCourt.top + 1;
}

/*
//  tells the bottom limit of court
*/
int getBottomLimit()
{
    return mCourt.bottom - 1;
}

/*
//  tells the left limit of court
*/
int getLeftLimit()
{
    return mCourt.left + 1;
}

/*
//  tells the right limit of court
*/
int getRightLimit()
{
    return mCourt.right - 1;
}

/*
//  tells the mid x point of court
*/
int getMidCourtX()
{
    return mCourt.left + (mCourt.right  - mCourt.left) / 2;
}

/*
//  tells the mid y point of court
*/
int getMidCourtY()
{
    return mCourt.top  + (mCourt.bottom - mCourt.top)  / 2;
}

/*
//  getGravity
//
//     Tells ball.c what gravity is
*/
double getGravity()
{
    return gravity;
}


/*
//  hLine
//
//     Draw a horizontal line (text) from point [x,y] 
//                                   to point [x, y2]
//                                 using char symbol
//
//     args:  x         --  x location to draw line from
//            y         --  y location to draw line from
//            x2        --  x location to draw line to.
//            symbol    --  char symbol to draw with.
*/
void hLine(int x, int y, int x2, char symbol)
{

    int i;
    
    /* move from x to x2 at y, drawing symbol */
    for (i = x; i <= x2; i++) {
        move(y, i);
        addch(symbol);
    }
}


/*
//  vLine
//
//     Draw a vertical line (text) from point [x,y] 
//                                   to point [x, y2]
//                                 using char symbol
//
//     args:  x         --  x location to draw line from
//            y         --  y location to draw line from
//            y2        --  y location to draw line to.
//            symbol    --  char symbol to draw with.
*/
void vLine(int x, int y, int y2, char symbol)
{
    int i;

    /* move from y to y2 at x, drawing symbol */
    for (i = y; i <= y2; i++) {
        move(i, x);
        addch(symbol);
    }
}


/*
//  randomInt
//
//     Generate a random integer between [min] and [max]
//
//     args:  min         --  minimum value to return
//            max         --  maximum value to return
*/
double randomInt(int min, int max)
{
    int range = max - min;
    
    return rand()%range + min;
}


/*
//  fatal
//
//     Fatal error message to stderr then exit (never returns)
*/
void fatal(char *errStr)
{
    fprintf(stderr, "%s\n", errStr);
    exit(1);
}
