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

 Copyright 2013 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 "Log.h"

#include "IconObject.h"

#include "IconClass.h"
#include "IconInfo.h"
#include "IconFactory.h"

#include "State.h"
#include "Folder.h"
#include "Editor.h"
#include "Service.h"
#include "Command.h"
#include "MvIconLanguage.h"
#include "Request.h"
#include "Queue.h"
#include "MvIconParameter.h"
#include "Path.h"
#include "FolderObserver.h"
#include "Tokenizer.h"
#include "Dependancy.h"
#include "MvQLogDialog.h"

#include <algorithm>
#include <sstream>
#include <assert.h>

#include "Metview.h"

#include <sys/stat.h>

vector<IconObject*> IconObject::objects_;


IconObject::IconObject(Folder* parent, const IconClass& kind, const string& name,
                       IconInfo* info) :
    name_(name),
    parent_(parent),
    class_(const_cast<IconClass*>(&kind)),
    info_(info),
    queue_(0),
    log_(0),
    editor_(0),
    logWindow_(0),
    locked_(false),
    link_(false),
    status_(DefaultStatus)
{
    string p;
    if (parent_) {
        parent_->attach();
        p = path().str();
    }
    else {
        p = name;
    }

    struct stat s;
    if (lstat(p.c_str(), &s) == 0) {
        link_   = S_ISLNK(s.st_mode);
        locked_ = faccess(p.c_str(), W_OK);
        //else locked_ = true;
    }
    //else locked_ = true;

    objects_.push_back(this);
}

IconObject::~IconObject()
{
    reset();
    if (parent_)
        parent_->detach();
    delete info_;


    vector<IconObject*>::iterator it;
    for (it = objects_.begin(); it != objects_.end(); it++) {
        if (*it == this) {
            objects_.erase(it);
            break;
        }
    }
}

const string& IconObject::name() const
{
    return name_;
}

Folder* IconObject::parent() const
{
    return parent_;
}

IconInfo& IconObject::info() const
{
    if (info_ == 0) {
        Request info("USER_INTERFACE");
        info("ICON_CLASS") = className().c_str();

        IconObject* o = const_cast<IconObject*>(this);
        o->info_      = new IconInfo(info);
        o->saveInfo();
    }

    return *info_;
}

string IconObject::fullName() const
{
    string p = parent()->fullName();
    if (p != "/")
        return p + "/" + name();
    else
        return p + name();
}

Path IconObject::path() const
{
    return parent()->path().add(name());
}

string IconObject::dotName(const string& n) const
{
    return string(".") + n;
}

Path IconObject::dotPath() const
{
    return parent()->path().add(dotName(name()));
}

string IconObject::embeddedName(const string& n) const
{
    return n + "#";
}

Path IconObject::embeddedPath() const
{
    return parent()->path().add(embeddedName(name()));
}

Folder* IconObject::embeddedFolder(const string& n, bool create) const
{
    return Folder::folder(parent()->fullName(), embeddedName(name()), n, create);
}

Folder* IconObject::embeddedFolder(bool create) const
{
    return Folder::folder(parent()->fullName(), embeddedName(name()), create);
}

bool IconObject::isEmbedded()
{
    //Needs to be improved!
    return (parent()->path().str().find("#") != string::npos);
}

const string& IconObject::className() const
{
    return iconClass().name();
}

const IconClass& IconObject::iconClass() const
{
    return *class_;
}

const IconClass& IconObject::editorClass() const
{
    return iconClass();
}

void IconObject::doubleClick()
{
    edit();
}

void IconObject::print(ostream& s) const
{
    s << className() << " - " << fullName();
}


void IconObject::command(const string& name)
{
    Command::execute(name, this);
}

Task* IconObject::action(const Action& a)
{
    Service* s = iconClass().service(a);
    if (s == 0)
        s = Service::find("InternalDesktopService");

    if (s != 0) {
        Task* t = s->task(a, this);
        if (t != 0) {
            if (queue_ == 0)
                queue_ = new Queue(this);
            queue_->push(t);
            return t;
        }
    }
    return 0;
}


Log& IconObject::log()
{
    if (log_ == 0)
        log_ = new Log(this);
    return *log_;
}

Path IconObject::logPath()
{
    return log().path();
}

/*Request IconObject::request() const
{
	cout << "Oops, should not be called..." << endl;
	return Request("");
}

void IconObject::request(const Request&)
{
	cout << "Oops, should not be called..." << endl;
}*/

Request IconObject::fullRequest() const
{
    return request();
}

Request IconObject::requestForVerb(const string& verb) const
{
    Request r = request();
    if (r.getVerb() == verb)
        return r;

    return Request();
}

MvIconLanguage& IconObject::language() const
{
    return iconClass().language();
}

