#include "wires_puzzle.h"

const static char* WIRES_FILE_PATH = "/sdcard/wires.txt";
const static char* TAG = "wires_puzzle";

static int color_name_len[NUM_COLORS] = {
    3, // red
    6, // yellow
    5, // green
    4, // blue
    5, // black
    5, // white
};

static int max(int n0, int n1, int n2, int n3, int n4, int n5);

void wires_to_string(WireColor* wires, char* out_wires_string) {
    for (int i = 0; i < NUM_WIRES; i++) {
        switch (wires[i]) {
            case WireColor::wire_red:
                out_wires_string[i] = 'r';
                break;
            case WireColor::wire_yellow:
                out_wires_string[i] = 'y';
                break;
            case WireColor::wire_green:
                out_wires_string[i] = 'g';
                break;
            case WireColor::wire_blue:
                out_wires_string[i] = 'b';
                break;
            case WireColor::wire_black:
                out_wires_string[i] = 'k';
                break;
            case WireColor::wire_white:
                out_wires_string[i] = 'w';
                break;
        }
    }
}
void cut_to_string(bool* cut, char* out_cut_string) {
    for (int i = 0; i < NUM_WIRES; i++) {
        if (cut[i]) {
            out_cut_string[i] = 'c';
        } else {
            out_cut_string[i] = ' ';
        }
    }
}

void string_to_wires(char* wires_string, WireColor* out_wires) {
    for (int i = 0; i < NUM_WIRES; i++) {
        switch (wires_string[i]) {
            case 'r':
                out_wires[i] = WireColor::wire_red;
                break;
            case 'y':
                out_wires[i] = WireColor::wire_yellow;
                break;
            case 'g':
                out_wires[i] = WireColor::wire_green;
                break;
            case 'b':
                out_wires[i] = WireColor::wire_blue;
                break;
            case 'k':
                out_wires[i] = WireColor::wire_black;
                break;
            case 'w':
                out_wires[i] = WireColor::wire_white;
                break;
        }
    }
}

void save_wires_to_sd_card(WireColor* wires) {
    FILE* f = fopen(WIRES_FILE_PATH, "w");
    if (f == NULL) {
        ESP_LOGE(TAG, "Failed to open wires file to write");
    }
    char wires_string[NUM_WIRES+1] = {0};
    wires_to_string(wires, wires_string);
    fprintf(f, wires_string);
    fclose(f);
}

void load_wires_from_sd_card(WireColor* out_wires) {
    FILE* f = fopen(WIRES_FILE_PATH, "r");
    if (f == NULL) {
        ESP_LOGW(TAG, "Failed to read wires file. Generating new wires");
        generate_new_wires(out_wires);
        save_wires_to_sd_card(out_wires);
        return;
    }
    char wires_string[NUM_WIRES+1] = {0};
    fgets(wires_string, sizeof(wires_string), f);
    fclose(f);

    string_to_wires(wires_string, out_wires);
}

/// @brief Fills the array with 6 random WiresColors.
/// @param wires and array of len >= 6 to be populated with random colors
void generate_new_wires(WireColor* wires) {
    for (int w = 0; w < NUM_WIRES; w++) {
        // roughly evenly distributed
        uint32_t rand = esp_random() % NUM_COLORS;
        wires[w] = (WireColor)(rand);
    }

    bool cuts[NUM_WIRES] = {0};
    solve_wires(wires, cuts);
    int num_cuts = 0;
    for (int i = 0; i < NUM_WIRES; i++) {
        if (cuts[i]) num_cuts++;
    }

    // regenerate if there are less than 3 cuts.
    if (num_cuts < 3) {
        return generate_new_wires(wires);
    }
}

