#include "ProfileManager.h"
#include "ResourceManager.h"
#include "RGBController_Dummy.h"
#include "LogManager.h"
#include "filesystem.h"
#include <fstream>
#include <iostream>
#include <cstring>


#define OPENRGB_PROFILE_HEADER  "OPENRGB_PROFILE"
#define OPENRGB_PROFILE_VERSION OPENRGB_SDK_PROTOCOL_VERSION

ProfileManager::ProfileManager(std::string config_dir)
{
    configuration_directory = config_dir;
    UpdateProfileList();
}

ProfileManager::~ProfileManager()
{

}

bool ProfileManager::SaveProfile(std::string profile_name, bool sizes)
{
    /*---------------------------------------------------------*\
    | Get the list of controllers from the resource manager     |
    \*---------------------------------------------------------*/
    std::vector<RGBController *> controllers = ResourceManager::get()->GetRGBControllers();

    /*---------------------------------------------------------*\
    | If a name was entered, save the profile file              |
    \*---------------------------------------------------------*/
    if(profile_name != "")
    {
        /*---------------------------------------------------------*\
        | Extension .orp - OpenRgb Profile                          |
        \*---------------------------------------------------------*/
        std::string filename = profile_name;

        /*---------------------------------------------------------*\
        | Determine file extension                                  |
        \*---------------------------------------------------------*/
        if(sizes)
        {
            filename += ".ors";
        }
        else
        {
            filename += ".orp";
        }

        /*---------------------------------------------------------*\
        | Open an output file in binary mode                        |
        \*---------------------------------------------------------*/
        std::ofstream controller_file(configuration_directory + filename, std::ios::out | std::ios::binary | std::ios::trunc);

        /*---------------------------------------------------------*\
        | Write header                                              |
        | 16 bytes - "OPENRGB_PROFILE"                              |
        | 4 bytes - Version, unsigned int                           |
        \*---------------------------------------------------------*/
        unsigned int profile_version = OPENRGB_PROFILE_VERSION;
        controller_file.write(OPENRGB_PROFILE_HEADER, 16);
        controller_file.write((char *)&profile_version, sizeof(unsigned int));

        /*---------------------------------------------------------*\
        | Write controller data for each controller                 |
        \*---------------------------------------------------------*/
        for(std::size_t controller_index = 0; controller_index < controllers.size(); controller_index++)
        {
            unsigned char *controller_data = controllers[controller_index]->GetDeviceDescription(profile_version);
            unsigned int controller_size;

            memcpy(&controller_size, controller_data, sizeof(controller_size));

            controller_file.write((const char *)controller_data, controller_size);

            delete[] controller_data;
        }

        /*---------------------------------------------------------*\
        | Close the file when done                                  |
        \*---------------------------------------------------------*/
        controller_file.close();

        /*---------------------------------------------------------*\
        | Update the profile list                                   |
        \*---------------------------------------------------------*/
        UpdateProfileList();

        return(true);
    }
    else
    {
        return(false);
    }
}

void ProfileManager::SetConfigurationDirectory(std::string directory)
{
    configuration_directory = directory;
    UpdateProfileList();
}

bool ProfileManager::LoadProfile(std::string profile_name)
{
    return(LoadProfileWithOptions(profile_name, false, true));
}

bool ProfileManager::LoadSizeFromProfile(std::string profile_name)
{
    return(LoadProfileWithOptions(profile_name, true, false));
}