IconObject* IconObject::search(const string& fullName)
{
    //cout << "search " << fullName << endl;

    //return Folder::icon(fullName);

    if (fullName[0] != '/')
        return 0;

    Tokenizer parse("/");
    vector<string> n;
    parse(fullName, n);

    vector<string> names;

    for (vector<string>::iterator j = n.begin(); j != n.end(); ++j) {
        if (*j == "" || *j == ".")
            continue;

        if (*j == "..")
            names.pop_back();
        else
            names.push_back(*j);
    }

    return Folder::top()->find(names);
}

IconObject* IconObject::search(IconObject& /*o*/, const string& /*relativeName*/)
{
    return 0;
}

IconObject* IconObject::find(const vector<string>& n)
{
    return (n.size() == 0) ? this : 0;
}

IconObject* IconObject::find(const string& /*name*/)
{
    return 0;
}

void IconObject::saveInfo()
{
    //info().save(dotPath());
}

void IconObject::reparent(Folder* parent)
{
    if (parent_ != parent) {
        modified();

        Path old_path = path();
        Path old_emb  = embeddedPath();

        parent_->detach();
        parent_ = parent;
        parent_->attach();

        Path new_path = path();
        Path new_emb  = embeddedPath();

        old_path.rename(new_path);
        if (old_emb.exists())
            old_emb.rename(new_emb);

        modified();
    }
}

bool IconObject::checkNewName(const string& new_name)
{
    if (name_ == new_name)
        return false;

    if (!renamable()) {
        Log::error(this) << "Object cannot be renamed" << endl;
        return false;
    }

    if (parent()->find(new_name)) {
        Log::error(this) << "Cannot rename " << name() << " to " << new_name << ", an icon already exists" << endl;
        return false;
    }

    if ((new_name[0] == '.' && name_[0] != '.') || new_name.find("/") != string::npos || new_name.length() == 0) {
        Log::error(this) << "Cannot rename " << name() << " to " << new_name << ", invalid name" << endl;
        return false;
    }

    return true;
}


bool IconObject::rename(const string& new_name)
{
    if (!checkNewName(new_name))
        return false;

    modified();

    string old_name          = name_;
    string old_embedded_name = embeddedName(name_);

    Path old_path          = path();
    Path old_embedded_path = embeddedPath();
    Folder* old_emb        = embeddedFolder(false);

    name_ = new_name;

    Path new_path = path();
    old_path.rename(new_path);

    //If there is embedded folder
    if (old_embedded_path.exists()) {
        if (old_emb) {
            old_emb->rename(embeddedName(name()));
        }
        else {
            old_embedded_path.rename(embeddedPath());
        }
    }

    parent()->renamed(this, old_name, new_name);
    modified();

    return true;
}

void IconObject::position(int x, int y)
{
    info().position(x, y);
    saveInfo();
}

void IconObject::createFiles()
{
}

set<string> IconObject::can()
{
    set<string> c = iconClass().can();

    if (iconClass().canHaveLog() &&
        (iconClass().type() != "Visdef" && iconClass().type() != "Window" &&
         iconClass().type() != "Plotmode")) {
        c.insert("log");
    }

    if (parent()->descendant(Folder::folder("wastebasket")))
        c.erase("delete");

    if (locked() && !isLink()) {
        c.erase("cut");
        c.erase("delete");
        c.erase("destroy");
        c.erase("rename");
    }

    if (isBrokenLink()) {
        c.erase("duplicate");
        c.erase("copy");
        c.erase("rename");
    }

    /*Folder *f=parent();
	if(f && f->locked())
	{
	  	c.erase("duplicate");
	  	c.erase("delete");
		c.erase("cut");
		c.erase("destroy");
		c.erase("compress");
	}*/

    if (c.find("save") == c.end())
        c.erase("clear");

    c.insert("remove");
    //c.insert("output");
    c.insert("mergeIcon");
    c.insert("replaceIcon");

    //TODO: find a better solution
    if (iconClass().name() != "ECCHARTS") {
        c.erase("export_macro");
        c.erase("export_python");
    }

    return c;
}

//-------------------------------------------------------------------------
void IconObject::toWastebasket()
{
    Folder* f = Folder::folder("wastebasket");
    if (f == 0) {
        Log::error(this) << "Can't find wastebasket!" << endl;
    }
    else {
        //position(0,0);
        f->adopt(this);
    }
}


void IconObject::edit()
{
    set<string> c = iconClass().can();
    if (c.find("edit") != c.end()) {
        Editor::open(this);
    }
}

void IconObject::edit2()
{
    //Editor::open(this,"ExternalTextEditor");
}

void IconObject::showLog()
{
    MvQLogDialog::open(this);
}

//-------------------------------------------------------------------------

