#ifndef ELECTRON_FLUX_H
#define ELECTRON_FLUX_H

#include <map>
#include <string>
#include "ATC_TypeDefs.h"

namespace ATC {

  /**
   *  @class  ElectronFlux
   *  @brief  Base class for the flux appearing in the electron density transport equation
   */

  class ElectronFlux
  {
    public:
      ElectronFlux();
      virtual ~ElectronFlux() {};
      /** computes flux */
      virtual void electron_flux(const FIELD_MATS &fields,
                                 const GRAD_FIELD_MATS & /* gradFields */,
                                       DENS_MAT_VEC &flux)
      {




        FIELD_MATS::const_iterator etField = fields.find(ELECTRON_TEMPERATURE);
        const DENS_MAT & etMat = etField->second;
        zeroWorkspace_.reset(etMat.nRows(),etMat.nCols());
        flux[0] = zeroWorkspace_;
        flux[1] = zeroWorkspace_;
        flux[2] = zeroWorkspace_;
      };
      void electron_convection(const FIELD_MATS &fields,
                                     DENS_MAT_VEC &flux)
      {
        FIELD_MATS::const_iterator edField = fields.find(ELECTRON_DENSITY);
        FIELD_MATS::const_iterator evField = fields.find(ELECTRON_VELOCITY);
        const DENS_MAT & n = edField->second;
        const DENS_MAT & v = evField->second;
        const CLON_VEC vx(v,CLONE_COL,0);
        const CLON_VEC vy(v,CLONE_COL,1);
        const CLON_VEC vz(v,CLONE_COL,2);
        zeroWorkspace_.reset(v.nRows(),1);

        if (maskX_) {
          flux[0] = zeroWorkspace_;
        }
        else {
          flux[0] = vx;
          flux[0] *= n;  // scale by n
        }

        if (maskY_) {
          flux[1] = zeroWorkspace_;
        }
        else {
          flux[1] = vy;
          flux[1] *= n;
        }

        if (maskZ_) {
          flux[2] = zeroWorkspace_;
        }
        else {
          flux[2] = vz;
          flux[2] *= n;
        }

      };
    protected:
      bool maskX_, maskY_, maskZ_;
      DENS_MAT zeroWorkspace_;
  };
  //-----------------------------------------------------------------------

  /**
   *  @class  ElectronFluxLinear
   *  @brief  Class for drift-diffusion electron flux with linear dependency on the electron density gradient
   */

  class ElectronFluxLinear : public ElectronFlux
  {
    public:
      ElectronFluxLinear(std::fstream &matfile, std::map<std::string,double> & parameters);
      virtual ~ElectronFluxLinear() {};
      virtual void electron_flux(const FIELD_MATS &fields,
                                 const GRAD_FIELD_MATS &gradFields,
                                       DENS_MAT_VEC &flux)
      {
        FIELD_MATS::const_iterator edField = fields.find(ELECTRON_DENSITY);
        GRAD_FIELD_MATS::const_iterator dEdField = gradFields.find(ELECTRON_DENSITY);
        GRAD_FIELD_MATS::const_iterator dPhiField = gradFields.find(ELECTRIC_POTENTIAL);
         // J_n = - \mu n E  - D grad n
        // note electrons move counter to electric field grad E = - grad \phi
        const DENS_MAT & n = edField->second;
        const DENS_MAT_VEC & dn   = dEdField->second;
        const DENS_MAT_VEC & dphi = dPhiField->second;

        //n.print("DENSITY");
        //for (int i = 0; i < 3; i++) {
        //  dn[i].print("GRAD N");
        //  dphi[i].print("GRAD PHI");
        //}
        //cout << "------------------------------------------------====\n";

        flux[0] = n;
        flux[1] = n;
        flux[2] = n;

        if (maskX_)
          flux[0] = 0.;
        else {
          flux[0] *= electronMobility_*dphi[0]; // scale by n to get : n \mu grad(\phi)
          flux[0] += -electronDiffusivity_* dn[0];
        }

        if (maskY_)
          flux[1] = 0.;
        else {
          flux[1] *= electronMobility_* dphi[1] ;
          flux[1] += -electronDiffusivity_* dn[1];
        }

        if (maskZ_)
          flux[2] = 0.;
        else {
          flux[2] *= electronMobility_*dphi[2];
          flux[2] += -electronDiffusivity_* dn[2];
        }

      };
    protected:
      double electronMobility_, electronDiffusivity_;
  };
  //-----------------------------------------------------------------------

