/***************************** 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 "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"

#include <algorithm>

static map<string, const IconClass*> classes;

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

static set<string> allActions;

IconClass::IconClass(const 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)
{
    map<string, const IconClass*>::iterator j;
    for (j = classes.begin(); j != classes.end(); ++j)
        s.next(*(*j).second);
}

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

#endif

bool IconClass::isValid(const string& name)
{
    map<string, const IconClass*>::iterator j = classes.find(name);
    return (j != classes.end());
}

const IconClass& IconClass::find(const string& name)
{
    map<string, const IconClass*>::iterator j = classes.find(name);
    if (j != classes.end())
        return *(*j).second;

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

const IconClass& IconClass::find(const string& name, const string& defaultType, bool caseSensitive)
{
    if (caseSensitive) {
        map<string, const IconClass*>::iterator j = classes.find(name);
        if (j != classes.end())
            return *(*j).second;
    }
    else {
        for (map<string, const IconClass*>::iterator it = classes.begin(); it != classes.end(); it++) {
            //We convert the class name to uppercase for comparison
            std::string str = it->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 *(*it).second;
            }
        }
    }

    return find(defaultType);
}

void IconClass::find(const string& str, vector<const IconClass*>& res, bool onlyCreatable)
{
    for (map<string, const IconClass*>::iterator it = classes.begin(); it != classes.end(); it++) {
        const IconClass* kind = it->second;

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

        string s;

        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) != string::npos) {
            res.push_back(kind);
            continue;
        }

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

        //Type
        s = kind->type();
        std::transform(s.begin(), s.end(), s.begin(), ::tolower);
        if (s.find(strTerm) != 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) != 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 (set<const IconClass*>::const_iterator k = outputs_.begin();
         k != outputs_.end(); ++k) {
        if (
            (*k)->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!
set<string> IconClass::can() const
{
    if (can_.empty())
        can_ = generateCan();

    return can_;
}

//Generate a list of the supported actions (can_)
set<string> IconClass::generateCan() const
{
    set<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<string> nonAllActions;
        if (nonAllActions.empty()) {
            nonAllActions.insert("extract");
            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 (set<string>::const_iterator it = nonAllActions.begin(); it != nonAllActions.end(); it++) {
            bool need = false;

            //Flag the action if it is not present in the services!
            Action nonAllAc(*it, "*");
            map<Action, Service*>::const_iterator itS = services_.find(nonAllAc);
            if (itS != services_.end()) {
                continue;
            }

            //Flag the action if it is not present in the otput classes
            for (set<const IconClass*>::const_iterator k = outputs_.begin();
                 k != outputs_.end(); ++k) {
                // cout << name_ << " can, adding " << (*k)->name_ << endl;
                set<string> oc                    = (*k)->can();
                set<string>::const_iterator itIcs = oc.find(*it);
                if (itIcs != oc.end()) {
                    need = true;
                    break;
                }
            }

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

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

        for (map<Action, Service*>::const_iterator j = services_.begin();
             j != services_.end(); ++j) {
            c.insert((*j).first.name());
        }


        for (set<const IconClass*>::const_iterator k = outputs_.begin();
             k != outputs_.end(); ++k) {
            // cout << name_ << " can, adding " << (*k)->name_ << endl;
            set<string> oc = (*k)->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
{
    IconClass* 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) cout << " output " << output->name_;
    //cout << " with " << *s << endl;

    self->services_[action] = s;
}

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

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

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

const string missing("///");

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

    ifstream in(file);

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

    if (in.good()) {
        string line;
        while (getline(in, line) && actLine < maxLines) {
            vector<string> vec;
            stringstream sst(line);
            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 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 (map<string, const IconClass*>::iterator it = classes.begin(); it != classes.end(); it++) {
            //We convert the class name to uppercase for comparison
            std::string str = it->first;
            std::transform(str.begin(), str.end(), str.begin(), ::toupper);
            if (str == typeStr) {
                s = it->first;
                return true;
            }
        }
    }
    return false;
}

static 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);
            {
                string ft = ScanFileType(file);
                if (ft != "NOTE")
                    return ft;

                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 string& file)
{
    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()) {
            ifstream in(dot.str().c_str());
            string str;
            while (getline(in, str)) {
                if (str.find("ICON_CLASS") != string::npos) {
                    std::size_t pos = str.find("=");
                    if (pos != string::npos && pos < str.size() - 1) {
                        str = str.substr(pos + 1, str.size());
                        string type;
                        for (unsigned int i = 0; i < str.size(); i++) {
                            if (str[i] != ' ' && str[i] != ',' && str[i] != '\n')
                                type += str[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 0;
}

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");
    string name = defaultName();

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

    return 0;
}

#if 0

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

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

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

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

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

bool IconClass::skipDepandancies(const 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);
        string fname          = p.name();
        string::size_type pos = fname.rfind(".icon");
        if (pos != string::npos) {
            fname = fname.substr(0, pos);
        }

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

        Path psvg(s + "/icons_mv5/" + fname + ".svg");
        if (psvg.exists()) {
            //cout << "svg " << psvg.str() << endl;
            return psvg;
        }
        else {
            Path pxpm(s + "/icons_mv5/" + fname + ".xpm");
            if (pxpm.exists()) {
                //cout << "svg " << psvg.str() << 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 ? 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....

    string t = type();

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

    return 2;
}
