//
//  XSPEC12  November 2003
//
//

// location for functions that are in namespace XSGlobal but not part
// of the Global class.

#include <XSUser/Global/Global.h>
#include <XSUser/Help/HelpComposite.h>
#include <XSUser/UserInterface/TclRedAlert.h>
#include <XSPlot/Plot/Plot.h>
#include <XSPlot/Plot/Graph.h>
#include <XSPlot/Plot/PlotCommand.h>
#include <XSPlot/Plot/PlotCommandCreator.h>
#include <XSFit/Fit/Fit.h>
#include <XSFit/Fit/FitMethod.h>
#include <XSFit/FitMethod/LevMarq/LevMarq.h>
#include <XSFit/MCMC/ChainManager.h>
#include <XSFit/Randomizer/Randomizer.h>
#include <XSFit/Randomizer/RandomizerPolicies.h>
#include <XSFit/StatMethod/ChiSquare/ChiSquare.h>
#include <XSFit/StatMethod/Cstat/Cstat.h>
#include <XSFit/StatMethod/EDF/EDF.h>
#include <XSFit/StatMethod/EDF/EDFVariants.h>
#include <XSFit/StatMethod/Lstat/Lstat.h>
#include <XSModel/GlobalContainer/ModelContainer.h>
#include <XSModel/GlobalContainer/ResponseContainer.h>
#include <XSModel/GlobalContainer/DataContainer.h>
#include <XSModel/GlobalContainer/Weight.h>
#include <XSModel/Model/Model.h>
#include <XSModel/Model/Component/Component.h>
#include <XSModel/Model/Component/TableComponent.h>
#include <XSModel/Model/Component/OGIPTable/OGIPTable.h>
#include <XSModel/Model/EmissionLines/LineList.h>
#include <XSModel/Model/EmissionLines/Apec.h>
#include <XSModel/Model/EmissionLines/Bearden.h>
#include <XSModel/Model/EmissionLines/Mekal.h>
#include <XSModel/Parameter/Parameter.h>
#include <XSModel/Parameter/ParamLinkList.h>
#include <XSModel/Data/DataSet.h>
#include <XSModel/Data/SpectralData.h>
#include <XSModel/Data/BackCorr/Background.h>
#include <XSModel/Data/Detector/DummyResponse.h>
#include <XSModel/DataFactory/XspecRegistry.h>
#include <XSUtil/Error/Error.h>
#include <XSUtil/Parse/MathExpression.h>
#include <XSUtil/Parse/XSparse.h>
#include <XSUtil/Signals/SignalHandler.h>
#include <XSUtil/Utils/XSutility.h>
#include <XSUtil/FunctionUtils/FunctionUtility.h>
#include <XSFunctions/functionMap.h>
#include <Handler/HandlerUtils.h>
#include <XSGlobal.h>
#include <XSContainer.h>
#include <XSstreams.h>
#include <iostream>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>    
#include <cassert>
#include <memory>
#include <cstring>

extern "C" {
#ifdef GUI_SUPPORT
  #include <pow.h>
  #include <itk.h>
#endif
}

namespace xstcl
{
   // container definitions for the command set and their
   // one-line help summaries
   char xsLogOut[] = "LogOut";
   char xsLogErr[] = "ErrOut";
   int  xs_echo_script = 0;
   char xsTclPrompt[] = "xs_tcl_prompt";
   char* xs_tcl_prompt = 0;
   Tcl_Trace XS_Echo_Trace = 0;
}

namespace XSGlobal
{
    GlobalData* globalData = 0;
    std::map <string,Tcl_ObjCmdProc*> commandMap;
    std::map <string,string> summaryMap;
    std::map <string,string> pdfMap;        //good place for this?
}

bool
XSGlobal::printDocs(const char* command, const char* firstArgument)
{  
        bool done = false;
        if (!firstArgument || (strlen(firstArgument) == 1 && *firstArgument == '?') )
        {
                // print out summary information on blank or '?'
                tcout << summaryMap[command] << std::endl;
                done = true;
        }        
        return done;   
}

void 
XSGlobal::registerNativeFitMethods()
{
        FitMethod::registerMethod("leven",new LevMarq("10 0.01"));        
//        FitMethod::registerMethod("anneal",new Anneal);
        summaryMap["method"] =  refreshFitMethodNames();
}

