/*
 * shins_util_f.cpp
 *
 *  Created on: Sep 15, 2021
 *      Author: fulvio laudisio
 */

#include "shins_util_f.h"
#include <algorithm>
#include <ios>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <cstring> // std::memcpy


#include <sys/stat.h> // mkdir
#include <unistd.h>   // chown, chdir


namespace shins
{
namespace util
{
    int shinslog::counter = 0;
    char shinslog::log[] = {0};//shinslog::init();

    pthread_mutex_t shinslog::m_mutex;

    std::string m_dataSaveDirectory = "/data/fits/";
    std::string m_currentSaveDirectory;

    void SetDataSaveDirectory(const std::string & dir)
    {
         m_dataSaveDirectory = dir;
    };

    char * shinslog::init()
    {
        int i;
        for(i = 0; i < 772; i++)
        {
            *(shinslog::log + i) = 0;
        }
        pthread_mutex_init(&m_mutex, NULL);
        return log;
    }

    int shinslog::call()
    {
        pthread_mutex_lock(&m_mutex);
        counter++;
        int size = counter*3;
        if(counter > 256)
            size = 768;
        int i;
        for(i = size - 3; i < size; i++)
            *(log + i) = '_';
        *(log + i) = 0;
        int result = counter - 1;
        pthread_mutex_unlock(&m_mutex);
        return result;
    }

    int shinslog::back(int new_counter)
    {
        pthread_mutex_lock(&m_mutex);
        counter--;// = new_counter;
        if(counter < 0)counter = 0;
        int size = counter*3;
        if(counter > 256)
            size = 768;
        for(int i = size; i < size + 3; i++)
            *(log + i) = 0;
        pthread_mutex_unlock(&m_mutex);
        return new_counter;
    }


    unsigned char to_upper(unsigned char c)
    {
        unsigned char res = c;
        static unsigned char add = 'a' - 'A'; // 0x20
        if(res >= 'a' && res <= 'z')res -= add;
        return res;
    }

    unsigned char to_lower(unsigned char c)
    {
        unsigned char res = c;
        static unsigned char add = 'a' - 'A'; // 0x20
        if(res >= 'A' && res <= 'Z')res += add;
        return res;
    }

    std::string str_toupper(std::string s)
    {
        std::transform(s.begin(), s.end(), s.begin(), to_upper);
        return s;
    }

    std::string str_tolower(std::string s)
    {
        std::transform(s.begin(), s.end(), s.begin(), to_lower);
        return s;
    }


    std::string left(const std::string & s, int n)
    {
        if(n >= 0)
            return s.substr(0, std::min<int>(n, s.size()));
        else
            throw(std::invalid_argument("Invalid n"));
    }


    std::string right(const std::string & s, int n)
    {
        if(n >= 0)
            return s.substr(std::max<int>(s.size() - n, 0));
        else
            throw(std::invalid_argument("Invalid n"));
    }


    bool contains(const std::string &s, const std::string & sub)
    {
        return s.find(sub) != s.npos;
    }


    const std::string & banner_string(const std::string & msg, char fill, int len, bool no_double_space)
    {
        int separator_size = msg.length();
        static std::string result;
        if(separator_size > (len - 6))
        {
            result = msg.substr(0, len - 6);
            separator_size = len - 6;
        }
        else
            result = msg;
        int left = (len - separator_size - 6)/2;
        int right = len - separator_size - 6 - left;
        std::string space = "   ";
        if(no_double_space)space = " ~ ";
        if(msg.length() > 0)result = space + result + space;
        result = std::string(left, fill) + result +  std::string(right, fill);
        return result;
    }