void IconObject::duplicate()
{
    clone(parent(), info().x() + 20, info().y() + 20, false);
    //parent()->position(c,info().x()+20,info().y()+20);
}

IconObject* IconObject::clone(Folder* f, bool update)
{
    return clone(f, IconInfo::undefX(), IconInfo::undefY(), update);
}

IconObject* IconObject::clone(Folder* f, int x, int y, bool update)
{
    //createFiles();

    string new_name;

    f->scan();

    if (update)
        new_name = name();
    else if (f == parent())
        new_name = f->duplicateName(name());
    else
        new_name = f->uniqueName(name());

    Path old_path = path();
    Path old_emb  = embeddedPath();

    Path new_path = f->path().add(new_name);
    Path new_emb  = f->path().add(embeddedName(new_name));

    old_path.copy(new_path);
    if (old_emb.exists()) {
        old_emb.copy(new_emb);
    }

    IconObject* obj = IconFactory::create(f, new_name, *class_, x, y);

    return obj;
}


//-------------------------------------------------------------------------

void IconObject::editor(Editor* e)
{
    if (editor_ != e) {
        editor_ = e;
        if (e)
            notifyOpened();
        else
            notifyClosed();
    }
}

Editor* IconObject::editor()
{
    return editor_;
}

void IconObject::raiseEditor()
{
    if (editor_)
        editor_->raiseIt();
}

//-------------------------------------------------------------------------

void IconObject::logWindow(MvQLogDialog* e)
{
    logWindow_ = e;
}

MvQLogDialog* IconObject::logWindow()
{
    return logWindow_;
}

//-------------------------------------------------------------------------

void IconObject::removeFiles()
{
    modified();
    path().remove();
    if (embeddedPath().exists())
        embeddedPath().remove();
}

//-------------------------------------------------------------------------

void IconObject::modified()
{
    MvApplication::notifyIconModified(
        fullName().c_str(),
        className().c_str());

    // Forget any outstanding task
    reset();
    notifyChanged();
}

void IconObject::created()
{
    MvApplication::notifyIconCreation(
        fullName().c_str(),
        className().c_str());
}

string IconObject::dropText() const
{
    Request r = request();
    return request2string(r);
}

string IconObject::relativeName(IconObject* other) const
{
    return relpath(fullName().c_str(), other->fullName().c_str());
}

string IconObject::makeFullName(const string& name) const
{
    return makepath(parent()->fullName().c_str(), name.c_str());
}

//-------------------------------------------------------------------------

void IconObject::reset()
{
    if (logWindow_)
        logWindow_->reject();

    if (log_) {
        delete log_;
        log_ = 0;
    }

    if (queue_)
        queue_->ownerGone();
    queue_ = 0;

    dependancies_.clear();
}

//-------------------------------------------------------------------------

void IconObject::notifyWaiting()
{
    status_ = WaitingStatus;
    if (parent_)
        parent()->tellObservers(&FolderObserver::waiting, this);
}

void IconObject::notifyError()
{
    status_ = ErrorStatus;
    if (parent_)
        parent()->tellObservers(&FolderObserver::error, this);
}

void IconObject::notifyDestroyed()
{
    // iterate through a copy to avoid circular reference
    auto obsTmp = observers_;
    for (auto *o: obsTmp) {
        o->iconDestroyed(this);
    }
}

void IconObject::notifyChanged()
{
    status_ = DefaultStatus;
    parent_->tellObservers(&FolderObserver::modified, this);

    for (set<IconObserver*>::iterator j = observers_.begin(); j != observers_.end(); ++j)
        (*j)->iconChanged(this);
}

void IconObject::notifyReady()
{
    status_ = ReadyStatus;
    if (parent_)
        parent_->tellObservers(&FolderObserver::ready, this);
}
void IconObject::notifyOpened()
{
    if (parent_)
        parent_->tellObservers(&FolderObserver::opened, this);
}
void IconObject::notifyClosed()
{
    if (parent_)
        parent_->tellObservers(&FolderObserver::closed, this);
}


//-------------------------------------------------------------------------

void IconObject::addObserver(IconObserver* o)
{
    if (observers_.find(o) == observers_.end())
        observers_.insert(o);
}

void IconObject::removeObserver(IconObserver* o)
{
    if (observers_.find(o) != observers_.end())
        observers_.erase(o);
}

//-------------------------------------------------------------------------

const set<DependancyH>& IconObject::dependancies()
{
    return dependancies_;
}

//-------------------------------------------------------------------------

void IconObject::destroy()
{
    IconObjectH save = this;
    removeFiles();
    parent()->release(this);  //this notify the folderobservers!!

    //Do we need it?
    notifyDestroyed();

    if (editor_)
        editor_->empty();
}