void XSGlobal::initializeGUILibraries(Tcl_Interp* xsInterp)
{
        int status(TCL_OK);
// initialize Tk. If the initialization succeeds, defind the Tk package to Tcl. 
// If it fails, generally speaking this is because the user does not have access
// to and X display, either because of permissions, environment (no DISPLAY variable)
// or because they are not running under X. In this case, simply continue silently
// and do not initialize Tk. 

#ifdef GUI_SUPPORT
        if ( (status = Tk_Init(xsInterp)) == TCL_OK) 
        {
                char tk[] = "Tk";
                Tcl_StaticPackage(xsInterp, tk, Tk_Init, Tk_SafeInit); 
        } 

        // initialize Itcl/Itk

        //
        //  This is itkwish, so import all [incr Tcl] commands by
        //  default into the global namespace.  Fix up the autoloader
        //  to do the same. Use TclInitErr exception to make the flow look simple.
        //  In principle we could load Itcl for the non GUI case, but this is not
        //  too necessary, so we load both or neither.

        if (status == TCL_OK) 
        {
		if ( (status = Itk_Init(xsInterp)) == TCL_OK)
		{
               	        char CItk[] = "Itk";
               	        char itkn[] = "::itk::*";
               	        Tcl_StaticPackage(xsInterp, CItk, Itk_Init, (Tcl_PackageInitProc *) NULL);
                        if ((status = 
                                Tcl_Import(xsInterp, Tcl_GetGlobalNamespace(xsInterp),itkn, 1)) == TCL_OK)
                        {
               	                char mkindex[] = 
               	                       "auto_mkindex_parser::slavehook { _%@namespace import -force ::itcl::* ::itk::* }";
               	                if ( (status = Tcl_Eval(xsInterp,mkindex))  == TCL_ERROR)
                                {
                   	                throw TclInitErr("Itk Error: constructing script index itcl/itk");

                                }
                        }
                        else
                        {
                                throw TclInitErr("Itk Error: getting itk namespace");
                        }
                }
                else
                {
 			throw TclInitErr("Itcl Error: cannot initialize Itk");
		}
        }
        else
        {
                throw TclInitErr("Itcl Error: getting itcl namespace");
        }

        if ( (status = Pow_Init(xsInterp)) == TCL_ERROR)
        {
                std::cerr << "Cannot initialize graphical interface.\n";
                std::cerr << "Switching to command line mode." << std::endl;
                globalData->gui(false); 

        }
#endif
    // NB this necessitates "true" as the default
    // setting since the .xspecrc file hasn't been read yet.

}

void 
XSGlobal::registerNativeStatMethods()
{
        using namespace XSContainer;
        Weight* standard = new StandardWeight;
        Weight* churazov = new ChurazovWeight;
        Weight* gehrels = new GehrelsWeight;
        Weight* modWeight = new ModelWeight;
        StatMethod::registerWeightingMethod(standard->name(), standard);     
        StatMethod::registerWeightingMethod(churazov->name(), churazov);     
        StatMethod::registerWeightingMethod(gehrels->name(), gehrels);     
        StatMethod::registerWeightingMethod(modWeight->name(), modWeight);
        StatMethod::registerMethod("chi",new ChiSquare);
        StatMethod::registerMethod("cstat",new Cstat);  
	StatMethod::registerMethod("lstat",new Lstat); 
        //StatMethod::registerMethod("k-s",new EDF<K_S>);
        //StatMethod::registerMethod("c-vm",new EDF<C_vM>);
        //StatMethod::registerMethod("a-d",new EDF<A_D>);
        string doc("set statistic to optimize \n    Syntax:statistic [ ");
        doc += StatMethod::statNames();
        doc += " ]";
        summaryMap["statistic"] = doc;
        doc =  "set data weighting\n    Syntax:weight [ "   ; 
        doc += StatMethod::weightNames();
        doc += " ]";
        summaryMap["weight"] = doc;
}

