/***************************************************************************
                          dataDir.cpp  -  description
                             -------------------
    begin                : Sam March 1 2003
    copyright            : (C) 2003-2024 by Andre Simon
    email                : a.simon@mailbox.org
 ***************************************************************************/


/*
This file is part of Highlight.

Highlight is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Highlight is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Highlight.  If not, see <http://www.gnu.org/licenses/>.
*/


#include <string>
#include <string_view>
#include <fstream>
#include <vector>
#include "platform_fs.h"

#include "datadir.h"

using std::cerr;
using std::cin;
using std::cout;
using std::ifstream;
using std::ios;
using std::string;
using std::string_view;


string DataDir::LSB_DATA_DIR="/usr/share/highlight/";
string DataDir::LSB_CFG_DIR="/etc/highlight/";
string DataDir::LSB_DOC_DIR="/usr/share/doc/highlight/";


void DataDir::initSearchDirectories ( const string &userDefinedDir )
{

#ifndef _WIN32
    possibleDirs.push_back ( Platform::getHomePath() + "/.highlight/" );
#endif

    if ( !userDefinedDir.empty() ) possibleDirs.push_back ( userDefinedDir );

    char* hlEnvPath=getenv("HIGHLIGHT_DATADIR");
    if (hlEnvPath!=NULL) {
        possibleDirs.push_back ( hlEnvPath );
    }

#ifndef _WIN32

#ifdef HL_DATA_DIR
    possibleDirs.push_back ( HL_DATA_DIR );
#else
    possibleDirs.push_back ( LSB_DATA_DIR );
#endif

#ifdef HL_CONFIG_DIR
    possibleDirs.push_back ( HL_CONFIG_DIR);
#else
    possibleDirs.push_back ( LSB_CFG_DIR);
#endif

#else
    possibleDirs.push_back(Platform::getAppPath()); //not needed because of fallback in searchFile
#endif
}

std::string DataDir::searchFile(std::string_view path)
{
    for (const auto& dir : possibleDirs) {
        std::string fullPath = dir;
        fullPath.append(path);
        // cerr << "Searching " << fullPath << "\n";
        if (Platform::fileExists(fullPath))
            return fullPath;
    }
    return std::string(path);
}


const void DataDir::printConfigPaths()
{
    for ( const auto& dir : possibleDirs ) {
        if ( Platform::fileExists ( dir ) )
            cout << dir <<"\n";
    }
}

const string DataDir::getLangPath ( const string & file )
{
    return searchFile(string("langDefs") + Platform::pathSeparator + file);
}

const string DataDir::getThemePath ( const string & file, bool base16)
{
    string themesPath=string("themes")+Platform::pathSeparator;

    if (base16)
        themesPath=themesPath + "base16" + Platform::pathSeparator;

    return searchFile(themesPath+file);
}

const string DataDir::getPluginPath ( const string & file)
{
    return searchFile(string("plugins")+Platform::pathSeparator+file);
}

const string DataDir::getFiletypesConfPath (const string & file)
{
    return searchFile(file + ".conf");
}

const string DataDir::getThemePath ( )
{
    return getSystemDataPath()+"themes"+Platform::pathSeparator;
}

const string DataDir::getLangPath ( )
{
    return getSystemDataPath()+"langDefs"+Platform::pathSeparator;
}

const string DataDir::getPluginPath ( )
{
    return getSystemDataPath()+"plugins"+Platform::pathSeparator;
}

const string DataDir::getSystemDataPath ( )
{
#ifndef _WIN32
#ifdef HL_DATA_DIR
    return HL_DATA_DIR;
#else
    return LSB_DATA_DIR;
#endif
#else
    return Platform::getAppPath();
#endif
}

const string  DataDir::getI18nDir()
{
    return getSystemDataPath()+"gui_files"+Platform::pathSeparator+"i18n"+Platform::pathSeparator;
}

const string  DataDir::getExtDir()
{
    return getSystemDataPath() +"gui_files"+Platform::pathSeparator+"ext"+Platform::pathSeparator;
}

const string DataDir::getDocDir()
{
    return getSystemDataPath();
}

const string DataDir::getEncodingHint (const string &syntax) {

    return encodingHint[getFileBaseName(syntax)];
}

string DataDir::getFileSuffix(const string& fileName)
{
    size_t ptPos=fileName.rfind(".");
    size_t psPos = fileName.rfind ( Platform::pathSeparator );
    if (ptPos == string::npos) {
        return  (psPos==string::npos) ? fileName : fileName.substr(psPos+1, fileName.length());
    }
    return (psPos!=string::npos && psPos>ptPos) ? "" : fileName.substr(ptPos+1, fileName.length());
}

string DataDir::guessFileType ( const string& suffix, const string &inputFile, bool useUserSuffix, bool forceShebangCheckStdin )
{
    string baseName = getFileBaseName(inputFile);
    if (assocByFilename.count(baseName)) return assocByFilename.find(baseName)->second;

    string lcSuffix = StringTools::change_case(suffix);
    if (assocByExtension.count(lcSuffix)) {
        return assocByExtension[lcSuffix];
    }

    if (!useUserSuffix) {
        string shebang =  analyzeFile(forceShebangCheckStdin ? "" : inputFile);
        if (!shebang.empty()) return shebang;
    }

    return lcSuffix;
}

