//#####################################################################
// Copyright 2020, Craig Schroeder.
// This file is part of PhysBAM whose distribution is governed by the license contained in the accompanying file PHYSBAM_COPYRIGHT.txt.
//#####################################################################
#ifndef __QT_FILE_MANAGER__
#define __QT_FILE_MANAGER__

#include <Core/Log/LOG.h>
#include <Core/Read_Write/FILE_UTILITIES.h>
#include <Core/Vectors/VECTOR.h>
#include <functional>
#include <map>
#include <memory>
#include <string>

namespace PhysBAM{

struct VIEWER_DIR;
class QT_RENDER_UTILS;
struct QT_COMPONENT_OPTION;
struct QT_COMPONENT_DATA;
class QT_WORLD;
class QT_WINDOW;
template<class TK,class T> class HASHTABLE;

class QT_OBJECT_BASE
{
public:
    bool stale=true;
    bool valid=false;
    bool common=false;

    virtual ~QT_OBJECT_BASE() = default;
    virtual void Read() = 0;

    std::pair<std::string,bool> Find_File(const VIEWER_DIR& viewer_dir,const std::string& file);
    void Invalidate(bool invalidate_common=false);
};

template<class OBJ>
class QT_OBJECT:public QT_OBJECT_BASE
{
protected:
    // Must never be null
    OBJ* obj=nullptr;
public:
    
    QT_OBJECT(OBJ* obj)
        :obj(obj){}

    OBJ& Obj(){return *obj;}
};

template<class OBJ>
class QT_FILE_OBJECT:public QT_OBJECT<OBJ>
{
    const VIEWER_DIR& viewer_dir;
    std::string file;
    std::string loaded_file;
    OBJ obj_raw;
    
public:
    using QT_OBJECT<OBJ>::valid;
    using QT_OBJECT<OBJ>::stale;
    using QT_OBJECT<OBJ>::Find_File;

    QT_FILE_OBJECT(const VIEWER_DIR& viewer_dir,const std::string& file)
        :QT_OBJECT<OBJ>(&obj_raw),viewer_dir(viewer_dir),file(file){}

    template<class T> std::enable_if_t<sizeof((((T*)0)->Read_Obj(""),1))>
    Read_Helper(const std::string& filename,T*)
    {
        if(Ends_With(filename,".obj"))
        {
            obj_raw.mesh.Clean_Memory();
            obj_raw.particles.Clean_Memory();
            obj_raw.Read_Obj(filename);
        }
        else Read_From_File(filename,obj_raw);
    }

    void Read_Helper(const std::string& filename,...)
    {
        Read_From_File(filename,obj_raw);
    }

    void Read() override
    {
        if(!stale) return;
        stale=false;
        valid=false;
        auto [filename,common_file]=Find_File(viewer_dir,file);
        if(!filename.size()) return;
        try
        {
            Read_Helper(filename,&obj_raw);
            loaded_file=filename;
            valid=true;
            this->common=common_file;
        }
        catch(...)
        {
            LOG::printf("Failed to read %P\n",filename);
        }
    }

    static std::shared_ptr<QT_OBJECT<OBJ>>
    Make(const VIEWER_DIR& viewer_dir,const std::string& file)
    {
        static HASHTABLE<std::string,std::weak_ptr<QT_OBJECT<OBJ>>> hash;
        if(auto* p=hash.Get_Pointer(file))
            if(!p->expired())
                return p->lock();
        std::shared_ptr<QT_OBJECT<OBJ>> o(new QT_FILE_OBJECT(viewer_dir,file));
        hash.Get_Or_Insert(file)=o;
        return o;
    }
};

template<class OBJ,bool store_obj>
class QT_OBJECT_WRAPPER:public QT_OBJECT<OBJ>
{
    typedef ARRAY<std::shared_ptr<QT_OBJECT_BASE>> T_OBJECTS;
    typedef std::function<bool(OBJ*&,const T_OBJECTS&)> T_FUNC;
    typedef std::conditional_t<store_obj,OBJ,int> RAW_TYPE;
    T_FUNC func;
    RAW_TYPE obj_raw;
public:
    T_OBJECTS objects;

    using QT_OBJECT<OBJ>::valid;
    using QT_OBJECT<OBJ>::stale;

    QT_OBJECT_WRAPPER(const T_FUNC& func)
        :QT_OBJECT<OBJ>(store_obj?&obj_raw:nullptr),func(func)
    {}
    
    void Read() override
    {
        if(!stale) return;
        stale=false;
        valid=false;
        for(auto o:objects)
        {
            if(!o) return;
            o->Read();
            if(!o->valid) return;
        }
        valid=func(this->obj,objects);
    }
};
}
#endif
