//******************************************************************************
//** SCATMECH: Polarized Light Scattering C++ Class Library
//** 
//** File: inherit.cpp
//**
//** Thomas A. Germer
//** Optical Technology Division, National Institute of Standards and Technology
//** 100 Bureau Dr. Stop 8443; Gaithersburg, MD 20899-8443
//** Phone: (301) 975-2876; FAX: (301) 975-6991
//** Email: thomas.germer@nist.gov
//**
//** Version: 6.00 (February 2008)
//**
//******************************************************************************
#include "scatmech.h"
#include "inherit.h"
#include "askuser.h"
#include <sstream>

#include "filmtran.h"
#include "scattabl.h"
#include "dielfunc.h"
 
using namespace std;

namespace SCATMECH {

    bool Inheritance::show_model_mode=false;
    int Model::quiet=0;

    //
    // stringlist takes a string and parses it into a list of
    // strings without any whitespace...
    //
    void stringlist(const string& s,deque<string>& result) 
    {
        result.clear();
        result.push_back("");

        for (string::const_iterator ps=s.begin();ps!=s.end();++ps) {
            if (isspace(*ps)) {
                if (!result.back().empty()) result.push_back("");
            } else {
                result.back() += *ps;
            }
        }
        if (result.back().empty()) result.pop_back();
    }

    //
    // Wrap_String takes a string and formats it into lines with an indentation.
    //
    void Wrap_String(const string& in,string& out,int indent,int width) 
    {
        deque<string> ls;
        stringlist(in,ls);

        string indenter="\n";
        for (int i=0;i<indent;++i) indenter += space;
        int wide = width-indent;

        int currentwidth = 0;
        for (deque<string>::const_iterator pls=ls.begin();pls!=ls.end();++pls) {
            if ((pls->size()+currentwidth+1)>=wide) {
                out += indenter;
                currentwidth = pls->size()+1;
            } else {
                currentwidth += pls->size()+1;
            }
            out += *pls;
            out += space;
        }
    }

    //
    // display_descriptions returns a list of all of the class descriptions for a 
    // Model and its child classes. 
    //
    void
    display_descriptions(const Inheritance* inherit,const InheritanceList& ilist)
    {
        // Make sure the classes are registered...
        // Register_SCATMECH_Models();

        // Print out header...
        SCATMECH_output << endl << "-- Available " << inherit->get_name() << " Classes --" << endl;

        // Pointer to the list elements...
        InheritanceList::const_iterator v;

        // Point v to the begining of the list...

        int jj = 0,i;

        // Find the longest length of names...
        for (v = ilist.begin();v != ilist.end();++v) {
            int k = ((*v)->get_name()).size();
            if ((*v)->children.size()>1 && (*v)!=inherit) {
                k+=7;
            }
            if (k>jj) jj=k;
        }

        for (i=0,v = ilist.begin();v != ilist.end();++i,++v) {
            int k = ((*v)->get_name()).size();
            ostringstream message;
            message << "(" << (char)(i+'A') << ") ";
            SCATMECH_output << message.str();
            int menu=0;
            if ((*v)->children.size()>0 && (*v)!=inherit) {
                SCATMECH_output << "<MENU> ";
                menu=7;
            }
            SCATMECH_output << (*v)->get_name();
            if (Inheritance::show_model_mode) {
                for (int j=0;j<jj-k+2-menu;++j) SCATMECH_output << '.';
                string message;
                Wrap_String((*v)->get_description(),message,jj+6,80);
                SCATMECH_output << message;
            }
            SCATMECH_output << endl;
        }
    }

    //
    // get_model displays a list of a Model's child classes and asks the user to 
    // chose among them...
    //
    // If top==true, then one is at the top of the class heirarchy.
    //
    Model* 
    Inheritance::
    get_model(bool top) const
    {
        // First...make sure all the SCATMECH models are registered...
        // Register_SCATMECH_Models();

        while (1) {

            InheritanceList ilist;
  
            get_model_inheritance_list(ilist,2);
        
            // If there is only one element in the database, return that 
            // element...
            if (ilist.size()==1) return ilist[0]->make();

            InheritanceList::iterator v;
            for (v=ilist.begin(); v!=ilist.end(); ++v) {
                // Remove virtual elements that have no children or are the same as this...
                if ((*v)->is_virtual()&&((*v)->children.size()==0||(*v)==this)) { 
                    v = ilist.erase(v);
                }
            }

            // Print out the class names...
            display_descriptions(this,ilist);

            // Print out some other options...
            SCATMECH_output << endl;
            if (show_model_mode) {
                SCATMECH_output << "(?) Turn off descriptions" << endl;
            } else {
                SCATMECH_output << "(?) Turn on descriptions" << endl;
            }

            if (!top) SCATMECH_output << "(^) Return to previous menu" << endl;
        
            string response = AskUser("\nChoose one",string("A"));
        
            Model* model = get_named_model(response,true);
        
            if (model) {
                return model;
            } else if (response[0]=='?') {
                show_model_mode= !show_model_mode;
            } else if (response[0]=='^' && !top) {
                return (Model*)0;
            } else if (response.size()==1) {
                // Convert alphanumeric response to index number...
                int k = toupper(response[0])-'A';
                // Make sure it is within range...
                if (k>=0&&k<ilist.size()) {
                    // Get that model's instance...
                    InheritanceList::const_iterator v = ilist.begin();
                    advance(v,k);
                    const Inheritance* ib = *v;
                    if (ib==this) {
                        return ib->make();
                    } else {
                        Model* model = ib->get_model(false);
                        // If okay... were done...
                        if (model) {
                            return model;
                        } else { 
                            return 0;
                        }
                    }
                }
            }
        }
    }

