// Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin
// SPDX-License-Identifier: BSD-3-Clause
//
// --------------------------------------------------------------------------
// $Maintainer: Hendrik Weisser $
// $Authors: Hendrik Weisser $
// --------------------------------------------------------------------------

#pragma once

#include <OpenMS/DATASTRUCTURES/DefaultParamHandler.h>

// Remove libSVM header include
// #include <svm.h>

#include <map>
#include <vector>
#include <utility> // for "pair"
#include <tuple>
#include <memory> // for std::unique_ptr

namespace OpenMS
{
  /**
     @brief Simple interface to support vector machines for classification and regression (via LIBSVM).

     This class supports:
     - (multi-class) classification and regression with an epsilon-SVR.
     - linear or RBF kernel.
     
     It uses cross-validation to optimize the respective SVM/SVR parameters @e C, @e p (SVR-only) and (RBF kernel only) @e gamma.

     Usage:
     SVM models are generated by the the setup() method.
     SimpleSVM provides two common use cases for convinience:
     - all data: Passing all data (training + test) as @p predictors to setup and training on a subset.
     - training only: Passing exclusivly training data as @p predictors to setup.
     The parameter @p outcomes of setup() defines in both cases the training set; it contains the indexes of observations 
     (corresponding to positions in the vectors in @p predictors) together with the class labels (or regression values) for training.
     
     Given @e N observations of @e M predictors, the data are coded as a map of predictors (size @e M), each a numeric vector of values for different observations (size @e N).

     To predict class labels (or regression values) based on a model, use one of the predict() methods:
     - if setup with all data: the parameter @p indexes of predict() takes a vector of indexes corresponding to the observations for which predictions should be made. 
       (With an empty vector, the default, predictions are made for all observations, including those used for training.)
     - if setup with training data only: by passing a new PredictorMap.     

     
     @htmlinclude OpenMS_SimpleSVM.parameters
  */
  class OPENMS_DLLAPI SimpleSVM :
    public DefaultParamHandler
  {

  public:
    /// Mapping from predictor name to vector of predictor values
    typedef std::map<String, std::vector<double> > PredictorMap;

    /// Mapping from predictor name to predictor min and max
    typedef std::map<String, std::pair<double, double> > ScaleMap;

    /// SVM/SVR prediction result
    struct Prediction
    {
      /// Predicted class label (or regression value)
      double outcome;

      /// Class label (or regression value) and their predicted probabilities
      std::map<double, double> probabilities;
    };

    /// Default constructor
    SimpleSVM();

    /// Destructor
    ~SimpleSVM() override;

    // Prevent copy and assignment
    SimpleSVM(const SimpleSVM&) = delete;
    SimpleSVM& operator=(const SimpleSVM&) = delete;

    /**
       @brief Load data and train a model.

       @param predictors Mapping from predictor name to vector of predictor values (for different observations). All vectors should have the same length; values will be changed by scaling.
       @param outcomes Mapping from observation index to class label or regression value in the training set.
       @param classification true (default) if SVM classification should be used, SVR otherwise

       @throw Exception::IllegalArgument if @p predictors is empty
       @throw Exception::InvalidValue if an invalid index is used in @p outcomes
       @throw Exception::MissingInformation if there are fewer than two class labels in @p outcomes, or if there are not enough observations for cross-validation
    */
    void setup(PredictorMap& predictors, const std::map<Size, double>& outcomes, bool classification = true);

    /**
       @brief Predict class labels or regression values (and probabilities).

       @param predictions Output vector of prediction results (same order as @p indexes).
       @param indexes Vector of observation indexes for which predictions are desired. If empty (default), predictions are made for all observations.

       @throw Exception::Precondition if no model has been trained
       @throw Exception::InvalidValue if an invalid index is used in @p indexes
    */
    void predict(std::vector<Prediction>& predictions,
                 std::vector<Size> indexes = std::vector<Size>()) const;

    /**
       @brief Predict class labels or regression values (and probabilities).

       @param predictors Mapping from predictor name to vector of predictor values (for different observations). All vectors should have the same length; values will be changed by scaling applied to training data in setup.
       @param predictions Output vector of prediction results (same order as @p indexes).
       
       @throw Exception::Precondition if no model has been trained
       @throw Exception::InvalidValue if an invalid index is used in @p indexes
    **/
    void predict(PredictorMap& predictors, std::vector<Prediction>& predictions) const;

    /**
       @brief Get the weights used for features (predictors) in the SVM model

       Currently only supported for two-class classification.
       If a linear kernel is used, the weights are informative for ranking features.

       @throw Exception::Precondition if no model has been trained, or if the classification involves more than two classes
    */
    void getFeatureWeights(std::map<String, double>& feature_weights) const;

    /// Write cross-validation (parameter optimization) results to a CSV file
    void writeXvalResults(const String& path) const;

    /// Get data range of predictors before scaling to [0, 1]
    const ScaleMap& getScaling() const;

  protected:
    // Forward declaration of implementation class
    class Impl;
    
    // Pointer to implementation (Pimpl pattern)
    std::unique_ptr<Impl> pimpl_;
  };
}
