#pragma once

#include "wled.h"

//
// v2 usermod that provides data about modes and
// palettes to other usermods. Notably it provides:
// * A direct method for a mode or palette name
// * Ability to retrieve mode and palette names in 
//   alphabetical order
// 
// char **getModesQStrings()
// Provides an array of char* (pointers) to the names of the
// palettes within JSON_mode_names, in the same order as 
// JSON_mode_names. These strings end in double quote (")
// (or \0 if there is a problem).
//
// byte *getModesAlphaIndexes()
// An array of byte designating the indexes of names of the
// modes in alphabetical order. "Solid" will always remain 
// at the front of the list.
//
// char **getPalettesQStrings()
// Provides an array of char* (pointers) to the names of the
// palettes within JSON_palette_names, in the same order as 
// JSON_palette_names. These strings end in double quote (")
// (or \0 if there is a problem).
//
// byte *getPalettesAlphaIndexes()
// An array of byte designating the indexes of names of the
// palettes in alphabetical order. "Default" and those
// starting with "(" will always remain at the front of the list.
//

// Number of modes at the start of the list to not sort
#define MODE_SORT_SKIP_COUNT 1

// Which list is being sorted
char **listBeingSorted = nullptr;

/**
 * Modes and palettes are stored as strings that
 * end in a quote character. Compare two of them.
 * We are comparing directly within either
 * JSON_mode_names or JSON_palette_names.
 */
int re_qstringCmp(const void *ap, const void *bp) {
    char *a = listBeingSorted[*((byte *)ap)];
    char *b = listBeingSorted[*((byte *)bp)];
    int i = 0;
    do {
        char aVal = pgm_read_byte_near(a + i);
        if (aVal >= 97 && aVal <= 122) {
            // Lowercase
            aVal -= 32;
        }
        char bVal = pgm_read_byte_near(b + i);
        if (bVal >= 97 && bVal <= 122) {
            // Lowercase
            bVal -= 32;
        }
        // Relly we shouldn't ever get to '\0'
        if (aVal == '"' || bVal == '"' || aVal == '\0' || bVal == '\0') {
            // We're done. one is a substring of the other
            // or something happenend and the quote didn't stop us.
            if (aVal == bVal) {
                // Same value, probably shouldn't happen
                // with this dataset
                return 0;
            }
            else if (aVal == '"' || aVal == '\0') {
                return -1;
            }
            else {
                return 1;
            }
        }
        if (aVal == bVal) {
            // Same characters. Move to the next.
            i++;
            continue;
        }
        // We're done
        if (aVal < bVal) {
            return -1;
        }
        else {
            return 1;
        }
    } while (true);
    // We shouldn't get here.
    return 0;
}

class ModeSortUsermod : public Usermod {
private:

    // Pointers the start of the mode names within JSON_mode_names
    char **modes_qstrings = nullptr;

    // Array of mode indexes in alphabetical order.
    byte *modes_alpha_indexes = nullptr;

    // Pointers the start of the palette names within JSON_palette_names
    char **palettes_qstrings = nullptr;

    // Array of palette indexes in alphabetical order.
    byte *palettes_alpha_indexes = nullptr;

public:
    /**
     * setup() is called once at boot. WiFi is not yet connected at this point.
     * You can use it to initialize variables, sensors or similar.
     */
    void setup() {
        // Sort the modes and palettes on startup
        // as they are guarantted to change.
        sortModesAndPalettes();
    }

    char **getModesQStrings() {
        return modes_qstrings;
    }

    byte *getModesAlphaIndexes() {
        return modes_alpha_indexes;
    }

    char **getPalettesQStrings() {
        return palettes_qstrings;
    }

    byte *getPalettesAlphaIndexes() {
        return palettes_alpha_indexes;
    }

    /**
     * This Usermod doesn't have anything for loop.
     */
    void loop() {}

    /**
     * Sort the modes and palettes to the index arrays
     * modes_alpha_indexes and palettes_alpha_indexes.
     */
    void sortModesAndPalettes() {
        modes_qstrings = re_findModeStrings(JSON_mode_names, strip.getModeCount());
        modes_alpha_indexes = re_initIndexArray(strip.getModeCount());
        re_sortModes(modes_qstrings, modes_alpha_indexes, strip.getModeCount(), MODE_SORT_SKIP_COUNT);

        palettes_qstrings = re_findModeStrings(JSON_palette_names, strip.getPaletteCount());
        palettes_alpha_indexes = re_initIndexArray(strip.getPaletteCount());

        int skipPaletteCount = 1;
        while (true) {
            // How many palette names start with '*' and should not be sorted?
            // (Also skipping the first one, 'Default').
            if (pgm_read_byte_near(palettes_qstrings[skipPaletteCount]) == '*') {
                skipPaletteCount++;
            }
            else {
                break;
            }
        }

        re_sortModes(palettes_qstrings, palettes_alpha_indexes, strip.getPaletteCount(), skipPaletteCount);
    }

    byte *re_initIndexArray(int numModes) {
        byte *indexes = (byte *)malloc(sizeof(byte) * numModes);
        for (byte i = 0; i < numModes; i++) {
            indexes[i] = i;
        }
        return indexes;
    }

    /**
     * Return an array of mode or palette names from the JSON string.
     * They don't end in '\0', they end in '"'. 
     */
    char **re_findModeStrings(const char json[], int numModes) {
        char **modeStrings = (char **)malloc(sizeof(char *) * numModes);
        uint8_t modeIndex = 0;
        bool insideQuotes = false;
        // advance past the mark for markLineNum that may exist.
        char singleJsonSymbol;

        // Find the mode name in JSON
        bool complete = false;
        for (size_t i = 0; i < strlen_P(json); i++) {
            singleJsonSymbol = pgm_read_byte_near(json + i);
            switch (singleJsonSymbol) {
            case '"':
                insideQuotes = !insideQuotes;
                if (insideQuotes) {
                    // We have a new mode or palette
                    modeStrings[modeIndex] = (char *)(json + i + 1);
                }
                break;
            case '[':
                break;
            case ']':
                complete = true;
                break;
            case ',':
                modeIndex++;
            default:
                if (!insideQuotes) {
                    break;
                }
            }
            if (complete) {
                break;
            }
        }
        return modeStrings;
    }

    /**
   * Sort either the modes or the palettes using quicksort.
   */
    void re_sortModes(char **modeNames, byte *indexes, int count, int numSkip) {
        listBeingSorted = modeNames;
        qsort(indexes + numSkip, count - numSkip, sizeof(byte), re_qstringCmp);
        listBeingSorted = nullptr;
    }

    /*
     * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
     * Values in the state object may be modified by connected clients
     */
    void addToJsonState(JsonObject &root) {}

    /*
     * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
     * Values in the state object may be modified by connected clients
     */
    void readFromJsonState(JsonObject &root) {}

    /*
     * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
     * This could be used in the future for the system to determine whether your usermod is installed.
     */
    uint16_t getId()
    {
        return USERMOD_ID_MODE_SORT;
    }
};
