/**
 * @file io_datatype.h
 * @brief defining data types used for IO class
 *
 * @author Chuan Li, chuanli@clemson.edu
 */

#ifndef IO_DATATYPE_H_
#define IO_DATATYPE_H_

#include <string.h>
#include <iomanip>
#include <fstream>

#include "../interface/environment.h"
#include "../misc/misc_grid.h"
#include "io_exceptions.h"

/**
 * Class to store force field read from .siz and .crg files. Notice that values are all defined to be
 * private so that accessing these values have to be via the provided public member functions.
 */
class CForce
{
   private:
      //string strAtom;         // atom name          a6 ,6 characters
      char chrAtom[7];       // atom name          a6 ,6 characters
      //string strResidue;      // residue name       a3 ,3 characters
      char chrResidue[4];    // residue name       a3 ,3 characters
      //string strResidueNum;   // residue number     a4 ,4 characters
      char chrResidueNum[5]; // residue number     a4 ,4 characters
      //string strChain;        // subunit name       a1 ,1 characters
      char chrChain[2];      // subunit name       a1 ,1 characters
      delphi_real fValue;   // atom radii/charge  f8.4
      delphi_real fSigmaLJ;     // atom LJ sigma     f8.4
      delphi_real fEpsilonLJ;   // atom LJ epsilon   f8.4
      delphi_real fVdwGamma;    // atom vdw gamma     f8.4

   public:
      /**
       * default constructor
       */
      CForce()
      {
         //this->strAtom       = " ";
         strcpy(this->chrAtom, " ");
         //this->strResidue    = " ";
         strcpy(this->chrResidue, " ");
         //this->strResidueNum = " ";
         strcpy(this->chrResidueNum, " ");
         //this->strChain      = " ";
         strcpy(this->chrChain, " ");
         this->fValue        = 0.0;
         this->fSigmaLJ      = 0.0;
         this->fEpsilonLJ   = 0.0;
         this->fVdwGamma     = 0.0;
      };

      /**
       * function to set atom name
       * @param strAtomIn atom name read from input file
       */
      //void setAtom(const string strAtomIn) {this->strAtom = strAtomIn;}
      void setAtom(const string strAtomIn) { memcpy(this->chrAtom, strAtomIn.data(), 7); }

      /**
       * function to set residue name
       * @param strResidueNameIn residue name read from input file
       */
      //void setResidue(const string strResidueNameIn) {this->strResidue = strResidueNameIn;}
      void setResidue(const string strResidueNameIn) { memcpy(this->chrResidue, strResidueNameIn.data(), 4); }

      /**
       * function to set residue number
       * @param strResidueNumIn residue number read from input file
       */
      //void setResidueNum(const string strResidueNumIn) {this->strResidueNum = strResidueNumIn;}
      void setResidueNum(const string strResidueNumIn) { memcpy(this->chrResidueNum, strResidueNumIn.data(), 5);}

      /**
       * function to set chain name
       * @param strChainIn chain name read from input file
       */
      //void setChain(const string strChainIn) {this->strChain = strChainIn;}
       void setChain(const string strChainIn) { memcpy(this->chrChain, strChainIn.data(), 2); }

      /**
       * function to set the value (either charge or radius) associated with a particular atom
       * @param fValueIn force field value associated with this atom
       */
      void setValue(const delphi_real fValueIn) {this->fValue = fValueIn;}

      /**
       * function to set the LJ sigma (non-polar) associated with a particular atom
       * @param fValueIn force field value associated with this atom
       */
      void setSigmaLJ(const delphi_real fValueIn) {this->fSigmaLJ = fValueIn;}

      /**
       * function to set the LJ Epsilon (non-polar) associated with a particular atom
       * @param fValueIn force field value associated with this atom
       */
      void setEpsilonLJ(const delphi_real fValueIn) {this->fEpsilonLJ = fValueIn;}

      /**
       * function to set the gamma (non-polar) associated with a particular atom
       * @param fValueIn force field value associated with this atom
       */
      void setVdwGamma(const delphi_real fValueIn) {this->fVdwGamma = fValueIn;}

      /**
       * function to get atom name
       * @return atom name
       */
      string getAtom() const { return this->chrAtom; }

      /**
       * function to get residue name
       * @return residue name
       */
      string getResidue() const { return this->chrResidue; }

      /**
       * function to get residue number
       * @return residue number
       */
      string getResidueNum() const {return this->chrResidueNum; }

      /**
       * function to get chain name
       * @return chain name
       */
      string getChain() const { return this->chrChain; }

      /**
       * function to get force field value
       * @return charge or radius of this atom
       */
      delphi_real getValue() const {return this->fValue;}
      
      /**
       * function to get LJ sigma 
       * @return LJ sigma of this atom
       */
      delphi_real getSigmaLJ() const {return this->fSigmaLJ;}
      