    //
    // Inheritance::get_parameters gets a list of a model's parameters...
    //
    void
    Inheritance::
    get_parameters(ModelParameterList& result,bool top) const 
    {
        if (top) result.clear();
        if (parent) {
            parent->get_parameters(result,false);
        }
        for (ModelParameterList::const_iterator p=parameters.begin();p!=parameters.end();++p) {
            result.push_back(*p);
        }
    }

    //
    // Inheritance::get_modelparameter searches the list of parameters associated with
    // the model, and returns the information about the parameter...
    //
    const  ModelParameterBase* 
    Inheritance::get_modelparameter(const std::string& param) const
    {
        ModelParameterList mpl = get_parameters();
        for (ModelParameterList::const_iterator p=parameters.begin();p!=parameters.end();++p) {
            if ((*p)->name == param) return (*p);
        }
        return NULL;
    }

    //
    // Inheritance::get_model_inheritance_list gets the list of all models inherited by 
    // a specific model...
    //
    void
    Inheritance::
    get_model_inheritance_list(InheritanceList& result,int option) const
    {
        if (option) {
            result.clear();
            result.push_back(this);
        }

        for (InheritanceList::const_iterator v=children.begin();v != children.end();++v) {
            result.push_back(*v);
            if (option!=2) (*v)->get_model_inheritance_list(result,0);
        }
    }

    //
    // Inheritance::get_named_model returns an instantiation of a class named model.
    //
    Model* 
    Inheritance::
    get_named_model(const std::string& model,bool nothrow) const
    {
        const Inheritance *inheritance = get_named_inheritance(model,nothrow);
        if (inheritance) {
            if (!inheritance->virt)    return inheritance->make();
        }
        if (!nothrow) throw SCATMECH_exception("Model " + model + " is not instantiable");
        return (Model*)0;
    }

    const Inheritance*
    Inheritance::
    get_named_inheritance(const std::string& model,bool nothrow) const
    {
        InheritanceList ilist;
        get_model_inheritance_list(ilist);

        for (InheritanceList::const_iterator v=ilist.begin();v!=ilist.end();++v) {
            // If that model's name matches, return its inheritance...
            if (model==(*v)->get_name()) return *v;
        }

        // It was never found if it got here...
        if (!nothrow) {
            ostringstream os;
            os << string("Cannot find a " + name + " named " + model) << endl;
            os << "Valid choices are" << endl;
            for (InheritanceList::const_iterator v = ilist.begin();v!=ilist.end();++v) {
                os << "   " << (*v)->get_name() << endl;
            }
            throw SCATMECH_exception(os.str());
        }  else {
            return (const Inheritance* )0;
        }
    }

    ModelParameterBase::
    ModelParameterBase(const std::string& _name,
                       const std::string& _description,
                       const std::string& _type,
                       const std::string& _defaultvalue,
                       Inheritance& inherit) 
                       : ParameterInfo(_name,_description,_type,_defaultvalue)
    {
        inherit.add_parameter(this);
    }

    void
    Model::
    set_parameter_base(const std::string& parameter,const std::string& value)
    {
        if (parameter=="recalc") {
            recalc = from_string<int>(value);
        } else if (parameter=="quiet") {
            quiet = from_string<int>(value);
        } else {
            ModelParameterList mplist = get_inheritance().get_parameters();

            string _parameter = parameter;
            string _subparameter="";
            int pos = _parameter.find('.');

            if (pos!=string::npos) {
                _parameter = parameter.substr(0,pos);
                _subparameter = parameter.substr(pos+1,parameter.size());
                if (_subparameter=="") 
                    error("Subparameter missing: " + parameter);
            }
        
            ModelParameterList::iterator q;
            for (q = mplist.begin();q != mplist.end();++q) {
                const ModelParameterBase *param = *q;
                string name = param->name;
            
                if (_parameter==name) {
                    if (value!="default") {
                        param->set_parameter(this,_subparameter,value);    
                    } else {
                        if (param->defaultvalue!="") {
                            param->set_parameter(this,_subparameter,param->defaultvalue);    
                        }
                    }
                    recalc=1;
                    return;
                }
            }
                    
            error("Unknown parameter " + parameter + " in set_parameter");
        }
    }