std::vector<RGBController*> ProfileManager::LoadProfileToList
    (
    std::string     profile_name,
    bool            sizes
    )
{
    std::vector<RGBController*> temp_controllers;
    unsigned int                controller_size;
    unsigned int                controller_offset = 0;

    std::string filename = configuration_directory + profile_name;

    /*---------------------------------------------------------*\
    | Determine file extension                                  |
    \*---------------------------------------------------------*/
    if(sizes)
    {
        filename += ".ors";
    }
    else
    {
        filename += ((filename.substr(filename.size() - 4)==".orp") ? "" : ".orp");
    }

    /*---------------------------------------------------------*\
    | Open input file in binary mode                            |
    \*---------------------------------------------------------*/
    std::ifstream controller_file(filename, std::ios::in | std::ios::binary);

    /*---------------------------------------------------------*\
    | Read and verify file header                               |
    \*---------------------------------------------------------*/
    char            profile_string[16];
    unsigned int    profile_version;

    controller_file.read(profile_string, 16);
    controller_file.read((char *)&profile_version, sizeof(unsigned int));

    /*---------------------------------------------------------*\
    | Profile version started at 1 and protocol version started |
    | at 0.  Version 1 profiles should use protocol 0, but 2 or |
    | greater should be synchronized                            |
    \*---------------------------------------------------------*/
    if(profile_version == 1)
    {
        profile_version = 0;
    }

    controller_offset += 16 + sizeof(unsigned int);
    controller_file.seekg(controller_offset);

    if(strcmp(profile_string, OPENRGB_PROFILE_HEADER) == 0)
    {
        if(profile_version <= OPENRGB_PROFILE_VERSION)
        {
            /*---------------------------------------------------------*\
            | Read controller data from file until EOF                  |
            \*---------------------------------------------------------*/
            while(!(controller_file.peek() == EOF))
            {
                controller_file.read((char *)&controller_size, sizeof(controller_size));

                unsigned char *controller_data = new unsigned char[controller_size];

                controller_file.seekg(controller_offset);

                controller_file.read((char *)controller_data, controller_size);

                RGBController_Dummy *temp_controller = new RGBController_Dummy();

                temp_controller->ReadDeviceDescription(controller_data, profile_version);

                temp_controllers.push_back(temp_controller);

                delete[] controller_data;

                controller_offset += controller_size;
                controller_file.seekg(controller_offset);
            }
        }
    }

    return(temp_controllers);
}

