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

#include "IconClass.h"
#include "IconFactory.h"
#include "RootFolder.h"
#include "Items.h"
#include "FolderInfo.h"
#include "FolderSettings.h"
#include "FolderObserver.h"
#include "Request.h"
///#include "FolderWindow.h"

//#define DEBUG_FOLDER__

using std::string;

std::vector<Folder*> Folder::folders_;
const unsigned int Folder::maxNokidsNum_ = 100;

// Path stores the full path aka fullName
// Name stores the directory name

Folder::Folder(Folder* p, const IconClass& kind,
               const std::string& name, IconInfo* info) :
    IconObject(p, kind, name, info),
    ready_(false),
    scanIsOn_(false),
    folderInfo_(nullptr),
    settings_(nullptr)

{
}

Folder::~Folder()
{
    if (folderInfo_)
        delete folderInfo_;
    if (settings_)
        delete settings_;
}


FolderInfo* Folder::folderInfo()
{
    if (!folderInfo_)
        folderInfo_ = new FolderInfo(this);

    return folderInfo_;
}

FolderSettings* Folder::settings()
{
    if (!settings_)
        settings_ = new FolderSettings(this);

    return settings_;
}

int Folder::numOfIconKids()
{
    int cnt = 0;
    for (auto& kid : kids_)
        if (!kid.second->isFolder())
            cnt++;
    return cnt;
}


void Folder::saveFolderInfo()
{
    if (!locked_)
        folderInfo()->write();
}

void Folder::addObserver(FolderObserver* o)
{
    if (o) {
        if (!observers_.insert(o).second)
            return;
        // if(!ready_)
        //	scan();
        // else
        //	for(KidMap::iterator j = kids_.begin(); j != kids_.end(); ++j)
        //		o->arrived((*j).second);
    }
}

void Folder::removeObserver(FolderObserver* o)
{
    observers_.erase(o);
}

IconObject* Folder::create(const std::string& name)
{
    IconObject* obj = find(name);
    if (obj == nullptr) {
        if (noKids_.find(name) == noKids_.end()) {
            obj = IconFactory::create(this, name);
            if (obj == nullptr) {
                noKids_.insert(name);
            }
        }
    }

    return obj;
}

IconObject* Folder::findMulti(const std::vector<std::string>& names)
{
#ifdef DEBUG_FOLDER__
    std::cout << "Folder::find " << names.size() << std::endl;
    for (int i = 0; i < names.size(); i++)
        std::cout << '|' << names[i] << '|';
    std::cout << std::endl;
#endif
    if (names.size() == 0)
        return this;

    if (names.size() == 1)
        return find(names[0]);

    std::vector<std::string> rest(names.begin() + 1, names.end());
    IconObject* s = find(names[0]);

    return s ? s->findMulti(rest) : nullptr;
}

IconObject* Folder::find(const std::string& name)
{
    if (!ready_)
        scan();

    auto j = kids_.find(name);
    if (j == kids_.end())
        return nullptr;
    else
        return (*j).second;
}

#if 0
/*void Folder::edit()
{
	open();


	// if the user wants to keep just one folder view open, then
	// close the parent one

	MvRequest myPref = MvApplication::getPreferences();
	const char* openEachFolder = myPref( "OPEN_EACH_FOLDER" );

	if ( (openEachFolder != nullptr) && (strcmp( openEachFolder, "In Same Window") == 0))
	{
		if (parent_) parent_->close();
	}
}*/

#endif

/*string Folder::fullName() const
{
    return path_.str();
}

Path Folder::path() const
{
    return path_;
}*/


void Folder::scan()
{
    ready_ = true;
    scanIsOn_ = true;

    // cout << "Scan for: " << fullName() << std::endl;

    std::set<string> files = path().files();

    // Create and iconObject for each file/dir with the exception of the
    // dirs ending with a # character.
    for (const auto& file : files) {
        // if((*i).rfind("#") != (*i).size()-1)
        //{
        create(file);
        //}
    }

    // Remove non-existing icons

    std::vector<IconObjectH> erase;

    for (auto& kid : kids_)
        if (files.find(kid.first) == files.end())
            erase.push_back(kid.second);

    for (auto& k : erase)
        release(k);

    saveFolderInfo();
    scanIsOn_ = false;

    // Clear nokids if they overgrew
    if (noKids_.size() > maxNokidsNum_)
        noKids_.clear();
}


