// @(#)root/tmva $Id$
// Author: Andreas Hoecker, Joerg Stelzer, Helge Voss

/**********************************************************************************
 * Project: TMVA - a Root-integrated toolkit for multivariate data analysis       *
 * Package: TMVA                                                                  *
 * Class  : Option                                                                *
 *                                             *
 *                                                                                *
 * Description:                                                                   *
 *      Option container                                                          *
 *                                                                                *
 * Authors (alphabetical):                                                        *
 *      Andreas Hoecker <Andreas.Hocker@cern.ch> - CERN, Switzerland              *
 *      Joerg Stelzer   <Joerg.Stelzer@cern.ch>  - CERN, Switzerland              *
 *      Helge Voss      <Helge.Voss@cern.ch>     - MPI-K Heidelberg, Germany      *
 *                                                                                *
 * Copyright (c) 2005:                                                            *
 *      CERN, Switzerland                                                         *
 *      U. of Victoria, Canada                                                    *
 *      MPI-K Heidelberg, Germany                                                 *
 *      LAPP, Annecy, France                                                      *
 *                                                                                *
 * Redistribution and use in source and binary forms, with or without             *
 * modification, are permitted according to the terms listed in LICENSE           *
 * (http://mva.sourceforge.net/license.txt)                                       *
 **********************************************************************************/

#ifndef ROOT_TMVA_Option
#define ROOT_TMVA_Option

//////////////////////////////////////////////////////////////////////////
//                                                                      //
// Option                                                               //
//                                                                      //
// Class for TMVA-option handling                                       //
//                                                                      //
//////////////////////////////////////////////////////////////////////////

#include <sstream>
#include <vector>
#include <string>

#include "TObject.h"
#include "TString.h"
#include "TMVA/MsgLogger.h"

namespace TMVA {

   class Configurable;

   class OptionBase : public TObject {

   public:

      friend class Configurable;

      OptionBase( const TString& name, const TString& desc );
      virtual ~OptionBase() {}

      const char* GetName() const override { return fNameAllLower.Data(); }
      virtual const char* TheName() const { return fName.Data(); }
      virtual TString     GetValue(Int_t i=-1) const = 0;

      Bool_t IsSet() const { return fIsSet; }
      virtual Bool_t IsArrayOpt() const = 0;
      const TString& Description() const { return fDescription; }
      virtual Bool_t IsPreDefinedVal(const TString&) const = 0;
      virtual Bool_t HasPreDefinedVal() const = 0;
      virtual Int_t  GetArraySize() const = 0;
      virtual Bool_t SetValue( const TString& vs, Int_t i=-1 );

      using TObject::Print;
      virtual void Print( std::ostream&, Int_t levelofdetail=0 ) const = 0;

   private:

      virtual void SetValueLocal(const TString& vs, Int_t i=-1) = 0;

      const TString fName;         // name of variable
      TString fNameAllLower;       // name of variable
      const TString fDescription;  // its description
      Bool_t        fIsSet;        // set by user ?

   protected:

      static MsgLogger& Log();
   protected:

       ClassDefOverride(OptionBase,1);
   };

   // ---------------------------------------------------------------------------

   template <class T>

      class Option : public OptionBase {

   public:

   Option( T& ref, const TString& name, const TString& desc ) :
      OptionBase(name, desc), fRefPtr(&ref) {}
      virtual ~Option() {}

      // getters
      TString  GetValue( Int_t i=-1 ) const override;
      virtual const T& Value   ( Int_t i=-1 ) const;
      Bool_t HasPreDefinedVal() const override { return (fPreDefs.size()!=0); }
      Bool_t IsPreDefinedVal( const TString& ) const override;
      Bool_t IsArrayOpt()   const override { return kFALSE; }
      Int_t  GetArraySize() const override { return 0; }

      // setters
      virtual void AddPreDefVal(const T&);
      using OptionBase::Print;
      void Print       ( std::ostream&, Int_t levelofdetail=0 ) const override;
      virtual void PrintPreDefs( std::ostream&, Int_t levelofdetail=0 ) const;