void IconObject::empty()
{
    // Not called, obly wastebasket
}

bool IconObject::renamable() const
{
    //return !locked() && !parent()->locked() && editor_ == 0;
    return !locked() && !parent()->locked();
}

bool IconObject::locked() const
{
    return locked_;
}

void IconObject::lock()
{
    locked_ = true;
}

bool IconObject::temporary() const
{
    return false;
}

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

vector<IconObjectH> IconObject::subObjects(const string& param, const Request& r)
{
    int i     = 0;
    bool more = false;
    vector<IconObjectH> result;

    do {
        MvRequest a   = r(param.c_str(), i);
        const char* b = r(param.c_str(), i);

        more          = false;
        IconObjectH o = 0;

        if (a) {
//            a.print();
//            if (b)
//                cout << b << endl;

            more = true;
            // Embbeded object:
            // We need to create a temporary

//            cout << "Embedded object" << endl;
            o = IconFactory::create(this, a);
        }

        if (b && strcmp(b, "#")) {
            more = true;
            // Real object
            string name = makeFullName(b);
            o           = search(name);
//            if (o == 0)
//                cout << "Cannot find " << name << endl;
        }

        if (o != 0)
            result.push_back(o);

        i++;

    } while (more);

    return result;
}


void IconObject::setSubObjects(const string& name,
                               const vector<IconObjectH>& sub, Request& r, bool clean)
{
    //Delete the unused embedded subObjects
    if (clean) {
        vector<IconObjectH> oriSub = subObjects(name, r);
        for (vector<IconObjectH>::const_iterator j = oriSub.begin(); j != oriSub.end(); ++j) {
            IconObject* o = *j;
            if (o && o->isEmbedded()) {
                if (std::find(sub.begin(), sub.end(), o) == sub.end()) {
                    o->destroy();
                }
            }
        }
    }

    r.unsetParam(name.c_str());

    // We cannot mix embedded objects and real objects

    for (vector<IconObjectH>::const_iterator j = sub.begin(); j != sub.end(); ++j) {
        IconObject* o = *j;
        if (o->temporary()) {
            r(name.c_str()) += o->request();
        }
        else {
            string rel = relativeName(o);
            r(name.c_str()) += rel.c_str();
//            cout << "subObjects " << *this << endl;
//            cout << " subObjects " << *o << endl;
//            cout << " subObjects " << rel << endl;
        }
    }

    r.print();
}

bool IconObject::visible() const
{
    return name_[name_.length() - 1] != '#';
}

bool IconObject::sameAs(IconObject* other)
{
    if (&iconClass() != &other->iconClass())
        return false;

    Path p1 = this->path();
    Path p2 = other->path();

    FILE* f1 = fopen(p1.str().c_str(), "r");
    FILE* f2 = fopen(p2.str().c_str(), "r");

    bool same = f1 && f2;

    while (same) {
        int c1 = getc(f1);
        int c2 = getc(f2);
        same   = (c1 == c2);
        if (c1 == EOF || c2 == EOF)
            break;
    }

    if (f1)
        fclose(f1);
    if (f2)
        fclose(f2);

    return same;
}

void IconObject::touch()
{
    path().touch();
}

bool IconObject::isLink() const
{
    return link_;
}

bool IconObject::isBrokenLink() const
{
    if (!link_)
        return false;
    else {
        return !path().exists();
    }
}

bool IconObject::isInTimer() const
{
    return false;
}

void IconObject::drop(IconObject*)
{
}

bool IconObject::match(const std::string& namePattern, const std::string& typePattern)
{
    std::string s;
    bool nameOk = false;
    bool typeOk = false;

    if (namePattern.empty()) {
        nameOk = true;
    }
    else {
        s = name_;
        std::transform(s.begin(), s.end(), s.begin(), ::tolower);
        if (s.find(namePattern) != string::npos)
            nameOk = true;
    }

    if (typePattern.empty()) {
        typeOk = true;
    }
    else {
        s = class_->name();
        std::transform(s.begin(), s.end(), s.begin(), ::tolower);
        if (s.find(typePattern) != string::npos)
            typeOk = true;
        if (!typeOk) {
            s = class_->type();
            std::transform(s.begin(), s.end(), s.begin(), ::tolower);
            if (s.find(typePattern) != string::npos)
                typeOk = true;
        }
        if (!typeOk) {
            s = class_->iconBox();
            std::transform(s.begin(), s.end(), s.begin(), ::tolower);
            if (s.find(typePattern) != string::npos)
                typeOk = true;
        }
    }


    return nameOk && typeOk;
}

bool IconObject::checkRequest()
{
    Request r = request();
    if (r) {
        string verb   = r.getVerb();
        string icName = iconClass().name();

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

        return (verb == icName);
    }
    return false;
}
