#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <allegro5/allegro.h>
#include "objects.h"
#include "drawings.h"
#include "collisions.h"
#include "levels.h"
#include "audio.h"
#include "hiscore.h"

/* Enum för keys och game states */
enum KEYS {UP, DOWN, LEFT, RIGHT, SPACE, B, ESCAPE, ENTER, BACKSPACE};
enum STATE {MENU, PLAYING, GAMEOVER, QUALIFY};

/* Funktionsprototyper */
void call_drw_bld(bld *bld, int s);
void call_drw_mis(mis *mis, int s);
void call_drw_lne(lne *lne, int s);
void call_drw_bul(bul *bul, int s);
void call_drw_las(las *las, int s);
void call_drw_bom(bom *bom, int s);
void call_drw_xpl(xpl *xpl, int s);
void call_drw_ful(ful *ful, int s);
void call_drw_ufo(ufo *ufo, int s);
void call_drw_cba(cba *cba, int s);
void call_drw_com(com *com, int s);
void call_drw_qualify(chr *chr, int s, int w);
void new_game(ply *ply, hrc *hrc);

int main(void)
{
    /* Frö till funktionen rand()*/
    srand(time(NULL)); 
    
    /* Spelvariabler */
    int width = 800; /* Fönster bredd */
    int height = 600; /* Fönster höjd */
    int done = 0; /* Game loop bool */
    int fps = 60; /* Uppdatering sker vid 60 */
    int redraw = 1; /* Vippa för att uppdatera grafik */
    int keys[9] = {0,0,0,0,0,0}; /* Array for the keys */
    int state = MENU; /* Håller spel läge, börjar i meny */
    int seg[4] = {0}; /* Håller data för det aktuella segmentet */
    int lvllen = 160; /* Hur lång är en nuvå */
    int numlvl = 5; /* Antal nivåer */
    int lvlcnt = 0; /* Räknare för nivån */
    int pulse = 0; /* Vid 80 uppdateras spelet */
    int n_bld = 3; /* Antal samtidiga buggnader */
    int n_mis = 5; /* Antal samtidiga missiler */
    int n_lne = 36; /* Antal samtidiga linjer */
    int n_bul = 5; /* Antal samtidiga spelarskott */
    int n_bom = 2; /* Antal samtidiga bomber */
    int n_xpl = 20; /* Antal explosioner som kan vara igång samtidigt */
    int n_ful = 3; /* Antal bränslebehållare som kan vara i spel samtidigt */
    int n_ufo = 5; /* Antal ufo som kan vara i spåel samtidigt */
    int n_las = 10; /* Antal lasrar som kan vara i spel samtidigt */
    int n_cba = 5; /* Antal kanonkulor som kan vara i luften samtidigt */
    int n_chr = 30; /* Antal charactärer i chars array */
    int n_com = 10; /* Antal samtidiga kometer */

    
    /* Objekt variabler */
    ply ply;
    can can;
    aln aln;
    hrc hrc;
    bld bld[n_bld];
    lne lne[n_lne];
    bul bul[n_bul];
    bom bom[n_bom];
    xpl xpl[n_xpl];
    mis mis[n_mis];
    ful ful[n_ful];
    ufo ufo[n_ufo];
    las las[n_las];
    cba cba[n_cba];
    chr chr[n_chr];
    com com[n_com];
    
    
    /* Skapa allegro variabler */
    ALLEGRO_DISPLAY *display = NULL;
    ALLEGRO_EVENT_QUEUE *event_queue = NULL;
    ALLEGRO_TIMER *timer = NULL;

    /* Initiera allegro */
    if(!al_init()) {
        puts("ERROR: allegro failed to initialize.");
        return -1;
    }
    
    /* Anti aliansing */
    //al_set_new_display_option(ALLEGRO_SAMPLE_BUFFERS, 1, ALLEGRO_SUGGEST);
    //al_set_new_display_option(ALLEGRO_SAMPLES, 4, ALLEGRO_SUGGEST);
    
    /* Skapar display */
    al_set_new_display_flags(ALLEGRO_FULLSCREEN);
    display = al_create_display(width, height);
    if(!display) {
        puts("ERROR: allegro failed to create display.");
        return -1;
    }
    
    /* Laddar allegro addons */
    ini_allegro_addon();
    ini_allegro_audio();
    
    /* Initierar objekt i spelet */
    ini_ply(&ply, height);
    ini_bld(bld, n_bld);
    ini_lne(lne, n_lne);
    ini_bul(bul, n_bul);
    ini_bom(bom, n_bom);
    ini_xpl(xpl, n_xpl);
    ini_mis(mis, n_mis);
    ini_ful(ful, n_ful);
    ini_ufo(ufo, n_ufo);
    ini_las(las, n_las);
    ini_cba(cba, n_cba);
    ini_chr(chr, n_chr);
    ini_com(com, n_com);
    ini_can(&can);
    ini_aln(&aln);
    ini_hrc(&hrc);
    ini_hiscore();
    
    
    /* Installera tangentbord */
    al_install_keyboard(); 
    
    /* Skapar en font variabel (pekare) av typen ALLEGRO_FONT */
    ini_allegro_fonts();
    
    /* Skapar allegro färgvariabler */
    ini_allegro_colors();
    
    /* Skapar allegro ljudvariabler */
    ini_allegro_sounds();
    
    /*
     * Skapa en timer som ser till att cyckeln går 60 ggr per sekund 
     * Detta är en industristandard och är till för att spelet ska gå 
     * lika snabbt oberoende av hur snabb datorn är
     */
    timer = al_create_timer(1.0 / fps);
    
    /* Skapar en händelse kö samt kopplar tangentbordet och display till denna kö */
    event_queue = al_create_event_queue();
    al_register_event_source(event_queue, al_get_keyboard_event_source());
    al_register_event_source(event_queue, al_get_display_event_source(display));
    al_register_event_source(event_queue, al_get_timer_event_source(timer));
    
    /* Startar timern */
    al_start_timer(timer);

    /***************************************************************************
     * Huvud loopen, game loop                                                 *
     **************************************************************************/
    while(!done) {
        /*
         * Skapar en event vaiabel samt väntar på att den skall fyllas med
         * registrerade events
         */
        ALLEGRO_EVENT ev;
        al_wait_for_event(event_queue, &ev);
             
        /***********************************************************************
         * Uppdatera alla objekt. Detta görs när timern triggas vilket den är  *
         * satt att göra 60 ggr per sekund. All uppdateringar skall ske här    *
         **********************************************************************/
        if(ev.type == ALLEGRO_EVENT_TIMER) {
            redraw = 1;
            pulse++;
            
            /* Baserat på vad state är uppdaeras olika delar av programmet */
            if(state == MENU) {
            } else if(state == PLAYING) {
                if(ply.cur_fuel > 0) {
                    if(keys[UP])
                        mov_ply_up(&ply);
                    if(keys[DOWN])
                        mov_ply_down(&ply);
                    if(keys[LEFT])
                        mov_ply_left(&ply);
                    if(keys[RIGHT])
                        mov_ply_right(&ply);
                } else {
                    mov_ply_down(&ply);
                    mov_ply_right(&ply);
                }
                
            /* Explosioner skall fortsätta att uppdateras efter död */
            upd_xpl(xpl, n_xpl);
            
            /* Medans spelaren inte är död skall alla uppdateringa göras */
            if(!ply.died) {
                /* Uppdaterar objekt */
                upd_bul(bul, n_bul);
                upd_bom(bom, n_bom);
                upd_bld(bld, n_bld);
                upd_ful(ful, n_ful);
                upd_lne(lne, n_lne);
                upd_las(las, n_las);
                upd_com(com, n_com);
                upd_mis(mis, &ply, n_mis);
                upd_ufo(ufo, &ply, las, n_ufo, n_las);
                upd_las(las, n_las);
                upd_can(&can, cba, &ply, n_cba);
                upd_cba(cba, n_cba);
                upd_aln(&aln);
                
                
                /* Kontrollerar kollisioner */
                col_lne_bul(lne, bul, xpl, n_lne, n_bul, n_xpl);
                col_lne_bom(lne, bom, xpl, n_lne, n_bom, n_xpl);
                col_lne_com(lne, com, xpl, n_lne, n_com, n_xpl);
                col_lne_mis(lne, mis, xpl, n_lne, n_mis, n_xpl);
                col_lne_las(lne, las, xpl, n_lne, n_las, n_xpl);
                col_lne_cba(lne, cba, xpl, n_lne, n_cba, n_xpl);
                col_lne_ufo(lne, ufo, xpl, n_lne, n_ufo);
                col_bld_ply(bld, &ply, xpl, n_bld, n_xpl);
                col_bld_bul(bld, &ply, bul, xpl, n_bld, n_bul, n_xpl);
                col_bld_bom(bld, &ply, bom, xpl, n_bld, n_bom, n_xpl);
                col_lne_ply(lne, &ply, xpl, n_lne, n_xpl);
                col_mis_ply(mis, &ply, xpl, n_mis, n_xpl);
                col_mis_bul(mis, &ply, bul, xpl, n_mis, n_bul, n_xpl);
                col_mis_bom(mis, &ply, bom, xpl, n_mis, n_bom, n_xpl);
                col_ful_ply(ful, &ply, xpl, n_ful, n_xpl);
                col_ful_bul(ful, &ply, bul, xpl, n_ful, n_bul, n_xpl);
                col_ful_bom(ful, &ply, bom, xpl, n_ful, n_bom, n_xpl);
                col_ufo_ply(ufo, &ply, xpl, n_ufo, n_xpl);
                col_ufo_bul(ufo, &ply, bul, xpl, n_ufo, n_bul, n_xpl);
                col_ufo_bom(ufo, &ply, bom, xpl, n_ufo, n_bom, n_xpl);
                col_las_ply(las, &ply, xpl, n_las, n_xpl);
                col_can_ply(&can, &ply, xpl, n_xpl);
                col_can_bul(&can, &ply, bul, xpl, n_bul, n_xpl);
                col_can_bom(&can, &ply, bom, xpl, n_bom, n_xpl);
                col_aln_ply(&aln, &ply, xpl, n_xpl);
                col_aln_bul(&aln, &ply, bul, xpl, n_bul, n_xpl);
                col_aln_bom(&aln, &ply, bom, xpl, n_bom, n_xpl);
                col_cba_ply(cba, &ply, xpl, n_cba, n_xpl);
                col_com_ply(com, &ply, xpl, n_com, n_xpl);
                col_com_bom(com, &ply, bom, xpl, n_com, n_bom, n_xpl);
                col_com_bul(com, &ply, bul, xpl, n_com, n_bul, n_xpl);
                
                /* Pulsen i spelet */
                if(pulse >= 80) {
                    ply.score++;
                    ply.cur_fuel -= 3;
                    if(ply.cur_fuel <= 0)
                        ply.cur_fuel = 0;
                    
                    /* Ritar ut marken */
                    get_grd(ply.lvl, lvlcnt, seg);
                    spn_lne(lne, n_lne, seg[0], seg[1], seg[2], seg[3]);
                    if(seg[0] - seg[2] == 0) 
                        spn_lne(lne, n_lne, seg[0], seg[3], 
                            seg[2] + 80, seg[3]);

                    /* Ritar ut tak */
                    if(ply.lvl == 2 || ply.lvl == 3) {
                        get_rof(ply.lvl, lvlcnt, seg);
                        spn_lne(lne, n_lne, seg[0], seg[1], seg[2], seg[3]);
                        if(seg[0] - seg[2] == 0) 
                            spn_lne(lne, n_lne, seg[0], seg[3], 
                                seg[2] + 80, seg[3]);
                    }
                    
                    /* Ritar ut objektiv */
                    get_obj(ply.lvl, lvlcnt, seg);
                    switch(seg[0]) {
                        case 1:
                            spn_bld(bld, n_bld, seg[1], seg[2] - 20);
                            break;
                        case 2:
                            spn_ful(ful, n_ful, seg[1], seg[2] - 20);
                            break;
                        case 3:
                            spn_aln(&aln, seg[1], seg[2] - 20);
                            break;
                    }
                    
                    /* Ritar ut fiender */
                    get_emy(ply.lvl, lvlcnt, seg);
                    switch(seg[0]) {
                        case 1:
                            spn_mis(mis, n_mis, seg[1], seg[2] - 20);
                            break;
                        case 2:
                            spn_ufo(ufo, n_ufo, seg[1], seg[2] - 10);
                            break;
                        case 3:
                            spn_can(&can, seg[1], seg[2] - 20);
                            break;
                    }
                    
                    
                    
                    pulse = 0;
                    lvlcnt += 4;
                    if(lvlcnt >= lvllen) {
                        ply.lvl++;
                        if(ply.lvl > numlvl)
                            ply.lvl = 1;
                        lvlcnt = 0;
                    }
                }
                if(ply.lvl == 4 && lvlcnt > 40 && !(pulse % 20)) 
                        spn_com(com, n_com, (rand() % 700) + 100, 20);
                
            } else {
                ply.counter--;
                if(ply.counter == 0) {
                    pulse = 0;
                    lvlcnt = 0;
                    ply.counter = 0;
                    ply.died = 0;
                    ply.x = 20;
                    ply.y = height / 2;
                    ply.cur_fuel = ply.max_fuel;
                    ply.lives--;
                    nul_obj(&aln, &can, xpl, lne, bld, bom, bul, mis, ful, 
                        ufo, las, cba, com, n_bul, n_bom, n_bld, n_lne, n_xpl, 
                        n_mis, n_ful, n_ufo, n_las, n_cba, n_com);
                    if(ply.lives <= 0) {
                        if(qualify(ply.score))
                            state = QUALIFY;
                        else
                            state = GAMEOVER;
                    }
                }
            }
                       
            } else if(state == GAMEOVER) {

            } else if(state == QUALIFY)  {
                upd_ini(chr, &hrc, &ply, n_chr);
                if(ply.add) {
                    add_score(ply.score, ply.ini.initials);
                    sort_hiscore();
                    ply.add = 0;
                    state = MENU;
                }
            }
        } /* end if update */
        
        
        /***********************************************************************
         * Hanterar tangent ner och upp                                        *
         **********************************************************************/
        if(ev.type == ALLEGRO_EVENT_KEY_DOWN) {
            /* beroende på vilken tangent som tryckts ned sätt array till 1 */
            switch(ev.keyboard.keycode) {
                case ALLEGRO_KEY_UP:
                    keys[UP] = 1;
                    if(state == QUALIFY) {
                        ply_sound(5);
                        move_hrc_up(&hrc);
                    }
                    break;
                case ALLEGRO_KEY_DOWN:
                    keys[DOWN] = 1;
                    if(state == QUALIFY) {
                        ply_sound(5);
                        move_hrc_down(&hrc);
                    }
                    break;
                case ALLEGRO_KEY_LEFT:
                    keys[LEFT] = 1;
                    if(state == QUALIFY) {
                        ply_sound(5);
                        move_hrc_left(&hrc);
                    }
                    break;
                case ALLEGRO_KEY_RIGHT:
                    keys[RIGHT] = 1;
                    if(state == QUALIFY) {
                        ply_sound(5);
                        move_hrc_right(&hrc);
                    }
                    break;
                case ALLEGRO_KEY_SPACE:
                    keys[SPACE] = 1;
                    if(state == MENU) {
                        new_game(&ply, &hrc);
                        state = PLAYING;
                    } else if(state == PLAYING) {
                        spn_bul(bul, n_bul, &ply);
                    } else if(state == GAMEOVER) { 
                        state = MENU;
                    } else if(state == QUALIFY) {
                        ply.ini.update++;
                        if(ply.ini.update >= 3)
                            ply.ini.update = 3;
                    }
                    break;
                case ALLEGRO_KEY_B:
                    keys[B] = 1;
                    if(state == PLAYING) {
                        spn_bom(bom, n_bom, &ply);
                    }
                    break;
                case ALLEGRO_KEY_ESCAPE:
                    keys[ESCAPE] = 1;
                    if(state == MENU) {
                        done = 1;
                    } else if (state == PLAYING){
                        state = GAMEOVER;
                    } else if (state == GAMEOVER){
                        done = 1;
                    } else if (state == QUALIFY){
                        done = 1;
                    }
                    break;
                case ALLEGRO_KEY_ENTER:
                    keys[ENTER] = 1;
                    if(state == QUALIFY)
                        ply.add = 1;
                    break;
                case ALLEGRO_KEY_BACKSPACE:
                    keys[BACKSPACE] = 1;
                    if(state == QUALIFY) {
                        ply.ini.update--;
                        if(ply.ini.update <= 1)
                            ply.ini.update = 1;
                    }
                    break;
            } /* end switch key down */
        } else if(ev.type == ALLEGRO_EVENT_KEY_UP) {
            /* beroende på vilken tangent som släpps upp array till 0 */
            switch(ev.keyboard.keycode) {
                case ALLEGRO_KEY_UP:
                    keys[UP] = 0;
                    break;
                case ALLEGRO_KEY_DOWN:
                    keys[DOWN] = 0;
                    break;
                case ALLEGRO_KEY_LEFT:
                    keys[LEFT] = 0;
                    break;
                case ALLEGRO_KEY_RIGHT:
                    keys[RIGHT] = 0;
                    break;
                case ALLEGRO_KEY_SPACE:
                    keys[SPACE] = 0;
                    break;
                case ALLEGRO_KEY_B:
                    keys[B] = 0;
                    break;
                case ALLEGRO_KEY_ESCAPE:
                    keys[ESCAPE] = 0;
                    break;
                case ALLEGRO_KEY_ENTER:
                    keys[ENTER] = 0;
                    break;
                case ALLEGRO_KEY_BACKSPACE:
                    keys[BACKSPACE] = 0;
                    break;
            } /* end switch key_up */
        
        /* Om man klickar på det röda krysset skall spelet avslutas */
        } else if(ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) {
            done = 1;
        } 
        
        /***********************************************************************
         * Ritar ut grafiken för alla objekt. Detta görs endast om variabeln   *
         * redraw är 1 samt om det inte finns några andra händelser i kön.     *
         * Detta för att det är viktigare att handera uppdateringar än att     *
         * rita objekt.                                                        *
         **********************************************************************/
        if(redraw && al_is_event_queue_empty(event_queue)) {
            redraw = 0;

            /* Baserat på vad state är ritas olika saker */
            if(state == MENU) {
                /* Ritar meny */
                drw_menu(width);
                drw_bld(100, 450);
                drw_ful(350, 450);
                drw_aln(600, 450, 1);
                drw_mis(100, 520, 0);
                drw_ufo(350, 520);
                drw_can(600, 520, 1);
            } else if(state == PLAYING) {
                /* Ritar alla objekt */
                if(!ply.died)
                    drw_ply(ply.x, ply.y);
                
                call_drw_bld(bld, n_bld);
                call_drw_mis(mis, n_mis);
                call_drw_lne(lne, n_lne);
                call_drw_bul(bul, n_bul);
                call_drw_las(las, n_las);
                call_drw_bom(bom, n_bom);
                call_drw_xpl(xpl, n_xpl);
                call_drw_ful(ful, n_ful);
                call_drw_ufo(ufo, n_ufo);
                call_drw_cba(cba, n_cba);
                call_drw_com(com, n_com);
                drw_can(can.x, can.y, can.live);
                drw_aln(aln.x, aln.y, aln.live);
                drw_ply_stats(ply.lives, ply.score, ply.max_fuel, ply.cur_fuel);
                
                if(lvlcnt < 8)
                    drw_lvl(ply.lvl, height, width);
                
            } else if(state == GAMEOVER)  {
                /* ritar game over */
                drw_game_over(height, width);
                
            } else if(state == QUALIFY)  {
                /* ritar kvalifiseringsskärmen */
                call_drw_qualify(chr, n_chr, width);
                drw_ini(width, ply.ini.initials);
                drw_hrc(hrc.x, hrc.y);
            }

            /* Double buffering, visar skärmen som ritats i bakgrunden */
            al_flip_display(); 
            
            /* Rensar allt på den bakre skärmen */
            al_clear_to_color(al_map_rgb(0,0,0));
        } /* end if redraw */
        
    }
    return 0;
}

