/**
   \file type_depository.hpp
   \brief depository and draw objects
   \author Junhua Gu
 */



#ifndef TYPE_DEPOSITORY_HPP
#define TYPE_DEPOSITORY_HPP
#include <string>
#include <utility>
#include <core/optimizer.hpp>
#include <core/fitter.hpp>
#include <map>
#include <iostream>
namespace opt_utilities
{

  enum fetch_direction
    {
      in,out
    };

  class type_unregistered
    :public opt_exception
  {
    const char* what()const throw()
    {
      return "type not registred";
    }
  };

  template <typename T>
  class holder
  {
  private:
    T* ptr;
  public:
    holder()
      :ptr(0)
    {}
    
    holder(T* p)
      :ptr(p)
    {}

    holder(const holder& rhs)
      :ptr(rhs.ptr)
    {
      const_cast<holder&>(rhs).ptr=0;
    }

    ~holder()
    {
      delete ptr;
    }

    holder& operator=(const holder& rhs)
    {
      if(this==&rhs)
	{
	  return *this;
	}
      delete ptr;
      ptr=rhs.ptr;
      const_cast<holder&>(rhs).ptr=0;
    }

  public:
    T* release()
    {
      T* p=ptr;
      ptr=0;
      return p;
    }

    void destroy()
    {
      delete ptr;
    }

    T* get()const
    {
      return ptr;
    }

    void reset(T* p)
    {
      destroy();
      ptr=p;
    }

    operator T*()
    {
      return ptr;
    }

    operator const T*()const
    {
      return ptr;
    }
    
  public:
    T& operator*()
    {
      return *ptr;
    }

  public:
    T* operator->()
    {
      return ptr;
    }
  };

  
  template <typename Ty,typename Tx>
  void delete_clone(const func_obj<Ty,Tx>* pfo)
  {
    const_cast<func_obj<Ty,Tx>* >(pfo)->destroy();
  }

  template <typename Ty,typename Tx>
  void fetch_func_obj(const func_obj<Ty,Tx>* &fo,std::string cname,fetch_direction dir=in)
  {
    static std::map<std::string,holder<func_obj<Ty,Tx> > >pm;
    typename std::map<std::string,
      holder<func_obj<Ty,Tx> > >::iterator it=pm.find(cname);
    
    if(dir==out)
      {
	if(it==pm.end())
	  {
	    std::cerr<<cname<<std::endl;
	    throw type_unregistered();;
	  }
	else
	  {
	    func_obj<Ty,Tx>* result=it->second;
	    fo=result;
	  }
      }
    else if(dir==in)
      {
	//pm.insert(make_pair(cname,holder<func_obj<Ty,Tx> >(fo->clone())));
	
	pm[cname]=holder<func_obj<Ty,Tx> >(fo->clone());
      }
  }

  template <typename Ty,typename Tx>
  void register_func_obj(const func_obj<Ty,Tx>& fo)
  {
    const func_obj<Ty,Tx>* pfo=&fo;
    fetch_func_obj(pfo,fo.get_type_name(),in);
  }

  template <typename Ty,typename Tx>
  const func_obj<Ty,Tx>* get_func_obj(std::string cname)
  {
    const func_obj<Ty,Tx>* pom;
    fetch_func_obj(pom,cname,out);
    return pom;
  }


  template <typename Ty,typename Tx>
  void delete_clone(const opt_method<Ty,Tx>* pfo)
  {
    const_cast<opt_method<Ty,Tx>* >(pfo)->destroy();
  }

  template <typename Ty,typename Tx>
  void fetch_opt_method(const opt_method<Ty,Tx>* &fo,std::string cname,fetch_direction dir)
  {
    static std::map<std::string,holder<opt_method<Ty,Tx> > > pm;
    typename std::map<std::string,
      holder<opt_method<Ty,Tx> > >::iterator it=pm.find(cname);
    
    if(dir==out)
      {
	if(it==pm.end())
	  {
	    std::cerr<<cname<<std::endl;
	    throw type_unregistered();;
	  }
	else
	  {
	    opt_method<Ty,Tx>* result=it->second;
	    fo=result;
	  }
      }
    else if(dir==in)
      {
	//pm.insert(cname,fo->clone());
	pm[cname]=holder<opt_method<Ty,Tx> >(fo->clone());
      }
  }