bool ProfileManager::LoadDeviceFromListWithOptions
    (
    std::vector<RGBController*>&    temp_controllers,
    std::vector<bool>&              temp_controller_used,
    RGBController*                  load_controller,
    bool                            load_size,
    bool                            load_settings
    )
{
    for(std::size_t temp_index = 0; temp_index < temp_controllers.size(); temp_index++)
    {
        RGBController *temp_controller = temp_controllers[temp_index];

        /*---------------------------------------------------------*\
        | Do not compare location string for HID devices, as the    |
        | location string may change between runs as devices are    |
        | connected and disconnected                                |
        \*---------------------------------------------------------*/
        bool compare_location = true;

        if(load_controller->location.find("HID: ") == 0)
        {
            compare_location = false;
        }

        /*---------------------------------------------------------*\
        | Test if saved controller data matches this controller     |
        \*---------------------------------------------------------*/
        if((temp_controller_used[temp_index] == false                                               )
         &&(temp_controller->type            == load_controller->type                               )
         &&(temp_controller->name            == load_controller->name                               )
         &&(temp_controller->description     == load_controller->description                        )
         &&(temp_controller->version         == load_controller->version                            )
         &&(temp_controller->serial          == load_controller->serial                             )
         &&((temp_controller->location       == load_controller->location   ) || (!compare_location)))
        {
            /*---------------------------------------------------------*\
            | Set used flag for this temp device                        |
            \*---------------------------------------------------------*/
            temp_controller_used[temp_index] = true;

            /*---------------------------------------------------------*\
            | Update zone sizes if requested                            |
            \*---------------------------------------------------------*/
            if(load_size)
            {
                if(temp_controller->zones.size() == load_controller->zones.size())
                {
                    for(std::size_t zone_idx = 0; zone_idx < temp_controller->zones.size(); zone_idx++)
                    {
                        if((temp_controller->zones[zone_idx].name       == load_controller->zones[zone_idx].name      )
                         &&(temp_controller->zones[zone_idx].type       == load_controller->zones[zone_idx].type      )
                         &&(temp_controller->zones[zone_idx].leds_min   == load_controller->zones[zone_idx].leds_min  )
                         &&(temp_controller->zones[zone_idx].leds_max   == load_controller->zones[zone_idx].leds_max  )
                         &&(temp_controller->zones[zone_idx].leds_count != load_controller->zones[zone_idx].leds_count))
                        {
                            load_controller->ResizeZone(zone_idx, temp_controller->zones[zone_idx].leds_count);
                        }
                    }
                }
            }

            /*---------------------------------------------------------*\
            | Update settings if requested                              |
            \*---------------------------------------------------------*/
            if(load_settings)
            {
                /*---------------------------------------------------------*\
                | Update all modes                                          |
                \*---------------------------------------------------------*/
                if(temp_controller->modes.size() == load_controller->modes.size())
                {
                    for(std::size_t mode_index = 0; mode_index < temp_controller->modes.size(); mode_index++)
                    {
                        if((temp_controller->modes[mode_index].name       == load_controller->modes[mode_index].name      )
                         &&(temp_controller->modes[mode_index].value      == load_controller->modes[mode_index].value     )
                         &&(temp_controller->modes[mode_index].flags      == load_controller->modes[mode_index].flags     )
                         &&(temp_controller->modes[mode_index].speed_min  == load_controller->modes[mode_index].speed_min )
                         &&(temp_controller->modes[mode_index].speed_max  == load_controller->modes[mode_index].speed_max )
                         &&(temp_controller->modes[mode_index].colors_min == load_controller->modes[mode_index].colors_min)
                         &&(temp_controller->modes[mode_index].colors_max == load_controller->modes[mode_index].colors_max))
                        {
                            load_controller->modes[mode_index].speed      = temp_controller->modes[mode_index].speed;
                            load_controller->modes[mode_index].direction  = temp_controller->modes[mode_index].direction;
                            load_controller->modes[mode_index].color_mode = temp_controller->modes[mode_index].color_mode;

                            load_controller->modes[mode_index].colors.resize(temp_controller->modes[mode_index].colors.size());

                            for(std::size_t mode_color_index = 0; mode_color_index < temp_controller->modes[mode_index].colors.size(); mode_color_index++)
                            {
                                load_controller->modes[mode_index].colors[mode_color_index] = temp_controller->modes[mode_index].colors[mode_color_index];
                            }
                        }

                    }

                    load_controller->active_mode = temp_controller->active_mode;
                }

                /*---------------------------------------------------------*\
                | Update all colors                                         |
                \*---------------------------------------------------------*/
                if(temp_controller->colors.size() == load_controller->colors.size())
                {
                    for(std::size_t color_index = 0; color_index < temp_controller->colors.size(); color_index++)
                    {
                        load_controller->colors[color_index] = temp_controller->colors[color_index];
                    }
                }

                return(true);
            }
        }
    }

    return(false);
}

bool ProfileManager::LoadProfileWithOptions
    (
    std::string     profile_name,
    bool            load_size,
    bool            load_settings
    )
{
    std::vector<RGBController*> temp_controllers;
    std::vector<bool>           temp_controller_used;
    bool                        ret_val = false;

    /*---------------------------------------------------------*\
    | Get the list of controllers from the resource manager     |
    \*---------------------------------------------------------*/
    std::vector<RGBController *> controllers = ResourceManager::get()->GetRGBControllers();

    /*---------------------------------------------------------*\
    | Open input file in binary mode                            |
    \*---------------------------------------------------------*/
    temp_controllers = LoadProfileToList(profile_name);

    /*---------------------------------------------------------*\
    | Set up used flag vector                                   |
    \*---------------------------------------------------------*/
    temp_controller_used.resize(temp_controllers.size());

    for(unsigned int controller_idx = 0; controller_idx < temp_controller_used.size(); controller_idx++)
    {
        temp_controller_used[controller_idx] = false;
    }

    /*---------------------------------------------------------*\
    | Loop through all controllers.  For each controller, search|
    | all saved controllers until a match is found              |
    \*---------------------------------------------------------*/
    for(std::size_t controller_index = 0; controller_index < controllers.size(); controller_index++)
    {
        ret_val = LoadDeviceFromListWithOptions(temp_controllers, temp_controller_used, controllers[controller_index], load_size, load_settings);
        std::string current_name = controllers[controller_index]->name + " @ " + controllers[controller_index]->location;
        LOG_NOTICE("Profile loading: %s for %s", ( ret_val ? "Succeeded" : "FAILED!" ), current_name.c_str());
    }

    /*---------------------------------------------------------*\
    | Delete all temporary controllers                          |
    \*---------------------------------------------------------*/
    for(unsigned int controller_idx = 0; controller_idx < temp_controllers.size(); controller_idx++)
    {
        delete temp_controllers[controller_idx];
    }

    return(ret_val);
}

