// @(#)root/roostats:$Id$
// Author: Kyle Cranmer, Lorenzo Moneta, Gregory Schott, Wouter Verkerke
// Additional Contributions: Giovanni Petrucciani
/*************************************************************************
 * Copyright (C) 1995-2008, Rene Brun and Fons Rademakers.               *
 * All rights reserved.                                                  *
 *                                                                       *
 * For the licensing terms see $ROOTSYS/LICENSE.                         *
 * For the list of contributors see $ROOTSYS/README/CREDITS.             *
 *************************************************************************/
 
#ifndef ROOSTATS_ProfileLikelihoodTestStatEx
#define ROOSTATS_ProfileLikelihoodTestStatEx
 
 
#include "Rtypes.h"
 
#include "RooStats/TestStatistic.h"
 
 
#include "RooRealVar.h"
 
#include "RooNLLVar.h"
 
#include "Math/MinimizerOptions.h"
 
#include "RooStats/RooStatsUtils.h"

namespace RooStats {
    class ProfileLikelihoodTestStatEx : public TestStatistic {
        enum LimitType { twoSided, oneSided, oneSidedDiscovery };
    public:
        ProfileLikelihoodTestStatEx() {
            // Proof constructor. Do not use.
            fPdf = 0;
            fNll = 0;
            fCachedBestFitParams = 0;
            fLastData = 0;
            fLimitType = twoSided;
            fSigned = false;
            fDetailedOutputWithErrorsAndPulls = false;
            fDetailedOutputEnabled = false;
            fDetailedOutput = NULL;
            fLOffset = RooStats::IsNLLOffset();

            fVarName = "Profile Likelihood Ratio";
            fReuseNll = false;
            fMinimizer = ::ROOT::Math::MinimizerOptions::DefaultMinimizerType().c_str();
            fStrategy = ::ROOT::Math::MinimizerOptions::DefaultStrategy();
            fTolerance = TMath::Max(1., ::ROOT::Math::MinimizerOptions::DefaultTolerance());
            fPrintLevel = ::ROOT::Math::MinimizerOptions::DefaultPrintLevel();

        }
        ProfileLikelihoodTestStatEx(RooAbsPdf& pdf) {
            fPdf = &pdf;
            fNll = 0;
            fCachedBestFitParams = 0;
            fLastData = 0;
            fLimitType = twoSided;
            fSigned = false;
            fDetailedOutputWithErrorsAndPulls = false;
            fDetailedOutputEnabled = false;
            fDetailedOutput = NULL;
            fLOffset = RooStats::IsNLLOffset();

            fVarName = "Profile Likelihood Ratio Ex";
            fReuseNll = true;
            fMinimizer = ::ROOT::Math::MinimizerOptions::DefaultMinimizerType().c_str();
            fStrategy = ::ROOT::Math::MinimizerOptions::DefaultStrategy();
            // avoid default tolerance to be too small (1. is default in RooMinimizer)
            fTolerance = TMath::Max(1., ::ROOT::Math::MinimizerOptions::DefaultTolerance());
            fPrintLevel = ::ROOT::Math::MinimizerOptions::DefaultPrintLevel();
        }

        virtual ~ProfileLikelihoodTestStatEx() {
            if (fNll) delete fNll;
            if (fCachedBestFitParams) delete fCachedBestFitParams;
            if (fDetailedOutput) delete fDetailedOutput;
        }

        //LM use default copy constructor and assignment copying the pointers. Is this what we want ?

        void SetOneSided(Bool_t flag = true) { fLimitType = (flag ? oneSided : twoSided); }
        void SetOneSidedDiscovery(Bool_t flag = true) { fLimitType = (flag ? oneSidedDiscovery : twoSided); }
        void SetSigned(Bool_t flag = true) { fSigned = flag; }  // +/- t_mu instead of t_mu>0 with one-sided settings
        //void SetOneSidedDiscovery(Bool_t flag=true) {fOneSidedDiscovery = flag;}

        bool IsTwoSided() const { return fLimitType == twoSided; }
        bool IsOneSidedDiscovery() const { return fLimitType == oneSidedDiscovery; }

        static void SetAlwaysReuseNLL(Bool_t flag);

        void SetReuseNLL(Bool_t flag) { fReuseNll = flag; }
        void SetLOffset(Bool_t flag = kTRUE) { fLOffset = flag; }

        void SetMinimizer(const char* minimizer) { fMinimizer = minimizer; }
        void SetStrategy(Int_t strategy) { fStrategy = strategy; }
        void SetTolerance(double tol) { fTolerance = tol; }
        void SetPrintLevel(Int_t printlevel) { fPrintLevel = printlevel; }

        // Main interface to evaluate the test statistic on a dataset
        virtual Double_t Evaluate(RooAbsData& data, RooArgSet& paramsOfInterest) {
            return EvaluateProfileLikelihood(0, data, paramsOfInterest);
        }

        // evaluate  the profile likelihood ratio (type = 0) or the minimum of likelihood (type=1) or the conditional LL (type = 2)
        virtual Double_t EvaluateProfileLikelihood(int type, RooAbsData &data, RooArgSet & paramsOfInterest);

        virtual void EnableDetailedOutput(bool e = true, bool withErrorsAndPulls = false) {
            fDetailedOutputEnabled = e;
            fDetailedOutputWithErrorsAndPulls = withErrorsAndPulls;
            delete fDetailedOutput;
            fDetailedOutput = NULL;
        }
        virtual const RooArgSet* GetDetailedOutput(void) const {
            // Returns detailed output. The value returned by this function is updated after each call to Evaluate().
            // The returned RooArgSet contains the following:
            // <ul>
            // <li> the minimum nll, fitstatus and convergence quality for each fit </li>
            // <li> for each fit and for each non-constant parameter, the value, error and pull of the parameter are stored </li>
            // </ul>
            return fDetailedOutput;
        }

        // set the conditional observables which will be used when creating the NLL
        // so the pdf's will not be normalized on the conditional observables when computing the NLL
        virtual void SetConditionalObservables(const RooArgSet& set) { fConditionalObs.removeAll(); fConditionalObs.add(set); }

        // set the global observables which will be used when creating the NLL
        // so the constraint pdf's will be normalized correctly on the global observables when computing the NLL
        virtual void SetGlobalObservables(const RooArgSet& set) { fGlobalObs.removeAll(); fGlobalObs.add(set); }

        virtual void SetVarName(const char* name) { fVarName = name; }
        virtual const TString GetVarName() const { return fVarName; }

        virtual RooAbsPdf * GetPdf() const { return fPdf; }


        //      const bool PValueIsRightTail(void) { return false; } // overwrites default
    private:

        RooFitResult* GetMinNLL();
 
    private:

        RooAbsPdf* fPdf;
        RooAbsReal* fNll; //!
        const RooArgSet* fCachedBestFitParams;
        RooAbsData* fLastData;
        //      Double_t fLastMLE;
        LimitType fLimitType;
        Bool_t fSigned;

        // this will store a snapshot of the unconditional nuisance
        // parameter fit.
        bool fDetailedOutputEnabled;
        bool fDetailedOutputWithErrorsAndPulls;
        RooArgSet* fDetailedOutput; //!
        RooArgSet fConditionalObs;    // conditional observables
        RooArgSet fGlobalObs;    // global observables

        TString fVarName;

        Bool_t fReuseNll;
        TString fMinimizer;
        Int_t fStrategy;
        Double_t fTolerance;
        Int_t fPrintLevel;
        Bool_t fLOffset;
    protected:
        ClassDef(ProfileLikelihoodTestStatEx, 10)
    };
}

#endif