      /**
       * function to get LJ epsilon
       * @return LJ epsilon of this atom
       */
      delphi_real getEpsilonLJ() const {return this->fEpsilonLJ;}
      
      /**
       * function to get non-polar gamma (surface tension)
       * @return gamma of this atom
       */
      delphi_real getVdwGamma() const {return this->fVdwGamma;}
};

/**
 * class to store atom info read from .pdb file. Notice that values are all defined to be
 * private so that accessing these values have to be via the provided public member functions.
 */
class CAtomPdb // delphi_pdb_file_record
{
    private:
    //public:
      delphi_real fRadius;
      delphi_real fCharge;
      delphi_real fSigmaLJ;                  // LJ Rmin(for non-polar energy)
      delphi_real fEpsilonLJ;               // LJ epsilon (for non-polar energy)
      delphi_real fVdwGamma;                 // surface tension (for non-polar energy)
      delphi_real fSigmaGaussian;           // Shailesh: sigma-Gaussian for the atom
      SGrid<delphi_real> gPose;
      delphi_integer iIndex;
      //string strAtInf; //length=15
      char chrAtInf[16]; //length=15

      //argo added a new attribute
      //look for strAtResname* tags
      //string strAtResname;  //residue name, length=3
      char chrAtResname[4];  //residue name, length=3

   public:
      /**
       * default constructor
       */
      CAtomPdb()
      {
         this->fRadius  = 0.0;
         this->fCharge  = 0.0;
         this->fSigmaLJ      = 0.0;
         this->fEpsilonLJ   = 0.0;
         this->fVdwGamma     = 0.0;
	 this->fSigmaGaussian = 0.0;
         this->iIndex = 0;
         this->gPose.nX = 0.0; this->gPose.nY = 0.0; this->gPose.nZ = 0.0;
         //this->strAtInf = " ";
         strcpy(this->chrAtInf, " ");
         //this->strAtResname = " ";
         strcpy(this->chrAtResname, " ");
      };

      /**
       * function to set radius of this atom
       * @param fRadiusIn radius of this atom
       */
      void setRadius(const delphi_real fRadiusIn) {this->fRadius = fRadiusIn;}

      /**
       * function to set charge of this atom
       * @param fChargeIn charge on this atom
       */
      void setCharge(const delphi_real fChargeIn) {this->fCharge = fChargeIn;}


      /**
       * function to set sigma-gaussian of this atom
       * @param fSigmaGaussianIn sigma-gaussian on this atom
       */
      void setSigmaGaussian(const delphi_real fSigmaGaussianIn) {this->fSigmaGaussian = fSigmaGaussianIn;}

      /**
      * function to set index of this atom
      * @param iIndex index on this atom
      */
      void setIndex(const delphi_integer iIndexIn) { this->iIndex = iIndexIn; }

      /**
       * function to set position of this atom
       * @param gPoseIn position of this atom
       */
      void setPose(const SGrid<delphi_real> gPoseIn) {this->gPose = gPoseIn;}

      /**
       * function to set position of this atom
       * @param fX x-coordinate of this atom
       * @param fY y-coordinate of this atom
       * @param fZ z-coordinate of this atom
       */
      void setPose(const delphi_real fX, const delphi_real fY, const delphi_real fZ)
      {this->gPose.nX = fX; this->gPose.nY = fY; this->gPose.nZ = fZ;}

      /**
       * function to store the rest of line read from .pdb file for this atom
       * @param strAtInfIn string of the line containing the rest info in .pdb for this atom
       */
      //void setAtInf(const string strAtInfIn) {this->strAtInf = strAtInfIn;}
      void setAtInf(const string strAtInfIn) { memcpy(this->chrAtInf, strAtInfIn.data(), 16); }

      /**
       * function to store the residue name as read from .pdb file for this atom
       * @param strAtInfIn string of the line containing the residue name info in .pdb for this atom
       */
      //void setAtResname(const string strAtResname_in) {this->strAtResname = strAtResname_in;}
      void setAtResname(const string strAtResname_in) { strncpy(this->chrAtResname, strAtResname_in.c_str(), 4); this->chrAtResname[3]='\0'; }

      /**
       * function to set the LJ sigma value of an atom
       * @param fVS: LJ sigma value read from nonpolar param file
       */
      void setSigmaLJ(const delphi_real fVS) {this->fSigmaLJ = fVS;}

      /**
       * function to set the LJ Epsilon value of an atom
       * @param fVE: LJ epsilon value read from nonpolar param file
       */
      void setEpsilonLJ(const delphi_real fVE) {this->fEpsilonLJ = fVE;}
      