    EShinsMovements detect_command_type(const std::string second, double & abs, double & rel, std::string & unit)
    {
        std::istringstream is;
        double value;
        try
        {
            is.str(second);
            is >> value;
            if(is.fail())
            {
                if(second.size() > 2)
                {
                    is.clear();
                    int arg_size = second.size() - 2;
                    is.str(right(second, arg_size));
                    is >> value;
                    if(is.fail() || is.eof() == 0)
                    {
                        unit = constants::steps;
                        return SHINS_MOV_NAMED;
                    }
                    else if(is.eof())
                    {
                        char first_char = *second.begin();
                        char second_char = *(second.begin() + 1);
                        if(second_char == '#')
                            unit = "STEPS";
                        else if(second_char == '_')
                            unit = "MM";
                        else if(second_char == '%')
                            unit = "MICRON";
                        else if(second_char == '*')
                            unit = "DEGREE";
                        else if(second_char == '\"')
                            unit = "ARCSEC";
                        else if(second_char == '@')
                            unit = "DEGREEMOTOR";
                        else if(second_char == '$')
                            unit = "RAD";
                        else if(second_char == '!')
                            unit = "ABSENC";
                        else if(second_char == 'X')
                            unit = "DEFAULT";
                        else
                            return SHINS_MOV_NAMED;
                        if(first_char == 'A')
                        {
                            abs = value;
                            return SHINS_MOV_ABSOLUTE;
                        }
                        else if(first_char == 'R')
                        {
                            rel = value;
                            return SHINS_MOV_RELATIVE;
                        }
                        else
                            return SHINS_MOV_NAMED;
                    }
                }
                else
                    return SHINS_MOV_NAMED;
            }
            else
            {
                unit = "DEFAULT";
                abs = value;
                return SHINS_MOV_ABSOLUTE;
            }
        }
        catch(std::ios_base::failure)
        {
            return SHINS_MOV_UNDEFINED;
        }
        return SHINS_MOV_UNDEFINED;
    }

    std::string NamedStylePos(double val, const std::string & unit, EShinsMovements type)
    {
        std::stringstream result;
        char id = 'Z';// "DEFAULT"
        if(unit == "STEPS")id = '#';
        else if(unit == "MM")id = '_';
        else if(unit == "MICRON")id = '%';
        else if(unit == "DEGREE")id = '*';
        else if(unit == "ARCSEC")id = '\"';
        else if(unit == "DEGREEMOTOR")id = '@';
        else if(unit == "RAD")id = '$';
        else if(unit == "ABSENC")id = '!';
        else if(unit == "DEFAULT")id = 'X';

        if(type == SHINS_MOV_ABSOLUTE)
        {
            if(unit == "DEFAULT")
                result << val;
            else
            {
                result << "A" << id << val;
            }
        }
        else if(type == SHINS_MOV_RELATIVE)
        {
            result << "R" << id << val;
        }
        return result.str();
    }

    std::string remove_whitespaces(const std::string & original)
    {
        std::string output;
        output.reserve(original.size()); // optional, avoids buffer reallocations in the loop
        for(size_t i = 0; i < original.size(); ++i)
        {
            char wC = original[i];
            if(wC != ' ' && wC != '\t' && wC != '\n') output += wC;
        }
        return output;
    }


    const std::string & get_iso_time_str()
    {
        static std::string iso_time;
        static char time_string[20];
        time_t rawtime;
        time(&rawtime);
        strftime(time_string, 20, "%Y-%m-%dT%H-%M-%S", localtime(&rawtime));
        iso_time = time_string;
        return iso_time;
    }

    const std::string & get_iso_gmtime_str()
    {
        static std::string iso_time;
        static char time_string[20];
        time_t rawtime;
        time(&rawtime);
        strftime(time_string, 20, "%Y-%m-%dT%H-%M-%S", gmtime(&rawtime));
        iso_time = time_string;
        return iso_time;
    }