void ProfileManager::DeleteProfile(std::string profile_name)
{
    remove((configuration_directory + profile_name + ".orp").c_str());

    UpdateProfileList();
}

void ProfileManager::UpdateProfileList()
{
    profile_list.clear();

    /*---------------------------------------------------------*\
    | Load profiles by looking for .orp files in current dir    |
    \*---------------------------------------------------------*/
    for(const auto & entry : filesystem::directory_iterator(configuration_directory))
    {
        std::string filename = entry.path().filename().string();

        if(filename.find(".orp") != std::string::npos)
        {
            LOG_NOTICE("Found file: %s attempting to validate header", filename.c_str());

            /*---------------------------------------------------------*\
            | Open input file in binary mode                            |
            \*---------------------------------------------------------*/
            std::ifstream profile_file(configuration_directory + filename, std::ios::in | std::ios::binary);

            /*---------------------------------------------------------*\
            | Read and verify file header                               |
            \*---------------------------------------------------------*/
            char            profile_string[16];
            unsigned int    profile_version;

            profile_file.read(profile_string, 16);
            profile_file.read((char *)&profile_version, sizeof(unsigned int));

            if(strcmp(profile_string, OPENRGB_PROFILE_HEADER) == 0)
            {
                if(profile_version <= OPENRGB_PROFILE_VERSION)
                {
                    /*---------------------------------------------------------*\
                    | Add this profile to the list                              |
                    \*---------------------------------------------------------*/
                    filename.erase(filename.length() - 4);
                    profile_list.push_back(filename);

                    LOG_NOTICE("Valid v%i profile found for %s", profile_version, filename.c_str());
                }
            }

            profile_file.close();
        }
    }
}

unsigned char * ProfileManager::GetProfileListDescription()
{
    unsigned int data_ptr = 0;
    unsigned int data_size = 0;

    /*---------------------------------------------------------*\
    | Calculate data size                                       |
    \*---------------------------------------------------------*/
     unsigned short num_profiles = profile_list.size();

     data_size += sizeof(data_size);
     data_size += sizeof(num_profiles);

    for(unsigned int i = 0; i < num_profiles; i++)
    {
        data_size += sizeof (unsigned short);
        data_size += strlen(profile_list[i].c_str()) + 1;
    }

    /*---------------------------------------------------------*\
    | Create data buffer                                        |
    \*---------------------------------------------------------*/
    unsigned char *data_buf = new unsigned char[data_size];

    /*---------------------------------------------------------*\
    | Copy in data size                                         |
    \*---------------------------------------------------------*/
    memcpy(&data_buf[data_ptr], &data_size, sizeof(data_size));
    data_ptr += sizeof(data_size);

    /*---------------------------------------------------------*\
    | Copy in num_profiles                                      |
    \*---------------------------------------------------------*/
    memcpy(&data_buf[data_ptr], &num_profiles, sizeof(num_profiles));
    data_ptr += sizeof(num_profiles);

    /*---------------------------------------------------------*\
    | Copy in profile names (size+data)                         |
    \*---------------------------------------------------------*/
    for(unsigned int i = 0; i < num_profiles; i++)
    {
        unsigned short name_len = strlen(profile_list[i].c_str()) + 1;

        memcpy(&data_buf[data_ptr], &name_len, sizeof(name_len));
        data_ptr += sizeof(name_len);

        strcpy((char *)&data_buf[data_ptr], profile_list[i].c_str());
        data_ptr += name_len;
    }

    return(data_buf);
}
