/***************************** 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 "IconObject.h"

#include "Log.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 "MvLog.h"

#include <algorithm>
#include <sstream>
#include <string>
#include <cassert>

#include "Metview.h"
#include "MvLog.h"

#include <sys/stat.h>

std::vector<IconObject*> IconObject::objects_;


IconObject::IconObject(Folder* parent, const IconClass& kind, const std::string& name,
                       IconInfo* info) :
    name_(name),
    parent_(parent),
    class_(const_cast<IconClass*>(&kind)),
    info_(info),
    queue_(nullptr),
    log_(nullptr),
    editor_(nullptr),
    logWindow_(nullptr),
    locked_(false),
    link_(false),
    status_(DefaultStatus)
{
    Path p("");
    if (parent_) {
        parent_->attach();
    }
    objects_.push_back(this);
}

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

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

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

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

IconInfo& IconObject::info() const
{
    if (info_ == nullptr) {
        Request infoReq("USER_INTERFACE");
        infoReq("ICON_CLASS") = editorClassName().c_str();
        auto* o = const_cast<IconObject*>(this);
        o->info_ = new IconInfo(infoReq);
        o->saveInfo();
        return *(o->info_);
    }

    return *info_;
}

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

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

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

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

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

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

Folder* IconObject::embeddedFolder(const std::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("#") != std::string::npos);
}

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

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

const std::string& IconObject::editorClassName() const
{
    return editorClass().name();
}

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

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

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


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

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

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


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

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

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

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

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

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

    return {};
}

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

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

    // return Folder::icon(fullName);

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

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

    std::vector<std::string> names;

    for (auto& j : n) {
        if (j == "" || j == ".")
            continue;

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

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

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

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

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

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 std::string& new_name)
{
    if (name_ == new_name)
        return false;

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

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

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

    return true;
}


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

    modified();

    std::string old_name = name_;
    std::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()
{
}

std::set<std::string> IconObject::can()
{
    std::set<std::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 == nullptr) {
        Log::error(this) << "Can't find wastebasket!" << std::endl;
    }
    else {
        // position(0,0);
        f->adopt(this);
    }
}


void IconObject::edit()
{
    std::set<std::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();

    std::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());
}

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

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

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

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

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

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

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

    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 (auto observer : observers_)
        observer->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 std::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;
}

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

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

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

        more = false;
        IconObjectH o = nullptr;

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

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

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

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

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

        i++;

    } while (more);

    return result;
}


void IconObject::setSubObjects(const std::string& name,
                               const std::vector<IconObjectH>& sub, Request& r, bool clean)
{
    // Delete the unused embedded subObjects
    if (clean) {
        std::vector<IconObjectH> oriSub = subObjects(name, r);
        for (const auto& j : oriSub) {
            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 (const auto& j : sub) {
        IconObject* o = j;
        if (o->temporary()) {
            r(name.c_str()) += o->request();
        }
        else {
            std::string rel = relativeName(o);
            r(name.c_str()) += rel.c_str();
            //            std::cout << "subObjects " << *this << std::endl;
            //            std::cout << " subObjects " << *o << std::endl;
            //            std::cout << " subObjects " << rel << std::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();
    }
}

void IconObject::initStateInfo()
{
    updateStateInfo(false);
}

void IconObject::updateStateInfo(bool broadcast)
{
    bool changed = false;

    bool linkVal = path().isSymLink();
    if (linkVal != link_) {
        changed = true;
    }
    link_ = linkVal;

    bool lockedVal = (link_ && isBrokenLink()) ? false : path().locked();
    if (lockedVal != locked_) {
        changed = true;
    }
    locked_ = lockedVal;

    if (broadcast && changed) {
        notifyChanged();
    }
}

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) != std::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) != std::string::npos)
            typeOk = true;
        if (!typeOk) {
            s = class_->type();
            std::transform(s.begin(), s.end(), s.begin(), ::tolower);
            if (s.find(typePattern) != std::string::npos)
                typeOk = true;
        }
        if (!typeOk) {
            s = class_->iconBox();
            std::transform(s.begin(), s.end(), s.begin(), ::tolower);
            if (s.find(typePattern) != std::string::npos)
                typeOk = true;
        }
    }


    return nameOk && typeOk;
}

bool IconObject::checkRequest()
{
    Request r = request();
    if (r) {
        std::string verb = r.getVerb();
        std::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;
}