bool DataDir::loadFileTypeConfig (const string& name )
{
    string confPath=searchFile(name+".conf");
    try {
        Diluculum::LuaState ls;
        Diluculum::LuaValueList ret= ls.doFile (confPath);

        int idx=1;
        string langName;
        Diluculum::LuaValue mapEntry;
        while ((mapEntry = ls["FileMapping"][idx].value()) !=Diluculum::Nil) {
            langName = mapEntry["Lang"].asString();
            if (mapEntry["Extensions"] !=Diluculum::Nil) {
                readLuaList("Extensions", langName, mapEntry,  &assocByExtension);
            } else if (mapEntry["Filenames"] !=Diluculum::Nil) {
                readLuaList("Filenames", langName, mapEntry,  &assocByFilename);
            } else if (mapEntry["Shebang"] !=Diluculum::Nil) {
                assocByShebang.insert ( make_pair ( mapEntry["Shebang"].asString(),  langName ) );
            } else if (mapEntry["EncodingHint"] !=Diluculum::Nil) {
                encodingHint.insert ( make_pair ( langName, mapEntry["EncodingHint"].asString() ) );
            }
            idx++;
        }

    } catch (Diluculum::LuaError &err) {
        cerr <<err.what()<<"\n";
        return false;
    }
    return true;
}

bool DataDir::loadLSPConfig (const string& path )
{
    string confPath=searchFile(path+".conf");
    try {
        Diluculum::LuaState ls;
        Diluculum::LuaValueList ret= ls.doFile (confPath);

        int idx=1;
        std::string serverName;              ///< server name
        std::string executable;              ///< server executable path
        std::string syntax;                  ///< language definition which can be enhanced using the LS
        int delay;                           ///< server delay in milliseconds after initialization request
        bool legacy;                         ///< do not rely on a LS capabilities response
        std::vector<std::string> options;    ///< server executable start options
        Diluculum::LuaValue mapEntry;

        //{ Server="clangd", Exec="clangd", Syntax="c", Options={"--log=error"} },
        while ((mapEntry = ls["Servers"][idx].value()) !=Diluculum::Nil) {
            options.clear();
            serverName = mapEntry["Server"].asString();
            executable = mapEntry["Exec"].asString();
            syntax = mapEntry["Syntax"].asString();
            delay = 0;
            if (mapEntry["Delay"] !=Diluculum::Nil) {
                delay = mapEntry["Delay"].asNumber();
            }

            legacy = false;
            if (mapEntry["Legacy"] !=Diluculum::Nil) {
                legacy = mapEntry["Legacy"].asBoolean();
            }

            if (mapEntry["Options"] !=Diluculum::Nil) {
                int extIdx=1;
                while (mapEntry["Options"][extIdx] !=Diluculum::Nil) {
                    options.push_back(mapEntry["Options"][extIdx].asString());
                    extIdx++;
                }
            }

            highlight::LSPProfile profile;
            profile.executable = executable;
            profile.serverName = serverName;
            profile.syntax = syntax;
            profile.options = options;
            profile.delay = delay;
            profile.legacy = legacy;

            lspProfiles[serverName]=profile;

            idx++;
        }

    } catch (Diluculum::LuaError &err) {
        cerr <<err.what()<<"\n";
        return false;
    }
    return true;
}

bool DataDir::profileExists(const string &profile) {
    return lspProfiles.count(profile);
}


highlight::LSPProfile &DataDir::getProfile(const string &profile) {
    return lspProfiles[profile];
}


void DataDir::readLuaList(const string& paramName, const string& langName,Diluculum::LuaValue &luaVal, StringMap* extMap){
    int extIdx=1;
    string val;
    while (luaVal[paramName][extIdx] !=Diluculum::Nil) {
        val = luaVal[paramName][extIdx].asString();
        extMap->insert ( make_pair ( val,  langName ) );
        extIdx++;
    }
}

string DataDir::analyzeFile ( const string& file )
{
    string firstLine;
    if ( !file.empty() ) {
        ifstream inFile ( file.c_str() );
        getline ( inFile, firstLine );
    } else {

        //  This copies all the data to a new buffer, uses the data to get the
        //  first line, then makes cin use the new buffer that underlies the
        //  stringstream instance
        cin_bufcopy << cin.rdbuf();
        getline ( cin_bufcopy, firstLine );
        cin_bufcopy.seekg ( 0, ios::beg );
        cin.rdbuf ( cin_bufcopy.rdbuf() );
    }
    StringMap::iterator it;
    boost::xpressive::sregex rex;
    boost::xpressive::smatch what;
    for ( it=assocByShebang.begin(); it!=assocByShebang.end(); it++ ) {
        rex = boost::xpressive::sregex::compile( it->first );
        if ( boost::xpressive::regex_search( firstLine, what, rex )  ) return it->second;
    }
    return "";
}

string DataDir::getFileBaseName(const string& fileName){
    size_t psPos = fileName.rfind ( /*Platform::pathSeparator*/ '/' );
    return  (psPos==string::npos) ? fileName : fileName.substr(psPos+1, fileName.length());
}
