#include <stdlib.h>
#include <string.h>
#include "discgolf_face.h"    // Remember to change number of courses in this file
#include "watch.h"          
#include "watch_utility.h"

/* 
 * Keep track of scores in discgolf or golf!
 * The watch face operates in three different modes:
 *
 *  - dg_setting: Select a course
 *      Enter this mode by holding down the light button. The screen will display
 *      the label for the hole and the lowest score since last boot. 
 *      Press alarm to loop through the holes. Press the light button to make a 
 *      selection. This will reset all scores and start a new game in dg_idle mode.
 *
 *  -dg_idle: We're playing a hole
 *      This either shows your current score relative to par, or the score for a
 *      particular hole. 
 *      At the start of a game, press alarm to loop through the holes and leave it
 *      your starting hole. For optimal experience, play the course linearly after that
 *      If you're viewing the hole you're supposed to be playing, the watch face will
 *      display your score relative to par.
 *      Use the alarm button to view other holes than the one you're playing, in which
 *      case the input score for that hole will be displayed, in case it needs changing.
 *      Long press the alarm button to snap back to currently playing hole.
 *      To input scores for a hole in this mode, press the light button.
 *
 *  -dg_scoring: Input score for a hole
 *      In this mode, if the score is 0 (hasn't been entered during this round),
 *      it will blink, indicating we're in scoring mode. Press the alarm button
 *      to increment the score up until 15, in which case it loops back to 0. 
 *      Press the light button to save the score for that hole, advance one hole
 *      if you're not editing an already input score, and returning to idle mode.
 *
 *  When all scores have been entered, the LAP indicator turns on. At that point, if we enter 
 *  dg_setting to select a course, the score for that round is evaluated against the current 
 *  lowest score for that course, and saved if it is better.
*/
///////////////////////////////////////////////////////////////////////////////////////////////
// Enter course data
/* Initialize lowest scores with a high number */
int8_t best[courses];

static const uint8_t pars[][18] = {
   { 3, 3, 4, 3, 3, 3, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },    // Grafarholt
   { 3, 4, 3, 3, 4, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3 },    // Gufunes
   { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0 },    // Vífilsstaðir
   { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0 },    // Dalvegur
   { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0 },    // Laugardalur
   { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0 },    // Guðmundarlundur
   { 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 },    // Víðistaðatún
   { 3, 3, 3, 4, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 },    // Fossvogur
   { 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 },    // Klambratún
   { 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 },    // Seljahverfi
   { 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }     // Fella- og Hóla
};
static const uint8_t holes[] = {                                // Number of holes on each course
    18,
    18,
    10,
    10,
    10,
    10,
    9,
    9,
    9,
    9,
    9
};
/* Two-letter descriptive labels, second field can only be A, B, C, D, E, F, H, I, J, L, N, O, R, T, U and X. */
static const char labels[][2] = {
    { 'G', 'H' },
    { 'G', 'N' },
    { 'V', 'I' },
    { 'D', 'V' },
    { 'L', 'A' },
    { 'G', 'L' },
    { 'V', 'T' },
    { 'F', 'V' },
    { 'K', 'T' },
    { 'S', 'E' },
    { 'F', 'H' }
};

// End of course data
///////////////////////////////////////////////////////////////////////////////////////////////

/* Beep function */
static inline void beep(movement_settings_t *settings) {
    if (settings->bit.button_should_sound) watch_buzzer_play_note(BUZZER_NOTE_C7, 50);
}

/* Prep for a new round */
static inline void reset(discgolf_state_t *state) {
    for (int i = 0; i < holes[state->course]; i++) {
        state->scores[i] = 0;
    }
    state->hole = 1;
    watch_clear_indicator(WATCH_INDICATOR_LAP);
    return;
}

/* Total number of throws so far */
static inline uint8_t score_sum(discgolf_state_t *state) {
    uint8_t sum = 0;
    for (int i = 0; i < holes[state->course]; i++) {
        sum = sum + state->scores[i];
    }
    return sum;
}

/* Count how many holes have been played */
static inline uint8_t count_played(discgolf_state_t *state) {
    uint8_t holes_played = 0;
    for (int i = 0; i < holes[state->course]; i++) {
        if (state->scores[i] > 0) holes_played++;
    }
    return holes_played;
}


/* Calculate the current score relative to par */
static inline int8_t calculate_score(discgolf_state_t *state) {
    uint8_t par_sum = 0;
    uint8_t score_sum = 0;
    
    for (int i = 0; i < holes[state->course]; i++) {
        if (state->scores[i] > 0) {
            par_sum = par_sum + pars[state->course][i];
            score_sum = score_sum + state->scores[i];
        }
    }
    return score_sum - par_sum;
}

/* Store score if it's the best so far */
static inline void store_best(discgolf_state_t *state) {
    uint8_t played = count_played(state);
    if ( played == holes[state->course] ) {
        int8_t high_score = calculate_score(state);
        if (high_score < best[state->course] ) {
            best[state->course] = high_score;
        }
    }
}

/* Configuration at boot, the high score array can be initialized with your high scores if they're known */
void discgolf_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
    (void) settings;
    (void) watch_face_index;

    if (*context_ptr == NULL) {
       *context_ptr = malloc(sizeof(discgolf_state_t));
       discgolf_state_t *state = (discgolf_state_t *)*context_ptr;
       memset(*context_ptr, 0, sizeof(discgolf_state_t));
       state->hole = 1;
       state->course = 0;
       state->playing = holes[state->course] + 1;
       for (int i = 0; i < courses; i++) best[i] = 99;
       state->mode = dg_setting;
    }
}