void new_game(ply *ply, hrc *hrc)
{
    ply->lives = 3;
    ply->score = 0;
    ply->cur_fuel = ply->max_fuel;
    ply->lvl = 1;
    strcpy(ply->ini.chr1, ".");
    strcpy(ply->ini.chr2, ".");
    strcpy(ply->ini.chr3, ".");
    ply->ini.initials[0] = '\0';
    strcat(ply->ini.initials, ply->ini.chr1);
    strcat(ply->ini.initials, ply->ini.chr2);
    strcat(ply->ini.initials, ply->ini.chr3);
    ply->ini.update = 1;
    hrc->x = 220;
    hrc->y = 270;
}

/* Kallar på funktioner som visar kvalificeringsskärmen */
void call_drw_qualify(chr *chr, int s, int w)
{
    size_t i;
    drw_qualify(w);
    for(i = 0; i < s; i++) {
        drw_chr(chr[i].x, chr[i].y, chr[i].val);
    }
}

/* Kallar på funktionen som ritar explosionerna */
void call_drw_xpl(xpl *xpl, int s)
{
    size_t i;
    
    for(i = 0; i < s; i++) {
        if(xpl[i].live)
            drw_xpl(xpl[i].x, xpl[i].y, xpl[i].radius);
    }
}

/* Kallar på funktionen som ritar spelar bomberna */
void call_drw_bom(bom *bom, int s)
{
    size_t i;
    
    for(i = 0; i < s; i++) {
        if(bom[i].live) 
            drw_bom(bom[i].x, bom[i].y);
    }
}