void 
XSGlobal::registerNativePlotCommands(Plot* plot)
{
        std::pair<string,string> listOfPlotCommands(PlotCommandCreator::registerPlotCommands(plot));
        string& primary = listOfPlotCommands.first;
        string& secondary = listOfPlotCommands.second;        
        string doc ("plot data/models/fits etc\n    Syntax: primary plot commands:\n");
        size_t fieldWidth(15);
        size_t npcommands(primary.size() / fieldWidth);
        size_t  cnpl ( npcommands/5);
        for (size_t j = 0; j < cnpl; ++j)
        {
                doc +=  "\t";
                doc +=  primary.substr( j*5*fieldWidth, 5*fieldWidth );
                doc +=  "\n";     
        }
        doc +=  "\t";
        doc += primary.substr(cnpl*5*fieldWidth);
        doc +=  "\n";
        doc += "            secondary options ";
        doc += "(2-panel data plots - use with (l)data, (l)counts, icounts):\n";

        size_t nscommands(secondary.size() / fieldWidth);
        size_t  cnsl ( nscommands/5);
        for (size_t j = 0; j < cnsl; ++j)
        {
                doc +=  "\t";
                doc +=  secondary.substr( j*5*fieldWidth, 5*fieldWidth );
                doc +=  "\n";     
        }
        doc +=  "\t";
        doc += secondary.substr(cnsl*5*fieldWidth);
        summaryMap["plot"] = doc;

}

void
XSGlobal::registerNativeTables()
{
    TableComponent::registerTableFormat(new OGIPTable(""));
}

void
XSGlobal::registerNativeLineLists()
{
   LineList::registerLineList("apec", new Apec);
   LineList::registerLineList("bearden", new Bearden);
   LineList::registerLineList("mekal", new Mekal);
}

void 
XSGlobal::startUp()
{


        using namespace XSContainer;

        string title, buildDate;
	XSutility::XSVersionString(title, buildDate);
        tcout << '\n' << "\t\t" << title << std::endl; 
	tcout << '\t' << buildDate << '\n' << std::endl;            

        DataSet::xspecVersion(title);


        // now we can process the other initial settings. 

        globalData->processSettings();


	if (!globalData->gui())
	{
	   plot = Plot::Instance(globalData->defaultGraph(),globalData->managerDir());
           string waveUnits(globalData->settings("WAVE_PLOT_UNITS"));
           if (waveUnits.length())
           {
              const string angstroms("angstroms");
              if (angstroms.find(XSutility::lowerCase(waveUnits)) == 0)
                 plot->waveUnitsHz(0);
           }    
	}
	else
	{
	   // This will throw a RedAlert for the time being.
	        plot = Plot::Instance(" ",globalData->managerDir());
	}

        string plotDevice (globalData->settings("PLOTDEVICE"));
	try
	{
        	if ( plotDevice.length())
        	{
                	plot->graph()->setDevice(plotDevice);
        	}  
	}
	catch ( ... )
	{
		// do nothing, the exception will have  printed a
		// message
	}

        //register plot commands. Must happen after the plot control object 
        // is instantiated

        registerNativePlotCommands(plot);


}

void
XSGlobal::cleanUp()
{
   LineList::clearLineLists();
   TableComponent::clearTableFormats();
   FitMethod::clearMethods();
   StatMethod::clearWeightingMethods();
   StatMethod::clearMethods();
   delete Help::helpTree();
   delete DummyResponse::Instance();
   delete XspecRegistry::Instance();

   // in case the order is important this should be right.
   delete SignalHandler::instance();
   delete XSContainer::plot;
   delete XSContainer::fit;
   delete XSContainer::models;
   delete ParamLinkList::Instance();
   delete XSContainer::datasets;
   delete XSContainer::responses;

   // Deliberately placing this after the destruction of models.  
   // Not currently necessary (5/08), but model components do carry
   // generator pointers to model functions, and it's best that these
   // remain valid throughout the model destruction process.
   clearFunctionMap();
   MathExpression::clearOperatorsMap();

   system(string("rm -f " + globalData->undoFile()).c_str());

   std::ostringstream cmd;

   string autosaveFile = globalData->autoSaveFile();

   struct stat info;

   stat(autosaveFile.c_str(), &info);

   if(info.st_size)
       cmd << "mv -f " << autosaveFile << ' ' 
	   << autosaveFile.substr(0, autosaveFile.rfind("xautosav_") + 8) << ".xcm";
   else
       cmd << "rm -f " << autosaveFile;

   system((cmd.str()).c_str());

   delete globalData;
}

