#include "ForgettingExperiments.h"

#include <iostream>
#include <fstream>
#include <chrono>
#include <filesystem>

namespace armarx::armem::server::test
{

void get_information_single_ltm(
        std::map<std::string, ltm::processor::SnapshotFilter::FilterStatistics>& filter
        )
{
    /*
    nlohmann::json info;
    for(const auto& pair: filter.second){
        if(pair.second.accepted + pair.second.rejected < 1){
            ended_without_recording = true;
            ARMARX_INFO << "Trying to not add JSON file because accepted + rejected < 1";
        }
        std::map<std::string, std::string> information;
        information["Memory Name: "] = memoryName;
        information["additional time needed: "] = (std::to_string(pair.second.additional_time.count()) + " sec");
        information["number of accepted elements: "] = std::to_string(pair.second.accepted);
        information["number of rejected elements: "] = std::to_string(pair.second.rejected);
        information["Additional information: "] = pair.second.additional_info;
        information["Similarity-Type: "] = std::to_string(pair.second.similarity_type);
        information["Number of objects compared each time: "] = std::to_string((pair.second.number_of_compared_objects));
        information["Importance type: "] = pair.second.importance_type;
        auto time = std::chrono::high_resolution_clock::to_time_t(pair.second.start_time);
        auto t = localtime(&time);
        std::stringstream ss;
        ss << std::put_time(t, "%Y-%m-%d %H:%M:%S");
        std::string timeString = ss.str();
        information["Filter instance created at: "] = timeString;
        time = std::chrono::high_resolution_clock::to_time_t(pair.second.end_time);
        t = localtime(&time);
        std::stringstream s;
        s << std::put_time(t, "%Y-%m-%d %H:%M:%S");
        timeString = s.str();
        information["Filter instance last used at: "] = timeString;
        info[pair.first] = information;

        armarx::core::time::DateTime start_time = time_stats["Started " + filter.first];
        auto started = start_time.toDateTimeString();
        info["Started"] = started;
        ARMARX_INFO << started;

        armarx::core::time::DateTime stop_time = time_stats["Stopped " + filter.first];
        auto stopped = stop_time.toDateTimeString();
        info["Stopped"] = stopped;
        ARMARX_INFO << stopped;
    }
    jsonData[filter.first] = info;
    info.clear();
    */
}

void save_statistics(
        std::map<std::string, std::map<std::string, ltm::processor::SnapshotFilter::FilterStatistics>> stats,
        std::map<std::string, armarx::core::time::DateTime> time_stats,
        armarx::core::time::DateTime firstStartedRecording,
        armarx::core::time::DateTime firstStoppedRecording,
        std::filesystem::path exportPath,
        std::string exportName,
        std::string memoryName,
        std::string sim_json_data,
        std::string object_memory_json_data)
{
    ARMARX_INFO << "Saving statistics";

    std::filesystem::path d_p;
    try{
        d_p = test::getStatisticsDirectory(exportPath, exportName, memoryName);
        std::filesystem::create_directories(d_p);
        ARMARX_INFO << "Experiments will be saved at: " << d_p.string();
    } catch(...){
        ARMARX_WARNING << "Creating needed directories for saving statistics did not work";
    }

    //json object for statistics:
    nlohmann::json jsonData;

    //get current date:
    auto now = std::chrono::high_resolution_clock::now();
    auto now_ax_datetime = DateTime::Now();
    auto now_time = std::chrono::high_resolution_clock::to_time_t(now);
    auto time = localtime(&now_time);

    std::stringstream ss;
    ss << std::put_time(time, "%Y-%m-%d %H:%M:%S");
    std::string timeString = ss.str();
    std::stringstream s;
    s << std::put_time(time, "%Y%m%d%H%M%S");
    std::string timeID = s.str();
    jsonData["Statistics saved at"] = timeString;

    bool ended_without_recording = false;

    if(firstStoppedRecording.toMilliSecondsSinceEpoch() < firstStartedRecording.toMilliSecondsSinceEpoch()){
        //this can happen if the recording is not propperly stopped but interrupted by stopping
        //the component
        firstStoppedRecording = now_ax_datetime;
    }

    for(const auto& filter: stats){
       filter_statistics(filter.second, memoryName,firstStartedRecording, firstStoppedRecording, &jsonData);
    }

    if(ended_without_recording){
        return;
    }

    /*
    if(sim_json_data.empty() || object_memory_json_data.empty()){
        jsonData["Scene-Information for Simulation"] = load_scene_information("sim");
        jsonData["Scene-Information for ObjectMemory"] = load_scene_information("om");
        jsonData["Additional object in simulation"] = find_difference(jsonData["Scene-Information for ObjectMemory"], jsonData["Scene-Information for Simulation"]);
    }else {
        jsonData["Scene-Information for Simulation"] = sim_json_data;
        jsonData["Scene-Information for ObjectMemory"] = object_memory_json_data;
    }
    */

    std::string path = d_p.string();
    path += "/tmp_wm_";
    path += timeID;
    path += ".json";
    try{
    std::ofstream outputFile;
    outputFile.open(path);
        if(outputFile.is_open()){
            outputFile << jsonData.dump(4);
            outputFile.close();
        } else {
            if(outputFile.fail()){
                ARMARX_INFO << outputFile.rdstate();
                outputFile.clear();
            }
            ARMARX_INFO << "File is not open";
        }
    } catch(...){
        ARMARX_WARNING << "std::ofstream failed";
    }

    ARMARX_INFO << "Saving statistics completed";
}

nlohmann::json load_scene_information(std::string om_or_sim)
{
    std::string home_dir = getenv("HOME");
    if(home_dir.empty()){
        ARMARX_WARNING << "Trying to open home directory but env variable HOME is not set";
    }
    std::filesystem::path path = home_dir;
    path /= "code";
    path /= "h2t" ;
    path /= "PriorKnowledgeData";
    path /= "data";
    path /= "PriorKnowledgeData";
    path /= "scenes";
    std::string name_for_current_scene = "ARMAR-III-kitchen"; //this can be changed later, maybe to something that indicates better that these are the changed files
    std::string file_name = name_for_current_scene + "_" + om_or_sim + ".json";
    path /= file_name;

    std::ifstream ifs(path);
    nlohmann::json json_data = nlohmann::json::parse(ifs);

    return json_data;

}

nlohmann::json find_difference(nlohmann::json om, nlohmann::json sim)
{
    //the new object is at the last position of sim at the moment
    auto objects = sim.at("objects");
    int num_elements = objects.size();
    auto object = objects[num_elements - 1];
    return object;
}

/**
 * @brief getStatisticsDirectory returns the path to where the statistics for this forgetting process
 * should be saved
 * @param memoryName name of the memory, e.g. VisionMemory
 * @param exportName name of the export
 * @param exportPath path to the export folder with date for this LTM
 * @return path
 */
std::filesystem::path getStatisticsDirectory(
        std::filesystem::path exportPath,
        std::string exportName,
        std::string memoryName)
{
    //path consists of exportPath/Date/exportName/memoryName with the date being the start date!
    std::filesystem::path statisticsPath = exportPath;

    //add parts to exportPath
    statisticsPath /= exportName;
    statisticsPath /= memoryName;

    //add Statistics Directory:
    statisticsPath /= "Statistics";

    return statisticsPath;
}

void filter_statistics(
        std::map<std::string, ltm::processor::SnapshotFilter::FilterStatistics> statistics_processors,
        std::string memoryName,
        armarx::core::time::DateTime firstStartedRecording,
        armarx::core::time::DateTime firstStoppedRecording,
        nlohmann::json *statistics)
{
    //TODO: start time of processor should be when enabling recording for filters... not creation
    //TODO: make sure order stays the way it was when added
    nlohmann::json ltm_information;

    for(const auto& statistics_processor: statistics_processors){
        nlohmann::json processor_information;

        //get start time of the processor:
        auto time = std::chrono::high_resolution_clock::to_time_t(statistics_processor.second.start_time);
        auto t = localtime(&time);
        std::stringstream ss;
        ss << std::put_time(t, "%Y-%m-%d %H:%M:%S");
        std::string timeStringStart = ss.str();
        processor_information["Processor instance created at"] = timeStringStart;

        //get end time of the processor:
        auto timeEnd = std::chrono::high_resolution_clock::to_time_t(statistics_processor.second.end_time);
        auto t_end = localtime(&timeEnd);
        std::stringstream s;
        s << std::put_time(t_end, "%Y-%m-%d %H:%M:%S");
        std::string timeStringEnd = s.str();
        processor_information["Processor instance last used at"] = timeStringEnd;

        //get time the processor ran for in seconds:
        long seconds = timeEnd - time;
        processor_information["Lifetime of processor in sec"] = seconds;

        //actual runtime in first recording:
        processor_information["Processor started at"] = firstStartedRecording.toDateTimeString();
        processor_information["Processor stopped at"] = firstStoppedRecording.toDateTimeString();
        auto runtime = firstStoppedRecording.toMilliSecondsSinceEpoch() - firstStartedRecording.toMilliSecondsSinceEpoch();
        auto seconds_runtime = runtime / 1000.0;
        processor_information["Duration of recording in seconds"] = seconds_runtime;

        //add additional information:
        processor_information["Additional information"] = statistics_processor.second.additional_info;

        //add number of accepted elements if this is a filter:
        processor_information["Accepted elements"] = statistics_processor.second.accepted;

        //add number of rejected elements if this is a filter:
        processor_information["Rejected elements"] = statistics_processor.second.rejected;

        //add additional time needed:
        processor_information["Additional time needed in sec"] = statistics_processor.second.additional_time.count();

        //add importance or similarity type if this type of filter:
        auto importance_type = statistics_processor.second.importance_type;
        if (!importance_type.empty()){
            processor_information["Importance type"] = importance_type;
        }
        auto similarity_type = statistics_processor.second.similarity_type;
        if(similarity_type != aron::similarity::NDArraySimilarity::Type::NONE){
            processor_information["Similarity type"] = std::to_string(similarity_type);
            //add number of objects compared each time if similarity filter:
            processor_information["Number of objects compared each time"] =
                    statistics_processor.second.number_of_compared_objects;
        }

        //add memory name as a sanity check:
        processor_information["Memory name"] = memoryName;

        // add information about this specific processor to the information about the ltm:
        ltm_information[statistics_processor.first] = processor_information;
    }


    // add the ltm information to the statistics json object:
    (*statistics)["Episodic Memory Information"] = ltm_information;
}

}