void Folder::scanForNewFile(const std::string& name, int x, int y)
{
    ready_ = true;
    scanIsOn_ = true;

    IconObject* obj = find(name);
    if (obj == nullptr) {
        IconFactory::create(this, name, x, y);
    }

    saveFolderInfo();
    scanIsOn_ = false;
}

void Folder::recheckKidsIconClass()
{
    bool changed = false;
    for (auto& kid : kids_) {
        auto obj = kid.second;
        if (obj->recheckIconClass()) {
            changed = true;
            for (auto observer : observers_)
                observer->modified(obj);
        }
    }

    if (changed) {
        saveFolderInfo();
    }
}

void Folder::recheckKidIconClass(IconObject* obj)
{
    for (auto& kid : kids_) {
        if (kid.second == obj) {
            if (obj->recheckIconClass()) {
                for (auto observer : observers_) {
                    observer->modified(obj);
                }
                saveFolderInfo();
            }
            return;
        }
    }
}

#if 0
void Folder::visit(FolderVisitor& visitor) 
{	    
	if(!ready_)
		scan();

 	 for(KidMap::iterator j = kids_.begin(); j != kids_.end(); ++j)
	 	visitor.visit((*j).second);
}

//MODIF
Folder* Folder::top()
{
  	return 0;
	//static RootFolder root;
	//return &root;
}
#endif

#if 0  
Folder* Folder::folder(const std::string& name,bool create)
{

  
  
 return 0;

 	Folder* f;

	switch(name[0])
	{
		case 0:
			return top();
			//break;

		case '/':
			f = dynamic_cast<Folder*>(IconObject::search(name));
			if(f == 0 && create)
				f = dynamic_cast<Folder*>(IconFactory::create(name,IconClass::find("FOLDER")));
			return f;
			//break;

		default:
		   return dynamic_cast<Folder*>(Items::find(name));
		   //break;
	}
}

Folder* Folder::folder(const std::string& name1,const string& name2,bool create)
{
	return 0;
	
	Folder* f = folder(name1,create);
	return f?folder(f->fullName()+"/"+name2,create):0;
}

Folder* Folder::folder(const std::string& name1,const string& name2,
	const std::string& name3,bool create)
{
	return 0;
	
	Folder* f = folder(name1,name2);
	return f?folder(f->fullName()+"/"+name3):0;
}

#endif

bool Folder::adopt(IconObject* o)
{
    if (find(o->name()) != nullptr) {
        // Log::error(o) << "Icon already exists" << std::endl;
        return false;
    }

    auto* f = dynamic_cast<Folder*>(o);
    if (f && f->ancestor(this)) {
        marslog(LOG_WARN, "Folder::adopt - Infinite loop when trying to adopt icon=%s", o->name().c_str());
        return false;
    }

    f = o->parent();
    if (!f && o->iconClass().type() != "Folder") {
        marslog(LOG_WARN, "Folder::adopt - Cannot adopt icon object without a parent! icon=%s", o->name().c_str());
        return false;
    }

    kids_[o->name()] = o;
    folderInfo()->add(o->name(), &o->info());

    if (f != this) {
        o->reparent(this);
        if (f)
            f->release(o);
    }

    for (auto observer : observers_)
        observer->arrived(o);

    if (!scanIsOn_)
        saveFolderInfo();

    // If we have not scanned the folder yet we nned to do it now!
    if (!ready_) {
        scan();
    }

    return true;
}

bool Folder::release(IconObject* o)
{
    if (find(o->name()) == nullptr) {
        // Error("icon does not exist exists");
        return false;
    }

    kids_.erase(o->name());
    folderInfo()->remove(o->name());

    for (auto observer : observers_)
        observer->gone(o);

    saveFolderInfo();

    return true;
}

#if 0
void Folder::createFiles()
{
//MODIF
	return;
	IconObject::createFiles();
	path().mkdir();
}

set<string> Folder::can()
{
	set<string> c = IconObject::can();
	c.erase("examine");
	return c;
}
#endif

void Folder::createFiles()
{
    IconObject::createFiles();
    if (!path().exists())
        path().mkdir();
}