void 
XSGlobal::createCommandMap()
{

        const string& managerDir = XSGlobal::globalData->managerDir();
	string comfilename = managerDir + "/CommandSummary.txt";
	ifstream comfile;
	comfile.open(comfilename.c_str());
	if (!comfile) {
	  string message = "Cannot open " + comfilename;
	  throw RedAlert(message);
	}

	string line("");
	size_t nlines;
	string comName;

	while (!comfile.eof()) {

	  getline(comfile,line);
	  istringstream s(line);
	  s >> comName >> nlines;
	  s.clear();

	  string doc("");
	  for(size_t i=0; i<nlines; i++) {
	    getline(comfile,line);
	    doc += line;
	    if (i < nlines-1) doc += "\n";
	  }

	  summaryMap[comName] = doc;

	  getline(comfile,line);

	}


        commandMap["?"] = &xsComsum;
        commandMap["abund"] = &xsAbund;
        commandMap["addcomp"] = &xsAddcomp;
        commandMap["arf"] = &xsArf;
        commandMap["autosave"] = &xsAutosave;
        commandMap["backgrnd"] = &xsBackgrnd;
        commandMap["bayes"] = &xsBayes;
        commandMap["chain"] = &xsChain;
        commandMap["comsum"] = &xsComsum; // broken 11/29/01 - typing in comsum gives
                                          // nonsense.
        commandMap["corfile"] = &xsCorfile;
        commandMap["cornorm"] = &xsCornorm;
        commandMap["cosmo"] = &xsCosmo;
        commandMap["cpd"] = &xsCpd;
        commandMap["chatter"] = &xsChatter;
        commandMap["data"] = &xsData;
        commandMap["delcomp"] = &xsDelcomp;
        commandMap["diagrsp"] = &xsDiagrsp;
        commandMap["dummyrsp"] = &xsDummyrsp;
        commandMap["editmod"] = &xsEditmod;
        commandMap["energies"] = &xsEnergies;
        commandMap["eqwidth"] = &xsEqwidth;
        commandMap["error"] = &xsError;
        commandMap["uncertain"] = &xsError; 
        commandMap["exit"] = &xsExit;
        commandMap["fakeit"] = &xsFakeit;
        commandMap["fit"] = &xsFit;
        commandMap["flux"] = &xsFlux;
        commandMap["lumin"] = &xsFlux;
        commandMap["freeze"] = &xsFreeze;
        commandMap["ftest"] = &xsFtest;
        commandMap["gain"] = &xsGain;
        commandMap["goodness"] = &xsGoodness;
        commandMap["help"] = &xsHelp;
        commandMap["identify"] = &xsIdentify;        
        commandMap["ignore"] = &xsIgnore;
        commandMap["improve"] = &xsImprove;
        commandMap["initpackage"] = &xsInitpackage;
	commandMap["iplot"] = &xsIplot;
        commandMap["log"] = &xsLog;
        commandMap["lmod"] = &xsLmod;
        commandMap["margin"] = &xsMargin;
        commandMap["mdefine"] = &xsMdefine;
        commandMap["method"] = &xsMethod;
        commandMap["model"] = &xsModel;
        commandMap["newpar"] = &xsNewpar;
        commandMap["notice"] = &xsNotice;
        commandMap["plot"] = &xsPlot;
        commandMap["query"] = &xsQuery;
        commandMap["quit"] = &xsExit;
        commandMap["renorm"] = &xsRenorm;
        commandMap["response"] = &xsResponse;
        commandMap["script"] = &xsScript;
        commandMap["setplot"] = &xsSetplot;
        commandMap["save"] = &xsSave;        
        commandMap["show"] = &xsShow;
        commandMap["statistic"] = &xsStatistic;
        commandMap["steppar"] = &xsSteppar;
        commandMap["systematic"] = &xsSystematic;
        commandMap["thaw"] = &xsThaw;
        commandMap["tclout"] = &xsTclout;
        commandMap["time"] = &xsTime;
	commandMap["undo"] = &xsUndo;
        commandMap["untie"] = &xsUntie;
        commandMap["version"] = &xsVersion;
        commandMap["weight"] = &xsWeight;
        commandMap["xsect"] = &xsXsect;
        commandMap["xset"] = &xsXset;

        if ( !XSGlobal::globalData->gui()) 
        {       
                // internal, not documented.
                commandMap["unknown"] = &xsUnknown; 
        }
        else 
        {
                // internal, not documented: used by GUI
                // which redefines unknown by itself.
                // XSunknown is the default handler for
                // unknown commands caught this way.
                commandMap["XSunknown"] = &xsUnknown; 
        }


        commandMap["genetic"] = &xsGenetic;
        commandMap["recornrm"] = &xsRecornrm;       
        commandMap["thleqw"] = &xsThleqw;

}        