  /**
   *  @class  ElectronFluxThermopower
   *  @brief  Class for defining the electron flux (i.e., current) to include the elctron velocity or have a electron temperature-dependent mobility
   */

  class ElectronFluxThermopower : public ElectronFlux
  {
    public:
      ElectronFluxThermopower(std::fstream &matfile,std::map<std::string,double> & parameters);
      virtual ~ElectronFluxThermopower() {};
      virtual void electron_flux(const FIELD_MATS &fields,
                                 const GRAD_FIELD_MATS &gradFields,
                                 DENS_MAT_VEC &flux)
      {
        if (fields.find(ELECTRON_VELOCITY)!=fields.end()) {
          // J_n = - e n v, note the electron charge e is unity
          electron_convection(fields,flux);
          flux[0] *= -1;
          flux[1] *= -1;
          flux[2] *= -1;
        }
        else {
          FIELD_MATS::const_iterator edField = fields.find(ELECTRON_DENSITY);
          FIELD_MATS::const_iterator etField = fields.find(ELECTRON_TEMPERATURE);
          GRAD_FIELD_MATS::const_iterator dEdField = gradFields.find(ELECTRON_VELOCITY);
          GRAD_FIELD_MATS::const_iterator dPhiField = gradFields.find(ELECTRIC_POTENTIAL);
          GRAD_FIELD_MATS::const_iterator dEtField = gradFields.find(ELECTRON_TEMPERATURE);

          // J_n = - \mu n grad \phi  - \mu kB/e T_e grad n
          //       - \mu S n grad T_e - \mu kB/e n grad T_e
          const DENS_MAT & n   = edField->second;
          const DENS_MAT_VEC & dn   = dEdField->second;
          const DENS_MAT_VEC & dphi = dPhiField->second;
          const DENS_MAT_VEC & dT = dEtField->second;

          flux[0] = -electronMobility_*dphi[0];
          flux[1] = -electronMobility_*dphi[1];
          flux[2] = -electronMobility_*dphi[2];
          double coef = -electronMobility_*(seebeckCoef_ + kBeV_);
          flux[0] += coef* dT[0];
          flux[1] += coef* dT[1];
          flux[2] += coef* dT[2];
          flux[0] *= n; // scale by n
          flux[1] *= n;
          flux[2] *= n;

          //GRAD_FIELD tmp = dn;
          const DENS_MAT & Te   = etField->second;
          //tmp[0] *= Te;
          //tmp[1] *= Te;
          //tmp[2] *= Te;
          coef = -electronMobility_*kBeV_;
          //flux[0] += tmp[0];
          flux[0] += dn[0].mult_by_element(Te);
          flux[1] += dn[1].mult_by_element(Te);
          flux[2] += dn[2].mult_by_element(Te);
        }
      };
  protected:
      double electronMobility_, seebeckCoef_;
  };
  //-----------------------------------------------------------------------

  /**
   *  @class  ElectronFluxConvection
   *  @brief  Class for electron flux based on the standard convection term
   */

  class ElectronFluxConvection : public ElectronFlux
  {
    public:
      ElectronFluxConvection(std::fstream &matfile,std::map<std::string,double> & parameters);
      virtual ~ElectronFluxConvection() {};
      virtual void electron_flux(const FIELD_MATS &fields,
                                 const GRAD_FIELD_MATS & /* gradFields */,
                                       DENS_MAT_VEC &flux)
      {
        // flux = n v
        electron_convection(fields,flux);
      };
  };
}

#endif