      /**
       * function to set the gamma value of an atom
       * Gamma is mltiplied with the SASA of that atom to yield its contribution to the 
       * total Cavity energy of the molecule.
       * @param fG: gamma value read from nonpolar param file
       */
      void setVdwGamma(const delphi_real fG) {this->fVdwGamma = fG;}
      /**
       * function to get radius of this atom
       * @return radius of this atom
       */
      delphi_real getRadius() const {return this->fRadius;}

      /**
       * function to get charge on this atom
       * @return charge on this atom
       */
      delphi_real getCharge() const {return this->fCharge;}
       
      /**
       * function to get sigma-gaussian of this atom
       * @return fSigmaGaussian on this atom
       */
      delphi_real getSigmaGaussian() const {return this->fSigmaGaussian;}

      /**
      * function to get index on this atom
      * @return index on this atom
      */
      delphi_integer getIndex() const { return this->iIndex; }

      /**
       * function to get position of this atom
       * @return position of this atom
       */
      SGrid<delphi_real> getPose() const {return this->gPose;}

      /**
       * function to get rest info of this atom
       * @return string containing rest info of this atom
       */
      string getAtInf() const {return this->chrAtInf;}

      /**
       * function to get residue name of this atom
       * @return string containing residue name of this atom
       */
      string getAtResname() const {return this->chrAtResname;}
      
      /**
       * function to get LJ sigma of this atom
       * @return LJ sigma on this atom
       */
      delphi_real getSigmaLJ() const {return this->fSigmaLJ;}
      
      /**
       * function to get LJ epsilon of this atom
       * @return LJ epsilon on this atom
       */
      delphi_real getEpsilonLJ() const {return this->fEpsilonLJ;}
      
      /**
       * function to get gamma of this atom
       * @return gamma on this atom
       */
      delphi_real getVdwGamma() const {return this->fVdwGamma;}

};

/**
 * class that inherits CAtomPdb and has additional member functions and a constructor.
 */
class CSimAtom : public CAtomPdb
{
private:
    char atomName[7];
    char atomResname[4];
    int  atomResid;

public:
    // new Constructor
    CSimAtom() : CAtomPdb()
    {
        strcpy( this->atomName, " ");
        strcpy( this->atomResname, " ");
        atomResid= -1;
    }

    // destructor
    ~CSimAtom(){};

    /*
     * function to set atom name
     * @param name of this atom
     */
    void setAtomName(string&  name) { memcpy(this->atomName, name.data(), 7) ; }
    /*
     * function to get atom name
     * @return name of this atom
     */
    string getAtomName() const { return this->atomName; }
    
    /*
     * function to set residue name of the atom
     * @param name of the residue
     */
    void setAtomResname(string& name) { memcpy(this->atomResname, name.data(), 4) ; }
    /*
     * function to get the residue this atom belongs to
     * @return name of the residue this atom belongs to
     */
    string getAtomResname() const { return this->atomResname; }
    
    /*
     * function to set atom's residue ID
     * @param index of the residue
     */
    void setAtomResid(const int& rid) { this->atomResid = rid ; }
    /*
     * function to get atom's residue ID
     * @return index of the residue
     */
    int getAtomResid() const { return this->atomResid; }
};

/**
 * class to store the coordinates of the simulation atoms (objects of CSimAtom) in order of their appearence.
 * A vector of its objects will be created as a trajectory file is read and then the vector will be used to run
 * all of the modules to get the energy for each frame.
 */
class CFrame
{
public:
    int numAtoms;
    
    vector< SGrid<delphi_real> > coordinates;
    
    vector< SGrid<delphi_real> > aCoordinates;  // actual coordinates (identical to coordinates)
    vector< SGrid<delphi_real> > gCoordinates;  // gridCoordinates derived from actual coordinates
    
    SGrid<delphi_real> gfFrameMinCoordinate;    // 3D min
    SGrid<delphi_real> gfFrameMaxCoordinate;    // 3D max 
    SGrid<delphi_real> gfFrameCoordinateRange;  // (max - min)
    SGrid<delphi_real> gfFrameMidPoint;         // (max + min)/2
    SGrid<delphi_real> gfFrameBoxCenter;        // can be differentent from geom center depending on acenter/offset

    delphi_integer iFrameGrid;                     // gsize
    delphi_real    fFrameScale;                    // scale
    delphi_real    fFramePercentageFill;           // perfil
    delphi_real    fDebyeNum;                      // debyenum

    // constructor 1
    CFrame()
    {
        this->numAtoms = -1;
        this->coordinates.clear();
    };
    
    // constructor 2
    CFrame(int &natoms)
    {
        this->numAtoms = natoms;
        this->coordinates.resize(natoms);
    };

    // Destructor
    ~CFrame() {};
    