void solve_wires(WireColor* wires, bool* out_cut) {
    bool debug = false;

    // by default, don't cut any wires
    for (int i = 0; i < NUM_WIRES; i++) {
        out_cut[i] = false;
    }

    // Find all positions of all wire colors
    int red_pos[NUM_WIRES + 1] = {0};
    int red_pos_len = 0;
    int yellow_pos[NUM_WIRES + 1] = {0};
    int yellow_pos_len = 0;
    int green_pos[NUM_WIRES + 1] = {0};
    int green_pos_len = 0;
    int blue_pos[NUM_WIRES + 1] = {0};
    int blue_pos_len = 0;
    int black_pos[NUM_WIRES + 1] = {0};
    int black_pos_len = 0;
    int white_pos[NUM_WIRES + 1] = {0};
    int white_pos_len = 0;

    for (int i = 0; i < NUM_WIRES; i++) {
        if (wires[i] == WireColor::wire_red) {
            red_pos[red_pos_len++] = i;
        } else if (wires[i] == WireColor::wire_yellow) {
            yellow_pos[yellow_pos_len++] = i;
        } else if (wires[i] == WireColor::wire_green) {
            green_pos[green_pos_len++] = i;
        } else if (wires[i] == WireColor::wire_blue) {
            blue_pos[blue_pos_len++] = i;
        } else if (wires[i] == WireColor::wire_black) {
            black_pos[black_pos_len++] = i;
        } else if (wires[i] == WireColor::wire_white) {
            white_pos[white_pos_len++] = i;
        }
    }

    int* list_pos[NUM_COLORS] = {
        red_pos,
        yellow_pos,
        green_pos,
        blue_pos,
        black_pos,
        white_pos
    };
    int list_pos_len[NUM_COLORS] = {
        red_pos_len,
        yellow_pos_len,
        green_pos_len,
        blue_pos_len,
        black_pos_len,
        white_pos_len
    };

    // CUT CHECKS
    
    // 1. cut the second wire of all most common colors
    int max_len = max(red_pos_len, yellow_pos_len, green_pos_len, blue_pos_len, black_pos_len, white_pos_len);
    if (max_len >= 2) {
        for (int i = 0; i < NUM_COLORS; i++) {
            if (list_pos_len[i] == max_len) {
                int idx = list_pos[i][1];
                out_cut[idx] = true;
                if (debug) {
                    printf("C1. cutting %d\n", idx);
                }
            }
        }
    }

    // 2. cut the first wire if it is green or white
    if (wires[0] == WireColor::wire_green || wires[0] == WireColor::wire_white) {
        out_cut[0] = true;
        if (debug) {
            printf("C2. cutting %d\n", 0);
        }
    }

    // 3. cut blue wires in even positions (odd indexes)
    for (int i = 1; i < NUM_WIRES; i += 2) {
        if (wires[i] == WireColor::wire_blue) {
            out_cut[i] = true;
            if (debug) {
                printf("C3. cutting %d\n", i);
            }
        }
    }

    // 4. cut black and yellow wires next to black and yellow wires
    for (int i = 0; i < NUM_WIRES-1; i++) {
        if (
            (wires[i] == WireColor::wire_yellow || wires[i] == WireColor::wire_black) &&
            (wires[i+1] == WireColor::wire_yellow || wires[i+1] == WireColor::wire_black)
        ) {
            out_cut[i] = true;
            out_cut[i+1] = true;
            if (debug) {
                printf("C4. cutting %d, %d\n", i, i+1);
            }
        }
    }

    // 5. cut the last green wire next to a yellow or white wire
    for (int green_idx = green_pos_len-1; green_idx >= 0; green_idx--) {
        int pos = green_pos[green_idx];
        if (
            wires[pos-1] == WireColor::wire_yellow ||
            wires[pos-1] == WireColor::wire_white ||
            wires[pos+1] == WireColor::wire_yellow ||
            wires[pos+1] == WireColor::wire_white
        ) {
            out_cut[pos] = true;
            if (debug) {
                printf("C5. cutting %d\n", pos);
            }
            break;
        }
    }

    // 6. cut all white wires if there is a red wire in the 5th position
    if (wires[4] == WireColor::wire_red) {
        for (int white_idx = 0; white_idx < white_pos_len; white_idx++) {
            out_cut[white_pos[white_idx]] = true;
            if (debug) {
                printf("C6. cutting %d\n", white_pos[white_idx]);
            }
        }
    }

    // 7. cut the first black wire if there are more white wires than green wires
    if (white_pos_len > green_pos_len && black_pos_len > 0) {
        out_cut[black_pos[0]] = true;
        if (debug) {
            printf("C7. cutting %d\n", black_pos[0]);
        }
    }

    // 8. cut all wires in an alternating pattern of 2 colors at least 4 wires long
    for (int i = 0; i < NUM_WIRES-3; i++) {
        if (
            wires[i] == wires[i+2] &&
            wires[i+1] == wires[i+3] &&
            wires[i] == wires[i+1]
        ) {
            out_cut[i] = true;
            out_cut[i+1] = true;
            out_cut[i+2] = true;
            out_cut[i+3] = true;
            if (debug) {
                printf("C8. cutting %d, %d, %d, %d\n", i, i+1, i+2, i+3);
            }
        }
    }

    // 9. cut any wires if their position matches the number of letters in the color's name
    for (int i = 0; i < NUM_WIRES; i++) {
        if (color_name_len[wires[i]] == i+1) {
            out_cut[i] = true;
            if (debug) {
                printf("C9. cutting %d\n", i);
            }
        }
    }

    // 10. cut all red wires if there are no more than 2 wires of the same color
    if (max_len <= 2) {
        for (int i = 0; i < red_pos_len; i++) {
            out_cut[red_pos[i]] = true;
            if (debug) {
                printf("C10. cutting %d\n", red_pos[i]);
            }
        }
    }

    // 11. cut the last wire if it is the same color as the first wire
    if (wires[0] == wires[NUM_WIRES-1]) {
        out_cut[NUM_WIRES-1] = true;
        if (debug) {
            printf("C11. cutting %d\n", NUM_WIRES-1);
        }
    }

    // 12. cut any wire adjacent to both a yellow and blue wire
    for (int i = 0; i < NUM_WIRES-2; i++) {
        if (
            (wires[i] == WireColor::wire_yellow && wires[i+2] == WireColor::wire_blue) || 
            (wires[i] == WireColor::wire_blue && wires[i+2] == WireColor::wire_yellow)
        ) {
            out_cut[i+1] = true;
            if (debug) {
                printf("C12. cutting %d\n", i+1);
            }
        }
    }

    // NEVER CUT

    // 1. never cut blue wires next to red or green wires
    for (int i = 0; i < blue_pos_len; i++) {
        int pos = blue_pos[i];
        if (
            wires[pos-1] == WireColor::wire_red ||
            wires[pos-1] == WireColor::wire_green ||
            wires[pos+1] == WireColor::wire_red ||
            wires[pos+1] == WireColor::wire_green
        ) {
            out_cut[pos] = false;
            if (debug) {
                printf("N1. Never cutting %d\n", pos);
            }
        }
    }

    // 2. never cut white wires if there is at least one red, black and green wire
    if (red_pos_len > 0 && green_pos_len > 0 && black_pos_len > 0) {
        for (int i = 0; i < white_pos_len; i++) {
            out_cut[white_pos[i]] = false;
            if (debug) {
                printf("N2. Never cutting %d\n", white_pos[i]);
            }
        }
    }

    // 3. never cut red or black wires in the 4th or 7th positions
    if (wires[3] == WireColor::wire_red || wires[3] == WireColor::wire_black) {
        out_cut[3] = false;
        if (debug) {
            printf("N3. Never cutting %d\n", 3);
        }
    }
    if (wires[6] == WireColor::wire_red || wires[6] == WireColor::wire_black) {
        out_cut[6] = false;
        if (debug) {
            printf("N3. Never cutting %d\n", 6);
        }
    }

    // 4. never cut wires that have the same color on both sides of it
    for (int i = 0; i < NUM_WIRES-2; i++) {
        if (wires[i] == wires[i+2]) {
            out_cut[i+1] = false;
            if (debug) {
                printf("N4. Never cutting %d\n", i+1);
            }
        }
    }

    // 5. never cut a wire in the 1st, 2nd, or 3rd position if it is the same color as the wire in the 4th position.
    if (wires[0] == wires[3]) {
        out_cut[0] = false;
        if (debug) {
            printf("N5. Never cutting %d\n", 0);
        }
    }
    if (wires[1] == wires[3]) {
        out_cut[1] = false;
        if (debug) {
            printf("N5. Never cutting %d\n", 1);
        }
    }
    if (wires[2] == wires[3]) {
        out_cut[2] = false;
        if (debug) {
            printf("N5. Never cutting %d\n", 2);
        }
    }

    // 6. never cut a blue or green wire in the 8th postion
    if (wires[7] == WireColor::wire_blue || wires[7] == WireColor::wire_green) {
        out_cut[7] = false;
        if (debug) {
            printf("N6. Never cutting %d\n", 7);
        }
    }

    // 7. never cut a wire in the 5th position if there are no yellow wires
    if (yellow_pos_len == 0) {
        out_cut[4] = false;
        if (debug) {
            printf("N7 Never cutting %d\n", 4);
        }
    }
}

static int max(int n0, int n1, int n2, int n3, int n4, int n5) {
    int max = n0;

    if (n1 > max) max = n1;
    if (n2 > max) max = n2;
    if (n3 > max) max = n3;
    if (n4 > max) max = n4;
    if (n5 > max) max = n5;

    return max;
}