string Folder::uniqueName(const std::string& name)
{
    char buf[1024];
    int i = 0;

    if (kids_.find(name) == kids_.end())
        return name;

    Path p(name);
    std::string fName;
    std::string fSuffix;
    p.nameAndSuffix(fName, fSuffix);

    // For macro we want to keep the suffix
    if (fSuffix == "mv") {
        do {
            sprintf(buf, "%s_%d.%s", fName.c_str(), ++i, fSuffix.c_str());
        } while (kids_.find(buf) != kids_.end());
    }
    else {
        do {
            sprintf(buf, "%s %d", name.c_str(), ++i);
        } while (kids_.find(buf) != kids_.end());
    }

    return buf;
}


string Folder::duplicateName(const std::string& name)
{
    char buf[1024];
    int i = 0;
    int d = 0;

    if (name.find("Copy ") == 0) {
        std::size_t n = name.find(" of ");
        if (n != std::string::npos) {
            std::string s = name.substr(5, n - 5);
            if (is_number(s.c_str()))
                d = n + 4;
        }
    }

    do {
        sprintf(buf, "Copy %d of %s", ++i, name.c_str() + d);
    } while (kids_.find(buf) != kids_.end());

    return buf;
}


void Folder::position(IconObject* o, int x, int y)
{
    for (auto observer : observers_)
        observer->position(o, x, y);

    if (observers_.size() == 0)
        o->position(x, y);
}


void Folder::renamed(IconObject* o, const std::string& old_name, const string& new_name)
{
    IconObjectH save = o;
    kids_.erase(old_name);
    kids_[new_name] = o;

    folderInfo()->rename(old_name, new_name);

    for (auto observer : observers_)
        observer->renamed(o, old_name);

    saveFolderInfo();
}

void Folder::tellObservers(TellObserverProc proc, IconObject* o)
{
    IconObjectH save = o;
    for (auto f : observers_) {
        (f->*proc)(o);
    }
}

#if 0

void Folder::open()
{
//MODIF
return;
	//FolderWindow::open(this);
}

void Folder::close()
{
//MODIF
return;
	//FolderWindow::close(this);
}


#endif

bool Folder::ancestor(IconObject* o)
{
    if (o == this)
        return true;

    for (auto& kid : kids_) {
        IconObject* p = kid.second;
        if (o == p)
            return true;

        auto* f = dynamic_cast<Folder*>(p);
        if (f && f->ancestor(o))
            return true;
    }

    return false;
}

bool Folder::descendant(Folder* f)
{
    if (!f)
        return false;
    if (f == this)
        return true;
    return (parent()) ? parent()->descendant(f) : false;
}

#if 0

class FolderCopier : public FolderVisitor {
	FolderH target_;
	virtual void visit(IconObject* o) { 
		if(!target_->find(o->name()))
			o->clone(target_, false); 
	}
public:
	FolderCopier(Folder* f) : target_(f) {}
};

void Folder::copyContent(Folder* from)
{
//MODIF
return;
	
	FolderCopier cp(this);
	if(from) from->visit(cp);
}


class FolderCompare : public FolderVisitor {
	FolderH other_;
	virtual void visit(IconObject* o);
	bool same_;
public:
	FolderCompare(Folder* f) : other_(f),same_(true) {}
	bool same() const { return same_; }
};

void FolderCompare::visit(IconObject* o)
{
	if(!same_) return;

	IconObject* x = other_->find(o->name());
	if(x == 0) 
		same_ = false;
}

bool Folder::sameAs(IconObject* other)
{
	Folder* f = dynamic_cast<Folder*>(other);
	if(f == 0) return false;

	FolderCompare cmp1(this);
	f->visit(cmp1);

	FolderCompare cmp2(f);
	this->visit(cmp2);

	return cmp1.same() && cmp2.same();

}

void Folder::notifyChanged()
{
	// When a folder changes, (its name... )
	// mark all icons changed as well

	IconObject::notifyChanged();

	for(KidMap::iterator j = kids_.begin(); j != kids_.end(); ++j)
	{
		IconObject *p = (*j).second;
		p->notifyChanged();
	}
}