   protected:

      T& Value(Int_t=-1);

      void   SetValueLocal( const TString& val, Int_t i=-1 ) override;
      virtual Bool_t IsPreDefinedValLocal( const T& ) const;

      T* fRefPtr;
      std::vector<T> fPreDefs;  // templated vector
   };

   template<typename T>
      class Option<T*> : public Option<T> {

   public:

   Option( T*& ref, Int_t size, const TString& name, const TString& desc ) :
      Option<T>(*ref,name, desc), fVRefPtr(&ref), fSize(size) {}
      virtual ~Option() {}

      TString GetValue( Int_t i ) const override {
         std::stringstream str;
         str << std::scientific << Value(i);
         return str.str();
      }
      const T& Value( Int_t i ) const override { return (*fVRefPtr)[i]; }
      Bool_t IsArrayOpt()   const override { return kTRUE; }
      Int_t  GetArraySize() const override { return fSize; }

      using Option<T>::Print;
      void Print( std::ostream&, Int_t levelofdetail=0 ) const override;

      Bool_t SetValue( const TString& val, Int_t i=0 ) override;

      T& Value(Int_t i) { return (*fVRefPtr)[i]; }
      T ** fVRefPtr;
      Int_t fSize;

   };

} // namespace

namespace TMVA {

   //______________________________________________________________________
   template<class T>
      inline const T& TMVA::Option<T>::Value( Int_t ) const {
      return *fRefPtr;
   }

   template<class T>
      inline T& TMVA::Option<T>::Value( Int_t ) {
      return *fRefPtr;
   }

   template<class T>
      inline TString TMVA::Option<T>::GetValue( Int_t ) const {
      std::stringstream str;
      str << std::scientific << this->Value();
      return str.str();
   }

   template<>
      inline TString TMVA::Option<Bool_t>::GetValue( Int_t ) const {
      return Value() ? "True" : "False";
   }

   template<>
      inline TString TMVA::Option<Bool_t*>::GetValue( Int_t i ) const {
      return Value(i) ? "True" : "False";
   }

   template<class T>
      inline Bool_t TMVA::Option<T>::IsPreDefinedVal( const TString& val ) const
      {
         // template
         T tmpVal;
         std::stringstream str(val.Data());
         str >> tmpVal;
         return IsPreDefinedValLocal(tmpVal);
      }

   template<class T>
      inline Bool_t TMVA::Option<T>::IsPreDefinedValLocal(const T& val) const
      {
         // template
         if (fPreDefs.size()==0) return kTRUE; // if nothing pre-defined then allow everything

         typename std::vector<T>::const_iterator predefIt;
         predefIt = fPreDefs.begin();
         for (;predefIt!=fPreDefs.end(); predefIt++)
            if ( (*predefIt)==val ) return kTRUE;

         return kFALSE;
      }

   template<>
      inline Bool_t TMVA::Option<TString>::IsPreDefinedValLocal( const TString& val ) const
      {
         // template specialization for Bool_t
         TString tVal(val);
         tVal.ToLower();
         if (fPreDefs.size()==0) return kFALSE; // if nothing pre-defined then allow everything
         Bool_t foundPreDef = kFALSE;
         std::vector<TString>::const_iterator predefIt;
         predefIt = fPreDefs.begin();
         for (;predefIt!=fPreDefs.end(); predefIt++) {
            TString s(*predefIt);
            s.ToLower();
            if (s==tVal) { foundPreDef = kTRUE; break; }
         }
         return foundPreDef;
      }

   //______________________________________________________________________
   template<class T>
      inline void TMVA::Option<T>::AddPreDefVal( const T& val )
      {
         // template
         fPreDefs.push_back(val);
      }