void discgolf_face_activate(movement_settings_t *settings, void *context) {
    (void) settings;
    watch_clear_colon();
    discgolf_state_t *state = (discgolf_state_t *)context;

    /* If we were playing, go to current hole */
    if (state->playing <= holes[0]) {
        state->hole = state->playing;
    }
    /* Set LAP if round finished */
    if (count_played(state) == holes[state->course] ) {
        watch_set_indicator(WATCH_INDICATOR_LAP);
    }
    movement_request_tick_frequency(4);
}

bool discgolf_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
    (void) settings;
    
    discgolf_state_t *state = (discgolf_state_t *)context;

    switch (event.event_type) {
        case EVENT_TIMEOUT:
            /* Snap to first screen if we're not playing*/
            if ( count_played(state) == holes[state->course] || state->mode == dg_setting) {
                movement_move_to_face(0);
            }
            /* Cancel scoring if timed out */
            if (state->mode == dg_scoring) {
                state->scores[state->hole] = 0;
                state->mode = dg_idle;
            }
            break;
        /* Advance if not scoring */
        case EVENT_MODE_BUTTON_UP:
            if ( state->mode != dg_scoring ) {
				movement_move_to_next_face();
            }
            break;
        /* Go to default face if not scoring */
        case EVENT_MODE_LONG_PRESS:
            if ( state->mode != dg_scoring ) {
                movement_move_to_face(0);
            }
            break;
        case EVENT_LIGHT_BUTTON_UP:
            switch ( state->mode ) {
                case dg_idle:
                    /* Check if selected hole is the first one */
                    if ( score_sum(state) == 0 ) {
                        state->playing = state->hole;
                    }
                    /* Enter scoring mode */
                    state->mode = dg_scoring;
                    break;
                case dg_scoring:
                    /* Set the LAP indicator if all scores are entered */
                    if (count_played(state) == holes[state->course] ) {
                        watch_set_indicator(WATCH_INDICATOR_LAP);
                    }
                    /* Advance to next hole if not editing previously set score */
                    if ( state->hole == state->playing ) {
                        if (state->hole < holes[state->course]) state->hole++;
                        else state->hole = 1;
                        if (state->playing < holes[state->course]) state->playing++;
                        else state->playing = 1;
                    }
                    /* Return to idle */
                    state->mode = dg_idle;
                    break;
                case dg_setting:
                    /* Return to idle */
                    state->playing = holes[state->course] + 1;
                    state->mode = dg_idle;
                    break;
            }
            beep(settings);
            break;
        case EVENT_ALARM_BUTTON_UP:
            switch (state->mode) {
                /* Setting, loop through courses */
                case dg_setting:
                    state->course = (state->course + 1) % courses;
                    break;
                /* Scoring, increment score for current hole */
                case dg_scoring:
                    state->scores[state->hole - 1] = (state->scores[state->hole - 1] + 1) % 16;   // Loop around at 15
                    break;
                /* Idle, loop through holes */
                case dg_idle:
                    if (state->hole < holes[state->course]) {
                        state->hole++;
                    } else { state->hole = 1; }
                    break;
            }
            break;
        case EVENT_LIGHT_LONG_PRESS:
            /* Enter setting mode, reset state */
            if ( state->mode == dg_idle ) {
                state->mode = dg_setting;
                store_best(state);
                reset(state);
                beep(settings);
            }
            break;
        case EVENT_ALARM_LONG_PRESS:
            /* Snap back to currently playing hole if we've established one*/
            if ( (state->mode == dg_idle) && (state->hole != state->playing) && (state->playing <= holes[state->course]) ) {
                state->hole = state->playing;
                beep(settings);
            }
            break;
        default:
            break;
    }

    char buf[21];
    char prefix;
    int8_t diff;

    switch (state->mode) {
        /* Setting mode, display course label and high score */
        case dg_setting:
            if ( best[state->course] < 0 ) { 
                prefix = '-';
            } else { prefix = ' '; }
            sprintf(buf, "%c%c   %c%2d  ", labels[state->course][0], labels[state->course][1], prefix, abs(best[state->course]));
            break;
        /* Idle, show relative or input score */
        case dg_idle:
            if (state->hole == state->playing) {
                diff = calculate_score(state);
                if ( diff < 0 ) {
                    prefix = '-';
                } else { prefix = ' '; }
                sprintf(buf, "%c%c%2d %c%2d  ", labels[state->course][0], labels[state->course][1], state->hole, prefix, abs(diff));
            } else {
                sprintf(buf, "%c%c%2d  %2d  ", labels[state->course][0], labels[state->course][1], state->hole, state->scores[state->hole - 1]);
            }
            break;
        /* Scoring, show set score */
        case dg_scoring:
            sprintf(buf, "%c%c%2d  %2d  ", labels[state->course][0], labels[state->course][1], state->hole, state->scores[state->hole - 1]);
            break;
    }

    /* Blink during scoring */
    if (event.subsecond % 2 && state->mode == dg_scoring) {
        buf[6] = buf[7] = ' ';
    }

    /* Draw screen */
    watch_display_string(buf, 0);

    return true;
}

void discgolf_face_resign(movement_settings_t *settings, void *context) {
    (void) settings;
    (void) context;
    watch_clear_indicator(WATCH_INDICATOR_LAP);
}