  template <typename Ty,typename Tx>
  void register_opt_method(const opt_method<Ty,Tx>& fo)
  {
    const opt_method<Ty,Tx>* pfo=&fo;
    fetch_opt_method(pfo,fo.get_type_name(),in);
  }

  template <typename Ty,typename Tx>
  const opt_method<Ty,Tx>* get_opt_method(std::string cname)
  {
    const opt_method<Ty,Tx>* pom;
    fetch_opt_method(pom,cname,out);
    return pom;
  }

  template<typename Ty,typename Tx,typename Tp,typename Ts,typename Tstr>
  void delete_clone(const statistic<Ty,Tx,Tp,Ts,Tstr>* pfo)
  {
    const_cast<statistic<Ty,Tx,Tp,Ts,Tstr>* >(pfo)->destroy();
  }


  template<typename Ty,typename Tx,typename Tp,typename Ts,typename Tstr>
  void fetch_statistic(const statistic<Ty,Tx,Tp,Ts,Tstr>* &fo,std::string cname,fetch_direction dir)
  {
    static std::map<std::string,holder<statistic<Ty,Tx,Tp,Ts,Tstr> > > pm;
    typename std::map<std::string,
      holder<statistic<Ty,Tx,Tp,Ts,Tstr> > >::iterator it=pm.find(cname);
    
    if(dir==out)
      {
	if(it==pm.end())
	  {
	    std::cerr<<cname<<std::endl;
	    throw type_unregistered();;
	  }
	else
	  {
	    statistic<Ty,Tx,Tp,Ts,Tstr>* result=it->second;
	    fo=result;
	  }
      }
    else if(dir==in)
      {
	//pm.insert(cname,fo->clone());
	pm[cname]=holder<statistic<Ty,Tx,Tp,Ts,Tstr> >(fo->clone());
      }
  }

  template<typename Ty,typename Tx,typename Tp,typename Ts,typename Tstr>
  void register_statistic(const statistic<Ty,Tx,Tp,Ts,Tstr>& fo)
  {
    const statistic<Ty,Tx,Tp,Ts,Tstr>* pfo=&fo;
    fetch_statistic(pfo,fo.get_type_name(),in);
  }

  template<typename Ty,typename Tx,typename Tp,typename Ts,typename Tstr>
  const statistic<Ty,Tx,Tp,Ts,Tstr>* get_statistic(std::string cname)
  {
    const statistic<Ty,Tx,Tp,Ts,Tstr>* pst;
    fetch_statistic(pst,cname,out);
    return pst;
  }

  template <typename Ty,typename Tx,typename Tp,typename Tstr>
  void delete_clone(const param_modifier<Ty,Tx,Tp,Tstr>* pfo)
  {
    const_cast<param_modifier<Ty,Tx,Tp,Tstr>* >(pfo)->destroy();
  }

  template <typename Ty,typename Tx,typename Tp,typename Tstr>
  void fetch_param_modifier(const param_modifier<Ty,Tx,Tp,Tstr>* &fo,std::string cname,fetch_direction dir)
  {
    static std::map<std::string,holder<param_modifier<Ty,Tx,Tp,Tstr> > > pm;
    typename std::map<std::string,
      holder<param_modifier<Ty,Tx,Tp,Tstr> > >::iterator it=pm.find(cname);
    
    if(dir==out)
      {
	if(it==pm.end())
	  {
	    std::cerr<<cname<<std::endl;
	    throw type_unregistered();
	  }
	else
	  {
	    param_modifier<Ty,Tx,Tp,Tstr>* result=it->second;
	    fo=result;
	  }
      }
    else if(dir==in)
      {
	//pm.insert(cname,fo->clone());
	pm[cname]=holder<param_modifier<Ty,Tx,Tp,Tstr> >(fo->clone());
      }
  }