/* Kallar på funktionen som ritar spelar skotten */
void call_drw_bul(bul *bul, int s)
{
    size_t i;
    
    for(i = 0; i < s; i++) {
        if(bul[i].live) 
            drw_bul(bul[i].x, bul[i].y);
    }
}

/* Kallar på funktionen som ritar lasrar */
void call_drw_las(las *las, int s)
{
    size_t i;
    
    for(i = 0; i < s; i++) {
        if(las[i].live) 
            drw_las(las[i].x, las[i].y);
    }
}

/* Kallar på funktionen som ritar linjer*/
void call_drw_lne(lne *lne, int s)
{
    size_t i;
    
    for(i = 0; i < s; i++) {
        if(lne[i].live)
            drw_lne(lne[i].x1, lne[i].y1, lne[i].x2, lne[i].y2);
    }
}

/* Kallar på funktionen som ritar byggnader */
void call_drw_bld(bld *bld, int s)
{
    size_t i;
    
    for(i = 0; i < s; i++) {
        if(bld[i].live)
            drw_bld(bld[i].x, bld[i].y);
    }
}

void call_drw_ful(ful *ful, int s)
{
    size_t i;
    
    for(i = 0; i < s; i++) {
        if(ful[i].live)
            drw_ful(ful[i].x, ful[i].y);
    }
}

/* Kallar på funktionen som ritar kometer */
void call_drw_com(com *com, int s)
{
    size_t i;
    
    for(i = 0; i < s; i++) {
        if(com[i].live)
            drw_com(com[i].x, com[i].y);
    }
}

/* Kallar på funktionen som ritar missiler */
void call_drw_mis(mis *mis, int s)
{
    size_t i;
    
    for(i = 0; i < s; i++) {
        if(mis[i].live)
            drw_mis(mis[i].x, mis[i].y, mis[i].fired);
    }
}

void call_drw_cba(cba *cba, int s)
{
    size_t i;
    
    for(i = 0; i < s; i++) {
        if(cba[i].live)
            drw_cba(cba[i].x, cba[i].y);
    }
}

/* Kallar på funktionen som ritar ufon */
void call_drw_ufo(ufo *ufo, int s)
{
    size_t i;
    
    for(i = 0; i < s; i++) {
        if(ufo[i].live)
            drw_ufo(ufo[i].x, ufo[i].y);
    }
}