/***************************** LICENSE START ***********************************

 Copyright 2014 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include "IconClass.h"

#include <algorithm>
#include <fstream>
#include <iostream>
#include <map>
#include <set>
#include <sstream>
#include <string>
#include <vector>
#include "fstream_mars_fix.h"

#include "ConfigLoader.h"
#include "Service.h"
#include "IconInfo.h"
#include "IconObject.h"
#include "IconFactory.h"
#include "Items.h"
#include "Folder.h"
#include "MvIconLanguage.h"
#include "MvRequestUtil.hpp"
#include "MvScanFileType.h"

static std::map<std::string, const IconClass*> classes;

// All icon classed subclass from this one
static IconClass super("*", nullptr);

static std::set<std::string> allActions;

IconClass::IconClass(const std::string& name, request* r, const IconClass* sup) :
    MvIconClassCore(name, r),
    super_(sup)
{
    classes[name] = this;
    if (!super_ && this != &super)
        super_ = &super;
}

IconClass::~IconClass()
{
    classes.erase(name_);
}

void IconClass::scan(ClassScanner& s)
{
    std::map<std::string, const IconClass*>::iterator j;
    for (j = classes.begin(); j != classes.end(); ++j)
        s.next(*(*j).second);
}

#if 0
const std::string& IconClass::name() const
{
    return name_;
}

#endif

bool IconClass::isValid(const std::string& name)
{
    return (classes.find(name) != classes.end());
}

const IconClass& IconClass::find(const std::string& name)
{
    auto j = classes.find(name);
    if (j != classes.end())
        return *(*j).second;

    return *(new IconClass(name, empty_request(nullptr)));
}

const IconClass& IconClass::find(const std::string& name, const std::string& defaultType, bool caseSensitive)
{
    if (caseSensitive) {
        auto j = classes.find(name);
        if (j != classes.end())
            return *(*j).second;
    }
    else {
        for (auto& classe : classes) {
            // We convert the class name to uppercase for comparison
            std::string str = classe.first;
            std::transform(str.begin(), str.end(), str.begin(), ::toupper);
            std::string ucName = name;
            std::transform(ucName.begin(), ucName.end(), ucName.begin(), ::toupper);
            if (str == ucName) {
                return *classe.second;
            }
        }
    }

    return find(defaultType);
}

void IconClass::find(const std::string& str, std::vector<const IconClass*>& res, bool onlyCreatable)
{
    for (auto& classe : classes) {
        const auto* kind = classe.second;

        if (onlyCreatable && kind->canBeCreated() == false)
            continue;

        std::string s;

        std::string strTerm = str;
        std::transform(strTerm.begin(), strTerm.end(), strTerm.begin(), ::tolower);

        // Default name
        s = kind->defaultName();
        std::transform(s.begin(), s.end(), s.begin(), ::tolower);
        if (s.find(strTerm) != std::string::npos) {
            res.push_back(kind);
            continue;
        }

        // Name
        s = kind->name();
        std::transform(s.begin(), s.end(), s.begin(), ::tolower);
        if (s.find(strTerm) != std::string::npos) {
            res.push_back(kind);
            continue;
        }

        // Type
        s = kind->type();
        std::transform(s.begin(), s.end(), s.begin(), ::tolower);
        if (s.find(strTerm) != std::string::npos) {
            res.push_back(kind);
            continue;
        }

        // icon box
        s = kind->iconBox();
        std::transform(s.begin(), s.end(), s.begin(), ::tolower);
        if (s.find(strTerm) != std::string::npos) {
            res.push_back(kind);
            continue;
        }
    }
}

void IconClass::load(request* r)
{
    new IconClass(get_value(r, "class", 0), r);
}

bool IconClass::isSubClassOf(const IconClass& other) const
{
    return this == &other;
}

#if 0
string IconClass::editor() const
{
    const char* e = get_value(request_, "editor_type", 0);
    return e ? e : "NoEditor";
}
#endif

bool IconClass::canBecome(const IconClass& other) const
{
    if (isSubClassOf(other))
        return true;

    for (auto output : outputs_) {
        if (
            output->canBecome(other))
            return true;
    }
    return false;
}

// The list of supported actions (can_) is now cached! We rely on the fact that when can() is
// called for the first time all the initailisation has been done! We also suppose that there is
// at least one action defined for any of the classes so after the first call of can() can_cannot be
// empty!
std::set<std::string> IconClass::can() const
{
    if (can_.empty())
        can_ = generateCan();

    return can_;
}

// Generate a list of the supported actions (can_)
std::set<std::string> IconClass::generateCan() const
{
    std::set<std::string> c;

    // First check if all the actions are allowed for the class
    static Action all("*", "*");
    if (services_.find(all) != services_.end()) {
        c = allActions;

        // Now we need to remove some actions if they are not defined explicitly for this
        // class or its output classes in ObjectList
        static std::set<std::string> nonAllActions;
        if (nonAllActions.empty()) {
            nonAllActions.insert("extract");
            nonAllActions.insert("extract_subfolder");
            nonAllActions.insert("analyse");
            nonAllActions.insert("save");
            nonAllActions.insert("clear");
            nonAllActions.insert("visualise");
            nonAllActions.insert("examine");
            nonAllActions.insert("export");
        }

        // Loop for the actions to be checked
        for (const auto& nonAllAction : nonAllActions) {
            bool need = false;

            // Flag the action if it is not present in the services!
            Action nonAllAc(nonAllAction, "*");
            auto itS = services_.find(nonAllAc);
            if (itS != services_.end()) {
                continue;
            }

            // Flag the action if it is not present in the otput classes
            for (auto output : outputs_) {
                // std::cout << name_ << " can, adding " << (*k)->name_ << std::endl;
                auto oc = output->can();
                auto itIcs = oc.find(nonAllAction);
                if (itIcs != oc.end()) {
                    need = true;
                    break;
                }
            }

            // If the action is flagged we remove it
            if (!need)
                c.erase(nonAllAction);
        }
    }

    // Otherwise compiles a set of action from services and outpusts etc.
    else {
        if (super_) {
            c = super_->can();
        }

        for (const auto& service : services_) {
            c.insert(service.first.name());
        }


        for (auto output : outputs_) {
            // std::cout << name_ << " can, adding " << (*k)->name_ << std::endl;
            auto oc = output->can();
            c.insert(oc.begin(), oc.end());
        }
    }

    // Check editor
    if (editor() == "NoEditor")
        c.erase("edit");
    else
        c.insert("edit");

    return c;
}

void IconClass::service(const Action& action, const IconClass* output, Service* s) const
{
    auto* self = const_cast<IconClass*>(this);
    if (output)
        self->outputs_.insert(output);

    if (services_.find(action) != services_.end())
        return;

    allActions.insert(action.name());

    // cout << name_ << " can " << action << " ";
    // if(output) std::cout << " output " << output->name_;
    // cout << " with " << *s << std::endl;

    self->services_[action] = s;
}

Service* IconClass::service(const Action& action) const
{
    // cout << "IconClass::service class " << name_ << std::endl;
    // cout << "IconClass::service for " << action << std::endl;
    auto j = services_.find(action);
    if (j == services_.end()) {
        Service* s = super_ ? super_->service(action) : nullptr;
        if (s == nullptr && !(action.name() == "*"))
            return service(Action("*", action.mode()));
        else
            return s;
    }
    std::cout << *(j->second) << std::endl;
    return j->second;
}

static SimpleLoader<IconClass> loadClasses("object", 0);

//==================================================================

const std::string missing("///");

static bool is_request(const char* file, std::string& s)
{
    if (!file)
        return false;

    std::ifstream in(file);

    int maxLines = 5;
    int actLine = 0;
    std::string typeStr;

    if (in.good()) {
        std::string line;
        while (getline(in, line) && actLine < maxLines) {
            std::vector<std::string> vec;
            std::stringstream sst(line);
            std::string s;
            while (sst >> s) {
                vec.push_back(s);
            }

            if (vec.size() >= 1 && vec.at(0).substr(0) != "#") {
                typeStr = vec.at(0);
                if (typeStr.find(",") == typeStr.size() - 1) {
                    typeStr = typeStr.substr(0, typeStr.size() - 1);
                }

                // We convert the std::string to uppercase for comparison
                std::transform(typeStr.begin(), typeStr.end(), typeStr.begin(), ::toupper);
                break;
            }

            actLine++;
        }
    }

    // Close the file
    in.close();

    if (!typeStr.empty()) {
        for (auto& classe : classes) {
            // We convert the class name to uppercase for comparison
            std::string str = classe.first;
            std::transform(str.begin(), str.end(), str.begin(), ::toupper);
            if (str == typeStr) {
                s = classe.first;
                return true;
            }
        }
    }
    return false;
}

static std::string guess_file(const char* file)
{
    struct stat buf;

    if (stat(file, &buf) < 0)
        return "BAD";

    switch (buf.st_mode & S_IFMT) {
        case S_IFDIR:
            return ScanFileType(file);

        case S_IFREG:
            // return scan_file(file);
            {
                std::string ft = ScanFileType(file);
                if (ft != "NOTE")
                    return ft;

                std::string ft2;
                if (is_request(file, ft2))
                    return ft2;
                else
                    return ft;
            }

        default:
            return "SPECIAL";
    }
}

const IconClass& IconClass::guess(const Path& file)
{
    return guess(file.str());
}

const IconClass& IconClass::guess(const std::string& file)
{
    std::string kind = guess_file(file.c_str());
    return find(kind);
}

// We need to ensure that this call does not create a new iconClass. So it could be called from another thread.
// On top of that we cannot call anything that requires mars parsing because it is not safe!
void IconClass::guess(const std::string& fullName, std::string& icName, std::string& icType)
{
    if (!icName.empty()) {
        const IconClass& ic = IconClass::find(icName, "NOTE", true);
        icType = ic.type();
    }
    else {
        // try dot file
        Path icp(fullName);
        Path dot = icp.dot();

        // We need to parse the dot file by ourselves because requests cannot be used here (they are not thread safe).
        if (dot.exists()) {
            std::ifstream in(dot.str().c_str());
            std::string str;
            while (getline(in, str)) {
                if (str.find("ICON_CLASS") != std::string::npos) {
                    std::size_t pos = str.find("=");
                    if (pos != std::string::npos && pos < str.size() - 1) {
                        str = str.substr(pos + 1, str.size());
                        std::string type;
                        for (char i : str) {
                            if (i != ' ' && i != ',' && i != '\n')
                                type += i;
                        }

                        const IconClass& ic = IconClass::find(type, "NOTE", false);
                        icName = ic.name();
                        icType = ic.type();
                    }
                    break;
                }
            }
            in.close();
        }
        // If there is no dotfile we guess the file type
        else {
            //::guesss() does not create a new iconclass!
            const IconClass& ic = IconClass::guess(fullName);
            icName = ic.name();
            icType = ic.type();
        }
    }
}

IconObject* IconClass::createOne(Folder* f) const
{
    return createOne(f, IconInfo::undefX(), IconInfo::undefY());
}

IconObject* IconClass::createOne(Folder* f, int x, int y) const
{
    IconObject* obj = defaultObject();
    if (obj)
        return obj->clone(f, x, y, false);

    return nullptr;
}

IconObject* IconClass::createOneFromRequest(Folder* f) const
{
    return createOneFromRequest(f, IconInfo::undefX(), IconInfo::undefY());
}

IconObject* IconClass::createOneFromRequest(Folder* f, int x, int y) const
{
    Request r(name_);
    return IconFactory::create(f, r, x, y);
}

IconObject* IconClass::defaultObject() const
{
    Folder* f = Folder::folder("defaults");
    std::string name = defaultName();

    IconObject* obj = f->find(name);
    if (obj)
        return obj;
    else
        return IconFactory::create(f, name, *this);
    // return createOneFromRequest(f);

    return nullptr;
}

#if 0

string IconClass::defaultName() const
{
    const char* def = get_value(request_, "default_name", 0);
    return def ? std::string(def) : name_;
}

string IconClass::iconBox() const
{
    const char* def = get_value(request_, "icon_box", 0);
    return def ? std::string(def) : std::string();
}

string IconClass::helpPage() const
{
    const char* def = get_value(request_, "help_page", 0);
    return def ? std::string(def) : name_;
}

string IconClass::doubleClickMethod() const
{
    const char* def = get_value(request_, "doubleclick_method", 0);
    return def ? std::string(def) : std::string();
}

string IconClass::defaultMethod() const
{
    const char* def = get_value(request_, "default_method", 0);
    return def ? std::string(def) : "edit";
}

bool IconClass::skipDepandancies(const std::string& action) const
{
    const char* def = get_value(request_, "skip_dependancies", 0);
    return def ? (strcmp(def, action.c_str()) == 0) : false;
}

Path IconClass::pixmap() const
{
    const char* def = get_value(request_, "pixmap", 0);
    if (def) {
        Path p(def);
        std::string fname          = p.name();
        std::string::size_type pos = fname.rfind(".icon");
        if (pos != std::string::npos) {
            fname = fname.substr(0, pos);
        }

        std::string s       = getenv("METVIEW_DIR_SHARE");
        std::string svgName = s + "/icons_mv5/" + fname + ".svg";

        Path psvg(s + "/icons_mv5/" + fname + ".svg");
        if (psvg.exists()) {
            //cout << "svg " << psvg.str() << std::endl;
            return psvg;
        }
        else {
            Path pxpm(s + "/icons_mv5/" + fname + ".xpm");
            if (pxpm.exists()) {
                //cout << "svg " << psvg.str() << std::endl;
                return pxpm;
            }
        }

        return Path("");
    }

    return Path("");
}

bool IconClass::canBeCreated() const
{
    const char* def = get_value(request_, "can_be_created", 0);
    if (def) {
        return (strcmp(def, "True") == 0 || strcmp(def, "true") == 0) ? true : false;
    }

    return false;
}

bool IconClass::canHaveLog() const
{
    const char* def = get_value(request_, "can_have_log", 0);
    if (def) {
        return (strcmp(def, "True") == 0 || strcmp(def, "true") == 0) ? true : false;
    }

    return true;
}

string IconClass::type() const
{
    const char* def = get_value(request_, "type", 0);
    return def ? std::string(def) : name_;
}

Path IconClass::definitionFile() const
{
    const char* def = get_value(request_, "definition_file", 0);
    if (def == 0)
        def = "/dev/null";
    return Path(def);
}

Path IconClass::rulesFile() const
{
    const char* def = get_value(request_, "rules_file", 0);
    if (def == 0)
        def = "/dev/null";
    return Path(def);
}

long IconClass::expandFlags() const
{
    const char* def = get_value(request_, "expand", 0);
    return def ? atol(def) : EXPAND_MARS;
}

#endif

#if 0
MvIconLanguage& IconClass::language() const
{
    return MvIconLanguage::find(this);
}
#endif

// This should be in ObjectList...

// Visdef -> 1
// Data,File, Macro, Family, ..   -> 2
// View   -> 3
// Window -> 4

int IconClass::priority() const
{
    // Very, very UGLY if....

    std::string t = type();

    if (t == "Visdef")
        return 1;
    if (t == "View")
        return 3;
    if (t == "Window")
        return 4;

    return 2;
}