    int SetTimeWorkingDirectory(const std::string & time_stamp)
    {
        if(time_stamp.size() < 10)
        {
            std::stringstream log_msg;
            log_msg << "Error : Time stamp should be a string starting with YYYY-MM-DD received : \"" << time_stamp << "\"";
            throw(std::invalid_argument(log_msg.str()));
        }
        std::string current = left(time_stamp, 10);
        
        m_currentSaveDirectory = current;
        std::string to_set = m_dataSaveDirectory + m_currentSaveDirectory;

        struct stat st = {0};
        if(stat(to_set.c_str(), &st) == -1)
        {
            U9_LLOG("Creating directory : \"" << to_set << "\"");
            mkdir(to_set.c_str(), 0775);
            chown(to_set.c_str(), 1000, 1000);
        }

        return chdir(to_set.c_str());
    }

    const std::string & getCurrentDataDirectory()
    {
        return m_currentSaveDirectory;
    }

    bool swab4(const void * source, void * dest, size_t size)
    {
        char * rdest = (char *)dest;
        const char * rsource = (const char *)source;
        for(unsigned int i = 0; i < size; i += 4)
        {
            *(rdest + i + 3) = *(rsource + i);
            *(rdest + i + 2) = *(rsource + i + 1);
            *(rdest + i + 1) = *(rsource + i + 2);
            *(rdest + i) = *(rsource + i + 3);
        }
        return true;
    }


    bool get_bool_from_setup(const std::map<std::string, std::string> & map, const std::string & key, bool & val)
    {
        std::map<std::string, std::string>::const_iterator it = map.find(key);
        if(it == map.end())
        {
            return false;
        }
        else
        {
            if(it->second == "true" || it->second == "1")
                val = true;
            else if(it->second == "false" || it->second == "0")
                val = false;
            else
            {
                std::stringstream log_msg;
                log_msg << "Invalid boolean argument for \"" << it->first << "\" : \"" << it->second << "\"; must be on of {\"true\", \"false\", \"1\", \"0\"";
                throw(std::invalid_argument(log_msg.str()));
            }
            return true;
        }
    }

    std::map<std::string, std::string>
        extract_setup(const std::vector<std::string> & to_be_extracted, std::map<std::string, std::string> & TechCamSetup)
    {
        std::map<std::string, std::string> result;
        std::map<std::string, std::string>::iterator itSetup;
        std::vector<std::string>::const_iterator itExtract;
        for(itExtract = to_be_extracted.begin(); itExtract < to_be_extracted.end(); itExtract++)
        {
            itSetup = TechCamSetup.find(*itExtract);
            if(itSetup != TechCamSetup.end())
            {
                result[*itExtract] = itSetup->second;
                TechCamSetup.erase(itSetup);
            }
        }
        return result;
    }


    void extract_setup(std::map<std::string, std::string> & to_be_extracted, const std::map<std::string, std::string> & InstSetup)
    {
        std::map<std::string, std::string>::const_iterator itSetup;
        std::map<std::string, std::string>::iterator itExtract;
        for(itExtract = to_be_extracted.begin(); itExtract != to_be_extracted.end(); itExtract++)
        {
            itSetup = InstSetup.find(itExtract->first);
            if(itSetup != InstSetup.end())
            {
                to_be_extracted[itExtract->first] = itSetup->second;
            }
        }
    }

    char * get_char_star(const std::string & std_str)
    {
        static char result[80];
        int str_size = std_str.size();
        if(str_size > 79)str_size = 79;
        std::memcpy(result, &*std_str.begin(), str_size);
        *(result + str_size) = 0;
        return result;
    }


    void log_dictionary(std::string file, int line, const std::map<std::string, std::string> & dict)
    {
        std::map<std::string, std::string>::const_iterator itPair;
        U9_LLOG(file << " ~ Line : " << line);
        for(itPair = dict.begin(); itPair != dict.end(); itPair++)
        {
            U9_LLOG("Keyword : " << std::setfill('_') << std::setw(24) << itPair->first << "; Value : " << std::setw(60) << itPair->second);
        }
    }
}// namespace util

}// namespace shins