    string
    Model::
    get_parameter(const string& parameter) const
    {
        ostringstream result;

        if (parameter=="recalc") {
            result << recalc;
        } else if (parameter=="quiet") {
            result << quiet;
        } else {
            ModelParameterList mplist = get_inheritance().get_parameters();

            string _parameter = parameter;
            string _subparameter="";
            int pos = _parameter.find('.');

            if (pos!=string::npos) {
                _parameter = parameter.substr(0,pos);
                _subparameter = parameter.substr(pos+1,parameter.size());
                if (_subparameter=="") 
                    error("Subparameter missing: " + parameter);
            }
        
            ModelParameterList::iterator q;
            for (q = mplist.begin();q != mplist.end();++q) {
                const ModelParameterBase *param = *q;
                string name = param->name;
            
                if (_parameter==name) {
                    result << param->get_parameter(this,_subparameter);
                    return result.str();
                }
            }
                    
            error("Unknown parameter " + parameter + " in set_parameter");
        }
        return result.str();
    }

    ParameterInfo
    Model::
    get_parameter_info(const string& parameter) const
    {
        ParameterInfo result;
        if (parameter=="recalc") {
            result.name="recalc";
            result.type="int";
        } else if (parameter=="quiet") {
            result.name="quiet";
            result.type="int";
        } else {
            ModelParameterList mplist = get_inheritance().get_parameters();

            string _parameter = parameter;
            string _subparameter="";
            int pos = _parameter.find('.');

            if (pos!=string::npos) {
                _parameter = parameter.substr(0,pos);
                _subparameter = parameter.substr(pos+1,parameter.size());
                if (_subparameter=="")
                    error("Subparameter missing: " + parameter);
            }
        
            ModelParameterList::iterator q;
            for (q = mplist.begin();q != mplist.end();++q) {
                const ModelParameterBase *param = *q;
                string name = param->name;
            
                if (_parameter==name) {
                    if (_subparameter.size()==0) {
                        result = *param;
                    } else {
                        if (param->get_inheritance()!=NULL) {
                            Model* m =(Model*)(param->get_ptr(this)); 
                            result = m->get_parameter_info(_subparameter);
                        } else {
                            error("Parameter " + parameter + " does not have subparameters");
                        }
                    }
                    return result;
                }
            }
                    
            error("Unknown parameter " + parameter + " in set_parameter");
        }
        return result;
    }

    void
    Model::
    init()
    {
        ModelParameterList mplist = get_inheritance().get_parameters();

        ModelParameterList::iterator q;
        for (q = mplist.begin();q != mplist.end();++q) {
            const ModelParameterBase *param = *q;
            if (param->defaultvalue!="") {
                param->set_parameter(this,"",param->defaultvalue);    
            }
        }
        recalc=1;
    }

    void
    Model::
    AskUser() 
    {
        init();

        ModelParameterList mplist = get_inheritance().get_parameters();

        ModelParameterList::iterator q;
        for (q = mplist.begin();q != mplist.end();++q) {
            const ModelParameterBase* param = *q;
            param->AskUser(this);
        }
        recalc = 1;
    }

    void 
    Model::
    get_parameter_names(StringList& plist, const string& prefix) const
    {
        ModelParameterList mpl = get_inheritance().get_parameters();

        for (ModelParameterList::iterator q=mpl.begin();q!=mpl.end();++q) {
            plist.push_back(prefix+(*q)->name);
            if ((*q)->get_inheritance(this)!=NULL) {
                Model* m = (Model*)((*q)->get_ptr(this));
                m->get_parameter_names(plist,prefix+(*q)->name+'.');
            } 
        }
    }

    void
    Model::
    print_parameters(ostream& os) const
    {
        StringList vlist;
        get_parameter_names(vlist);
        StringList::iterator p;
        for (p=vlist.begin();p!=vlist.end();++p) {
            ParameterInfo info = get_parameter_info(*p);
            os << *p << " = " << get_parameter(*p) 
               << " (" << info.type << ": " 
               << info.description << ')' << endl;
        }
    }

    void 
    Model::
    error(const string& message) const 
    {
        string message2 = "Error in class " + get_inheritance().get_name() + ": " + message;
        throw SCATMECH_exception(message2);
    }

    Inheritance Model::inheritance("Model","Generalized Model",0,0);

} // namespace SCATMECH