  template<typename Ty,typename Tx,typename Tp,typename Tstr>
  void register_param_modifier(const param_modifier<Ty,Tx,Tp,Tstr>& fo)
  {
    const param_modifier<Ty,Tx,Tp,Tstr>* pfo=&fo;
    fetch_param_modifier(pfo,fo.get_type_name(),in);
  }

  template<typename Ty,typename Tx,typename Tp,typename Tstr>
  const param_modifier<Ty,Tx,Tp,Tstr>* get_param_modifier(std::string cname)
  {
    const param_modifier<Ty,Tx,Tp,Tstr>* ppm;
    fetch_param_modifier(ppm,cname,out);
    return ppm;
  }
  
  
  template <typename Ty,typename Tx>
  void delete_clone(const data_set<Ty,Tx>* pfo)
  {
    const_cast<data_set<Ty,Tx>* >(pfo)->destroy();
  }

  template <typename Ty,typename Tx>
  void fetch_data_set(const data_set<Ty,Tx>* &fo,std::string cname,fetch_direction dir)
  {
    static std::map<std::string,holder<data_set<Ty,Tx> > > pm;
    typename std::map<std::string,
      holder<data_set<Ty,Tx> > >::iterator it=pm.find(cname);
    
    if(dir==out)
      {
	if(it==pm.end())
	  {
	    std::cerr<<cname<<std::endl;
	    throw type_unregistered();;
	  }
	else
	  {
	    data_set<Ty,Tx>* result=it->second;
	    fo=result;
	  }
      }
    else if(dir==in)
      {
	//pm.insert(cname,fo->clone());
	pm[cname]=holder<data_set<Ty,Tx> >(fo->clone());
      }
  }

  template <typename Ty,typename Tx>
  void register_data_set(const data_set<Ty,Tx>& fo)
  {
    const data_set<Ty,Tx>* pfo=&fo;
    fetch_data_set(pfo,fo.get_type_name(),in);
  }

  template<typename Ty,typename Tx>
  const data_set<Ty,Tx>* get_data_set(std::string cname)
  {
    const data_set<Ty,Tx>* pds;
    fetch_data_set(pds,cname,out);
    return pds;
  }
  
  

  ////////////////////
  template <typename Ty,typename Tx,typename Tp,typename Tstr>
  void delete_clone(const model<Ty,Tx,Tp,Tstr>* pfo)
  {
    const_cast<model<Ty,Tx,Tp,Tstr>* >(pfo)->destroy();
  }


  template <typename Ty,typename Tx,typename Tp,typename Tstr>
  void fetch_model(const model<Ty,Tx,Tp,Tstr>* &fo,std::string cname,fetch_direction dir)
  {
    static std::map<std::string,holder<model<Ty,Tx,Tp,Tstr> > > pm;
    typename std::map<std::string,
      holder<model<Ty,Tx,Tp,Tstr> > >::iterator it=pm.find(cname);
    
    if(dir==out)
      {
	if(it==pm.end())
	  {
	    std::cerr<<cname<<std::endl;
	    throw type_unregistered();
	  }
	else
	  {
	    model<Ty,Tx,Tp,Tstr>* result=it->second;
	    fo=result;
	  }
      }
    else if(dir==in)
      {
	pm[cname]= holder<model<Ty,Tx,Tp,Tstr> >(fo->clone());
      }
  }

  template <typename Ty,typename Tx,typename Tp,typename Tstr>
  void register_model(const model<Ty,Tx,Tp,Tstr>& fo)
  {
    const model<Ty,Tx,Tp,Tstr>* pfo=&fo;
    fetch_model(pfo,fo.get_type_name(),in);
  }

  template <typename Ty,typename Tx,typename Tp,typename Tstr>
  const model<Ty,Tx,Tp,Tstr>* get_model(std::string cname)
  {
    const model<Ty,Tx,Tp,Tstr>* pds;
    fetch_model(pds,cname,out);
    return pds;
  }
  
}


#endif