void XSGlobal::registerFunctionUtility ()
{

        FunctionUtility::managerPath(globalData->managerDir());        
        const string HEADAS(getenv("HEADAS"));
        string dataPath (HEADAS + "/../spectral/modelData/");      
        FunctionUtility::modelDataPath(dataPath);
        string crossSections;
        string abundances;
        FunctionUtility::readInitializers(crossSections,abundances);
        string xSectDoc("Change/Report the photoionization cross sections\n");
        xSectDoc += "    Syntax: xsect " + crossSections;
        summaryMap["xsect"] = xSectDoc;
        string abundDoc("Change/Report the solar abundance table in use\n");
        abundDoc += "    Syntax: abund " + abundances +"\n";
        abundDoc += "            abund <name> <file> \n"; 
        abundDoc += "     (define & load new vector <name> from data in <file>)";
        summaryMap["abund"] = abundDoc;

}

string XSGlobal::refreshFitMethodNames()
{                       
        string fitMethodDoc("set fitting algorithm\n");     
        fitMethodDoc += "    Syntax:method [ ";
        fitMethodDoc += FitMethod::fitNames();
        fitMethodDoc += "]  <initial data for method>" ; 
        return fitMethodDoc;               
}

void XSGlobal::commonPlotHandler(const StringArray& args, bool isInteractive)
{
   using XSContainer::plot;
   StringArray plotCmd;

   //lastCommand defaults to "data" in Plot constructor
   plotCmd = (args[0] == "" ? plot->lastCommand() : args);

   const size_t N(plotCmd.size());
   string value(""), arg("");  

   if (N > 0)
   {
      arg = XSutility::lowerCase(plotCmd[0]);
      size_t commaInArg = arg.find_first_of(',');
      if ( commaInArg != XSparse::NOTFOUND()) 
      {
              // the "+1" parses off the first comma which is just a separator
              // between the command and the parameters.
              value = arg.substr(commaInArg+1);
              arg = arg.substr(0,commaInArg);
      }
      else value = Model::DEFAULT();
   }

   if ( N > 1)
   {
        Real dummy;
	size_t dummy2;
        bool isModel = (string("model").find(arg) == 0);

	string tmp, sub = plotCmd[1].substr(0, plotCmd[1].find(','));

        if ( !isModel && !XSutility::isReal(sub, dummy) && 
	     !XSparse::stringIntPair(sub, tmp, dummy2) && plotCmd[1][0] != ',')
        {
	    arg += " ";
	    arg += XSutility::lowerCase(sub);
        }
        // send this and all subsequent arguments to the value string.
        //  "value" string will be ignored by commands that don't use it
        // (all the data commands).
        // commands like "plot model <model name>" will use this.
        // commands like plot contour that are followed by real arguments must
        // deal with by the command.
        value = plotCmd[1];
        for (size_t j = 2; j < N; ++j)
        {
                value += " ";
                value += plotCmd[j];
        }              
   }

   PlotCommand* pC = PlotCommandCreator::commands(XSutility::lowerCase(arg));
   if (!pC)
   {
      throw YellowAlert("Plot subcommand does not exist or has not yet been implemented.\n");
   }
   else
   {
       //save last command
       plot->lastCommand(plotCmd);
      try
      {
	 plot->graph()->setInteractive(isInteractive);
//         std::cerr << value << '\n';
	 pC->execute(value);
      }
      catch (YellowAlert &)
      {
        // If exception propagates after PlotGroups are created,
        // but before command has a chance to call plot->makeTheGraph
        // (which deletes them), then needs to be cleaned up here.
         plot->resetPlotGroups();
	 throw;
      }
   }
}