   template<>
      inline void TMVA::Option<Bool_t>::AddPreDefVal( const Bool_t& )
      {
         // template specialization for Bool_t
         Log() << kFATAL << "<AddPreDefVal> predefined values for Option<Bool_t> don't make sense"
               << Endl;
      }

   template<>
      inline void TMVA::Option<Float_t>::AddPreDefVal( const Float_t& )
      {
         // template specialization for Float_t
         Log() << kFATAL << "<AddPreDefVal> predefined values for Option<Float_t> don't make sense"
               << Endl;
      }

   template<class T>
      inline void TMVA::Option<T>::Print( std::ostream& os, Int_t levelofdetail ) const
      {
         // template specialization for TString printing
         os << TheName() << ": " << "\"" << GetValue() << "\"" << " [" << Description() << "]";
         this->PrintPreDefs(os,levelofdetail);
      }

   template<class T>
      inline void TMVA::Option<T*>::Print( std::ostream& os, Int_t levelofdetail ) const
      {
         // template specialization for TString printing
         for (Int_t i=0; i<fSize; i++) {
            if (i==0)
               os << this->TheName() << "[" << i << "]: " << "\"" << this->GetValue(i) << "\"" << " [" << this->Description() << "]";
            else
               os << "    " << this->TheName() << "[" << i << "]: " << "\"" << this->GetValue(i) << "\"";
            if (i!=fSize-1) os << std::endl;
         }
         this->PrintPreDefs(os,levelofdetail);
      }

   //______________________________________________________________________
   template<class T>
      inline void TMVA::Option<T>::PrintPreDefs( std::ostream& os, Int_t levelofdetail ) const
      {
         // template specialization for TString printing
         if (HasPreDefinedVal() && levelofdetail>0) {
            os << std::endl << "PreDefined - possible values are:" << std::endl;
            typename std::vector<T>::const_iterator predefIt;
            predefIt = fPreDefs.begin();
            for (;predefIt!=fPreDefs.end(); predefIt++) {
               os << "                       ";
               os << "  - " << (*predefIt) << std::endl;
            }
         }
      }

   //______________________________________________________________________
   template<class T>
      inline Bool_t TMVA::Option<T*>::SetValue( const TString& val, Int_t ind )
      {
         // template
         if (ind >= fSize) return kFALSE;
         std::stringstream str(val.Data());
         if (ind < 0) {
            str >> Value(0);
            for (Int_t i=1; i<fSize; i++) Value(i) = Value(0);
         }
         else {
            str >> Value(ind);
         }
         return kTRUE;
      }

   template<class T>
      inline void TMVA::Option<T>::SetValueLocal( const TString& val, Int_t i )
      {
         // template
         std::stringstream str(val.Data());
         str >> Value(i);
      }

   template<>
      inline void TMVA::Option<TString>::SetValueLocal( const TString& val, Int_t )
      {
         // set TString value
         TString valToSet(val);
         if (fPreDefs.size()!=0) {
            TString tVal(val);
            tVal.ToLower();
            std::vector<TString>::const_iterator predefIt;
            predefIt = fPreDefs.begin();
            for (;predefIt!=fPreDefs.end(); predefIt++) {
               TString s(*predefIt);
               s.ToLower();
               if (s==tVal) { valToSet = *predefIt; break; }
            }
         }

         std::stringstream str(valToSet.Data());
         str >> Value(-1);
      }

   template<>
      inline void TMVA::Option<Bool_t>::SetValueLocal( const TString& val, Int_t )
      {
         // set Bool_t value
         TString valToSet(val);
         valToSet.ToLower();
         if (valToSet=="1" || valToSet=="true" || valToSet=="ktrue" || valToSet=="t") {
            this->Value() = true;
         }
         else if (valToSet=="0" || valToSet=="false" || valToSet=="kfalse" || valToSet=="f") {
            this->Value() = false;
         }
         else {
            Log() << kFATAL << "<SetValueLocal> value \'" << val
                  << "\' can not be interpreted as boolean" << Endl;
         }
      }
}
#endif
