// Copyright © 2016-2025 Thomas Nagler and Thibault Vatter
//
// This file is part of the vinecopulib library and licensed under the terms of
// the MIT license. For a copy, see the LICENSE file in the root directory of
// vinecopulib or https://vinecopulib.github.io/vinecopulib/.

#pragma once

#include <vinecopulib/bicop/fit_controls.hpp>
#include <vinecopulib/misc/nlohmann_json.hpp>

namespace vinecopulib {

// forward declaration of Abstract class
class AbstractBicop;
using BicopPtr = std::shared_ptr<AbstractBicop>;

//! @brief A class for bivariate copula models.
//!
//! @details The model is fully characterized by the family, 
//! rotation (one of `0`, `90`, `180`, `270`), a matrix of parameters, and 
//! variable types (two strings, one for each variable, either `"c"` 
//! for continuous or `"d"` for discrete).
//! 
//! Implemented families (see `BicopFamily`):
//!
//! ```
//! | type          | full name             | string identifier     |
//! |---------------|-----------------------|-----------------------|
//! | -             | Independence          | "indep"               |
//! | Elliptical    | Gaussian              | "gaussian"            |
//! |               | Student t             | "student"             |
//! | Archimedean   | Clayton               | "clayton"             |
//! |               | Gumbel                | "gumbel"              |
//! |               | Frank                 | "frank"               |
//! |               | Joe                   | "joe"                 |
//! |               | Clayton-Gumbel (BB1)  | "bb1"                 |
//! |               | Joe-Gumbel (BB6)      | "bb6"                 |
//! |               | Joe-Clayton (BB7)     | "bb7"                 |
//! |               | Joe-Frank (BB8)       | "bb8"                 |
//! | Extreme-Value | Tawn                  | "tawn"                |
//! | Nonparametric | Transformation kernel | "tll"                 |
//! ```
//!
class Bicop
{

public:
  // Constructors
  Bicop(const BicopFamily family = BicopFamily::indep,
        const int rotation = 0,
        const Eigen::MatrixXd& parameters = Eigen::MatrixXd(),
        const std::vector<std::string>& var_types = { "c", "c" });

  explicit Bicop(const Eigen::MatrixXd& data,
                 const FitControlsBicop& controls = FitControlsBicop(),
                 const std::vector<std::string>& var_types = { "c", "c" });

  Bicop(const Bicop& other);

  explicit Bicop(const std::string& filename);

  explicit Bicop(const nlohmann::json& input);

  Bicop& operator=(Bicop other);

  // Serialize
  nlohmann::json to_json() const;

  void to_file(const std::string& filename) const;

  // Getters and setters
  BicopFamily get_family() const;

  std::string get_family_name() const;

  int get_rotation() const;

  Eigen::MatrixXd get_parameters() const;

  double get_tau() const;

  double get_npars() const;

  double get_loglik() const;
  size_t get_nobs() const;
  double get_aic() const;
  double get_bic() const;
  double get_mbic(const double psi0 = 0.9) const;

  void set_rotation(const int rotation);

  void set_parameters(const Eigen::MatrixXd& parameters);

  void set_var_types(const std::vector<std::string>& var_types = { "c", "c" });

  std::vector<std::string> get_var_types() const;

  // Stats methods
  Eigen::VectorXd pdf(const Eigen::MatrixXd& u) const;

  Eigen::VectorXd cdf(const Eigen::MatrixXd& u) const;

  Eigen::VectorXd hfunc1(const Eigen::MatrixXd& u) const;

  Eigen::VectorXd hfunc2(const Eigen::MatrixXd& u) const;

  Eigen::VectorXd hinv1(const Eigen::MatrixXd& u) const;

  Eigen::VectorXd hinv2(const Eigen::MatrixXd& u) const;

  Eigen::MatrixXd simulate(
    const size_t& n,
    const bool qrng = false,
    const std::vector<int>& seeds = std::vector<int>()) const;

  // Methods modifying the family/rotation/parameters
  void fit(const Eigen::MatrixXd& data,
           const FitControlsBicop& controls = FitControlsBicop());

  void select(const Eigen::MatrixXd& data,
              FitControlsBicop controls = FitControlsBicop());

  // Fit statistics
  double loglik(const Eigen::MatrixXd& u = Eigen::MatrixXd()) const;

  double aic(const Eigen::MatrixXd& u = Eigen::MatrixXd()) const;

  double bic(const Eigen::MatrixXd& u = Eigen::MatrixXd()) const;

  double mbic(const Eigen::MatrixXd& u = Eigen::MatrixXd(),
              const double psi0 = 0.9) const;

  // Misc
  std::string str() const;

  double parameters_to_tau(const Eigen::MatrixXd& parameters) const;

  Eigen::MatrixXd tau_to_parameters(const double& tau) const;

  void flip();

  Eigen::MatrixXd get_parameters_lower_bounds() const;

  Eigen::MatrixXd get_parameters_upper_bounds() const;

  Bicop as_continuous() const;

private:
  Eigen::MatrixXd format_data(const Eigen::MatrixXd& u) const;

  void rotate_data(Eigen::MatrixXd& u) const;

  Eigen::MatrixXd prep_for_abstract(const Eigen::MatrixXd& u) const;

  void check_rotation(int rotation) const;

  void check_data(const Eigen::MatrixXd& u) const;

  void check_data_dim(const Eigen::MatrixXd& u) const;

  void check_var_types(const std::vector<std::string>& var_types) const;

  void flip_abstract_var_types();

  void check_weights_size(const Eigen::VectorXd& weights,
                          const Eigen::MatrixXd& data) const;

  void check_fitted() const;

  unsigned short get_n_discrete() const;

  double compute_mbic_penalty(const size_t nobs, const double psi0) const;

  BicopPtr get_bicop() const;

  BicopPtr bicop_;
  int rotation_{ 0 };
  size_t nobs_{ 0 };
  mutable std::vector<std::string> var_types_;
};
}

#include <vinecopulib/bicop/implementation/class.ipp>