void Folder::destroy()
{
 //MODIF
 return;
 
 if( ! isLink() )     //-- do not delete contents if link!
    {
	//-- IBM/AIX/xlC+STL loops with this construct:
	//for(KidMap::iterator j = kids_.begin(); j != kids_.end(); ++j)
	//{
	//	IconObject *p = (*j).second;
	//	p->destroy();
	//}

	//-- this one ok for xlC+STL:
	KidMap::iterator j = kids_.begin();
	while( j != kids_.end() )
	{
		IconObject *p = (*j).second;
		++j;
		p->destroy();
	}
    }

  IconObject::destroy();
}

void Folder::empty()
{
//MODIF
return;

	scan();
	KidMap kids = kids_;
	for(KidMap::iterator j = kids.begin(); j != kids.end(); ++j)
	{
		IconObject *p = (*j).second;
		p->toWastebasket();
	}
	scan();

}

void Folder::drop(IconObject* o)
{
//MODIF
return;

if(!locked())
	{
		o->position(0,0);
		adopt(o);
		parent()->tellObservers(&FolderObserver::highlight,this);
	}
}

#endif


void Folder::destroy()
{
    if (!isLink())  //-- do not delete contents if link!
    {
        auto kidsCopy = kids_;
        for (auto& kid : kidsCopy) {
            IconObject* p = kid.second;
            p->destroy();
        }
    }

    IconObject::destroy();
}

void Folder::iconClasses(std::map<std::string, IconObjectH>& res)
{
    for (auto& kid : kids_) {
        res[kid.second->className()] = kid.second;
    }
}

std::set<string> Folder::can()
{
    std::set<string> c = IconObject::can();
    c.erase("edit");
    c.erase("log");
    c.erase("compress");
    if (!isBrokenLink()) {
        c.insert("open");
        c.insert("openInTab");
        c.insert("openInWin");
        c.insert("bookmark");
    }
    else {
        c.erase("open");
        c.erase("openInTab");
        c.erase("openInWin");
        c.erase("bookmark");
    }

    return c;
}

Request Folder::request() const
{
    return {""};
}

//========================================================
//
//  Static methods
//
//========================================================

Folder* Folder::top()
{
    static RootFolder root;
    return &root;
}


Folder* Folder::folder(const std::string& name, bool create)
{
    Folder* f = nullptr;

    if (name == "/")
        return top();

    switch (name[0]) {
        case 0:
            return nullptr;
            // return top();
            // break;

        case '/':
            f = dynamic_cast<Folder*>(IconObject::search(name));
            if (f == nullptr && create)
                f = dynamic_cast<Folder*>(IconFactory::create(name, IconClass::find("FOLDER")));
            return f;
            // break;

        default:
            return dynamic_cast<Folder*>(Items::find(name));
            // break;
    }
}
/*
Folder* Folder::folder(const std::string&path,bool create)
{
        if(path.empty()) return 0;

    if(path.find("/") !=std::string::npos)
    {
        for(vector<Folder*>::iterator it=Folder::folders_.begin(); it != Folder::folders_.end(); it++)
        {
            if((*it)->path().str() == path)
            {
                return *it;
            }
        }

        if(create)
        {
            IconObject* obj = IconFactory::create(0,path,IconClass::find("FOLDER"));
            if(obj)
            {
                return static_cast<Folder*>(obj);
            }
        }
    }
    else
    {
        return static_cast<Folder*>(Items::find(path));
    }

    return 0;
}*/

Folder* Folder::folder(const std::string& name1, const string& name2, bool create)
{
    Folder* f = folder(name1, create);
    return f ? folder(f->fullName() + "/" + name2, create) : nullptr;
}

Folder* Folder::folder(const std::string& name1, const string& name2,
                       const std::string& name3, bool /*create*/)
{
    Folder* f = folder(name1, name2);
    return f ? folder(f->fullName() + "/" + name3) : nullptr;
}

IconObject* Folder::icon(const std::string& /*fullName*/)
{
    /*Path p(fullName);
    Folder *f=Folder::folder(p.directory().str());

    IconObject *obj=0;

    if(f)
    {
        obj=f->find(p.name());
        if(!obj)
        {
            f->scan();
            obj=f->find(p.name());
        }
    }

    else
    {
        obj=IconFactory::create(p.directory().str(),IconClass::find("FOLDER"));
        if(obj)
        {
            f=static_cast<Folder*>(obj);
            obj=f->find(p.name());
        }
    }

    return obj;*/
    return nullptr;
}

static IconMaker<Folder> maker1("FOLDER");
static IconMaker<Folder> maker2("Folder");