    // compute and assign a min, max and geom center to this frame 
    // while using the radius of atoms which are provided as input 
    // in the form of a vector of radii. 
    void computeFrameGeometry(vector<delphi_real>& vctfRadii)
    {

        SGrid <delphi_real> minCoord, maxCoord;

        minCoord.nX =  6000; minCoord.nY =  6000; minCoord.nZ =  6000;
        maxCoord.nX = -6000; maxCoord.nY = -6000; maxCoord.nZ = -6000;
        int iv = 0;
        delphi_real tmpRadius;

        vector< SGrid<delphi_real> >::iterator cit = this->coordinates.begin();
        while( cit != this->coordinates.end() )
        {
            tmpRadius = vctfRadii[iv];    
            minCoord = optMin<delphi_real> (minCoord, (*cit) - tmpRadius);
            maxCoord = optMax<delphi_real> (maxCoord, (*cit) + tmpRadius);
            
            cit++;
            iv++;
        }
        
        this->gfFrameMinCoordinate = minCoord;
        this->gfFrameMaxCoordinate = maxCoord;
        this->gfFrameCoordinateRange = maxCoord - minCoord;
        this->gfFrameMidPoint        = 0.5 * (this->gfFrameMinCoordinate + this->gfFrameMaxCoordinate);

        return;

    }

    // dump frame
    void dumpFrame(const string& strPdbFile, const int& iModPdbFormatOut)
    {
        ofstream ofPdbFileStream(strPdbFile.c_str());

        // print Min
        ofPdbFileStream << setw(15) << "Minimum : " ;
        ofPdbFileStream << right << setw(10) << setprecision(3) << this->gfFrameMinCoordinate.nX << " ";
        ofPdbFileStream << right << setw(10) << setprecision(3) << this->gfFrameMinCoordinate.nY << " ";
        ofPdbFileStream << right << setw(10) << setprecision(3) << this->gfFrameMinCoordinate.nZ;
        ofPdbFileStream << endl;
                              
        // print Max
        ofPdbFileStream << setw(15) << "Maximum : " ;
        ofPdbFileStream << right << setw(10) << setprecision(3) << this->gfFrameMaxCoordinate.nX << " ";
        ofPdbFileStream << right << setw(10) << setprecision(3) << this->gfFrameMaxCoordinate.nY << " ";
        ofPdbFileStream << right << setw(10) << setprecision(3) << this->gfFrameMaxCoordinate.nZ;
        ofPdbFileStream << endl;
        
        // print geometric midpoint 
        ofPdbFileStream << setw(15) << "Midpoint : " ;
        ofPdbFileStream << right << setw(10) << setprecision(3) << this->gfFrameMidPoint.nX << " ";
        ofPdbFileStream << right << setw(10) << setprecision(3) << this->gfFrameMidPoint.nY << " ";
        ofPdbFileStream << right << setw(10) << setprecision(3) << this->gfFrameMidPoint.nZ;
        ofPdbFileStream << endl;
        
        // print box Center
        ofPdbFileStream << setw(15) << "Box center : " ;
        ofPdbFileStream << right << setw(10) << setprecision(3) << this->gfFrameBoxCenter.nX << " ";
        ofPdbFileStream << right << setw(10) << setprecision(3) << this->gfFrameBoxCenter.nY << " ";
        ofPdbFileStream << right << setw(10) << setprecision(3) << this->gfFrameBoxCenter.nZ;
        ofPdbFileStream << endl;
        
        // print coordinate range 
        ofPdbFileStream << setw(15) << "Coordinate Range: " ;
        ofPdbFileStream << right << setw(10) << setprecision(3) << this->gfFrameCoordinateRange.nX << " ";
        ofPdbFileStream << right << setw(10) << setprecision(3) << this->gfFrameCoordinateRange.nY << " ";
        ofPdbFileStream << right << setw(10) << setprecision(3) << this->gfFrameCoordinateRange.nZ;
        ofPdbFileStream << endl;

        // scale
        ofPdbFileStream << setw(15) << "Scale : " << this->fFrameScale << endl;

        // perfil
        ofPdbFileStream << setw(15) << "\% Fill : " << this->fFramePercentageFill << endl;

        // grid size
        ofPdbFileStream << setw(15) << "Grid size : " << this->iFrameGrid << endl;
        
        // print the coordinates of all the atoms
        vector< SGrid<delphi_real> >::iterator trajSeeker = this->coordinates.begin();
        while (trajSeeker != this->coordinates.end())
        {
            ofPdbFileStream << right << setw(10) << setprecision(3) << (*trajSeeker).nX << " " ;
            ofPdbFileStream << right << setw(10) << setprecision(3) << (*trajSeeker).nY << " " ;
            ofPdbFileStream << right << setw(10) << setprecision(3) << (*trajSeeker).nZ << endl;
            trajSeeker++;
        }

        ofPdbFileStream.close();
    
    }

};
#endif // IO_DATATYPE_H_