void XSGlobal::saveAll(const string& outFileName)
{
    try
    {
	std::ofstream outFile(outFileName.c_str());
	// note that xspec11 also prints out gain
	// information - since the gain command is
	// not yet implemented in xspec12 this 
	// is not done here but needs to be addressed.
	// BD 11/16/03
	XSContainer::datasets->saveData(outFile);
	saveModel(outFile);
    }
    catch ( std::exception& )
    {
	tcerr << " File cannot be opened for writing\n"; 
	// disable autosave to forbid further messages.  
	globalData->autoSaveFrequency(XSparse::NOTFOUND());
    }
}

void XSGlobal::saveModel(std::ofstream& outStream)
{
    using namespace std;
    FitMethod* fitMethod  (XSContainer::fit->fitMethod());
    StatMethod* statMethod (XSContainer::fit->statMethod());

    outStream << "statistic "  << statMethod->name() << endl;
    outStream << "method "  << fitMethod->selectedSubMethod() << ' ' 
	    << fitMethod->settingString()   << endl;
    if(FunctionUtility::abundChanged())
        outStream << "cd " << FunctionUtility::abundPath() << "\nabund file " 
	          << FunctionUtility::abundanceFile() << endl;
    else
        outStream << "abund " << FunctionUtility::ABUND() << endl;
    outStream << "xsect " << FunctionUtility::XSECT() << endl;
    outStream << "cosmo " << FunctionUtility::getH0() << " "
        << FunctionUtility::getq0() << " " << FunctionUtility::getlambda0()
        << endl;
    outStream << "xset delta " 
        << XSContainer::models->proportionalDelta() << endl;
    const map<string,string>& xsetStrings = 
            FunctionUtility::modelStringDataBase();
    map<string,string>::const_iterator itString = xsetStrings.begin();
    map<string,string>::const_iterator itStringEnd = xsetStrings.end();
    while (itString != itStringEnd)
    {
       outStream << "xset " << itString->first << "  " 
            << itString->second << endl;
       ++itString;
    }

    XSContainer::models->saveData(outStream);
}

void XSGlobal::registerNativeRandomizingStrategies(Fit* fit)
{
   std::auto_ptr<RandomizerBase> rand(new Randomizer<Fit,RandomizerGaussDist>());
   // If the following call doesn't throw, relinquish ownership of 
   // rand to the fit container.
   fit->registerRandomizingStrategy(rand->name(), rand.get());
   RandomizerBase* tmp = rand.release();
   fit->chainManager()->setChainProposal(tmp->name());
   rand.reset(new Randomizer<ChainManager,RandomizerGaussDist>());
   fit->registerRandomizingStrategy(rand->name(), rand.get());
   rand.release();
   rand.reset(new Randomizer<RandomizerCmdLine,RandomizerGaussDist>());
   fit->registerRandomizingStrategy(rand->name(), rand.get());
   rand.release();
   rand.reset(new Randomizer<RandomizerCovarFile,RandomizerGaussDist>());
   fit->registerRandomizingStrategy(rand->name(), rand.get());
   rand.release();

   rand.reset(new Randomizer<Fit,RandomizerCauchyDist>());
   fit->registerRandomizingStrategy(rand->name(), rand.get());
   rand.release();
   rand.reset(new Randomizer<ChainManager,RandomizerCauchyDist>());
   fit->registerRandomizingStrategy(rand->name(), rand.get());
   rand.release();
   rand.reset(new Randomizer<RandomizerCmdLine,RandomizerCauchyDist>());
   fit->registerRandomizingStrategy(rand->name(), rand.get());
   rand.release();
   rand.reset(new Randomizer<RandomizerCovarFile,RandomizerCauchyDist>());
   fit->registerRandomizingStrategy(rand->name(), rand.get());
   rand.release();


   // We want to distinguish these native classes from any the
   // user may add in at a later time.  So fill in Fit's
   // nativeRandomizerNames container with these names.
   // (This can only be done once.)
   fit->initNativeRandomizerNames();
}