/***********************************************************************
 * 
 *  This is ClassIsotope.h, To extract the isotope mass from nubase_4.mas20.txt
 * 
 *-------------------------------------------------------
 *  created by Ryan (Tsz Leung) Tang, Sept-6, 2024
 *  email: goluckyryan@gmail.com
 * ********************************************************************/


#ifndef ISOTOPE_H
#define ISOTOPE_H

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include "constant.h" // amu
#include <stdlib.h>  //atoi
#include <algorithm>

std::string massData="nubase_4.mas20.txt";

class Isotope {
public:
  int A, Z;
  double Mass, MassError, BEA; // BEA in keV
  short twoSpin; // 2*J
  short parity;
  std::string Name, Symbol; // Name = 6Li, Symbol = Li
  std::string dataSource;
  
  Isotope(){dataSource = massData;};
  Isotope(int a, int z){ dataSource = massData; SetIso(a,z);  };
  Isotope(std::string name){ dataSource = massData; SetIsoByName(name); };

  void SetMassTablePath(std::string path){ dataSource = path;}

  void SetIso(int a, int z);
  void SetIsoByName(std::string name);

  double CalSp(int Np, int Nn); // this for the Np-proton, Nn-neutron removal 
  double CalSp2(int a, int z); // this is for (a,z) nucleus removal

  double CalBeta(double T){
    double Etot = Mass + T;
    double gamma = 1 + T/Mass;
    double beta = sqrt(1 - 1 / gamma / gamma ) ;
    return beta;
  }

  void Print();
  void ListShell();
   
private:

  void InvalidIso();
  void BreakName(std::string nameStr);
  void BreakJpi(std::string jPiStr);
  void DigestLine(std::string line, int A, int Z);

  void FindMassByAZ(int a, int z); // give mass, massError, BEA, Name, Symbol;
  void FindMassByName(std::string name); // give Z, mass, massError, BEA;

  int TwoJ(int nShell);
  std::string Orbital(int nShell);
  int magic(int i){
    switch (i){
      case 0: return   2; break;
      case 1: return   8; break;
      case 2: return  20; break;
      case 3: return  28; break;
      case 4: return  40; break;
      case 5: return  50; break;
      case 6: return  82; break;
      case 7: return 128; break;
    }
    return 0;
  }

  int magicShellID(int i){
    switch (i){
      case 0: return   0; break;
      case 1: return   2; break;
      case 2: return   5; break;
      case 3: return   6; break;
      case 4: return   9; break;
      case 5: return  10; break;
      case 6: return  15; break;
      case 7: return  21; break;
    }
    return 0;
  }
  
  int fileStartLine;
  int fileEndLine;
  int lineMass050_099;
  int lineMass100_149;
  int lineMass150_199;
  int lineMass200_249;
  int lineMass250;

  int colA[2];
  int colZ[2];
  int colName[2];
  int colMassExcess[2];
  int colMassError[2];
  int colSpin[2];
  
  void setFileLinesAndCol(){
    fileStartLine = 26;
    fileEndLine = 5869;
    
    lineMass050_099 = 638;
    lineMass100_149 = 1718;
    lineMass150_199 = 3123;
    lineMass200_249 = 4591;
    lineMass250     = 5495;

    colA[0] = 0;
    colA[1] = 3;

    colZ[0] = 4;
    colZ[1] = 8;

    colName[0] = 11;
    colName[1] = 16;

    colMassExcess[0] = 18;
    colMassExcess[1] = 31;

    colMassError[0] = 31;
    colMassError[1] = 42;

    colSpin[0] = 88;
    colSpin[1] = 97;
  }
  
  bool isFindOnce;
  
};

void Isotope::SetIso(int a, int z){
  FindMassByAZ(a,z); 
}

void Isotope::SetIsoByName(std::string name){
  FindMassByName(name); 
}

void Isotope::InvalidIso(){
  this->BEA  = std::nan("");
  this->Mass = std::nan("");
  this->MassError = std::nan("");
  this->Symbol = "non";
  this->Name   = "non";
  this->twoSpin = 0;
  this->parity = 0;
}

void Isotope::BreakName(std::string nameStr){
  std::string::size_type lastDigit = nameStr.find_last_of("0123456789");

  this->Symbol = nameStr.substr(lastDigit + 1);
  if (this->Symbol.length() == 1) {
    this->Symbol += " ";
  }

  this->A = std::stoi(nameStr.substr(0, lastDigit + 1));

}

void Isotope::BreakJpi(std::string jPiStr){

  jPiStr.erase(std::remove(jPiStr.begin(), jPiStr.end(), '('), jPiStr.end());
  jPiStr.erase(std::remove(jPiStr.begin(), jPiStr.end(), ')'), jPiStr.end());

  std::string::size_type lastDigit = jPiStr.find_last_of("0123456789");

  std::string spinStr = jPiStr.substr(0, lastDigit + 1);

  std::string::size_type lastSlash = spinStr.find_last_of('/');

  if (lastSlash != std::string::npos) {
    twoSpin = std::stoi(spinStr.substr(0, lastSlash + 1));
  } else {
    twoSpin = 2 * std::stoi(spinStr);
  }

  this->parity = (jPiStr.substr(lastDigit + 1) == "+") ? 1 : -1;

}

void Isotope::DigestLine(std::string line, int A, int Z){

  double massExcess = atof((line.substr(colMassExcess[0],colMassExcess[1]-colMassExcess[0])).c_str());

  Mass = massExcess/1000. + A * amu - Z * me;
  MassError =  atof((line.substr(colMassError[0],colMassError[1]-colMassError[0])).c_str()) / 1000.;

  BEA = (Z * mp + (A-Z) * mn - Mass)/A *1000;

  std::string str = line.substr(colName[0],colName[1]-colName[0]);
  str.erase(remove(str.begin(), str.end(), ' '), str.end());
  Name = str;

  BreakName(Name);

  if( this->Name == "1H" ) this->Name = "p";
  if( this->Name == "2H" ) this->Name = "d";
  if( this->Name == "3H" ) this->Name = "t";
  if( this->Name == "4He" ) this->Name = "a";

  str = line.substr(colSpin[0], colSpin[1]-colSpin[0]);
  str.erase(remove(str.begin(), str.end(), ' '), str.end());
  str.erase(std::remove(str.begin(), str.end(), '*'), str.end());
  str.erase(std::remove(str.begin(), str.end(), '#'), str.end());
  std::string Jpi = str;

  BreakJpi(Jpi);

  // printf("A %d  Z: %d | %s | %f | %f |%s|\n", A, Z, Name.c_str(), massExcess, Mass, Jpi.c_str());
}


void Isotope::FindMassByAZ(int A, int Z){
  std::string line;
  int    lineNum=0;

  std::ifstream myfile;
  int    flag=0;

  setFileLinesAndCol();

  int numLineStart = fileStartLine;
  int numLineEnd  = fileEndLine;

  this->A = A;
  this->Z = Z;

  if ( A >= 50 && A < 100) numLineStart = lineMass050_099;
  if ( A >=100 && A < 150) numLineStart = lineMass100_149;
  if ( A >=150 && A < 200) numLineStart = lineMass150_199;
  if ( A >=200 && A < 250) numLineStart = lineMass200_249;
  if ( A >=250           ) numLineStart = lineMass250;

  myfile.open(dataSource.c_str());

  if (myfile.is_open()) {
    while (/*! myfile.eof() &&*/ flag == 0 && lineNum <numLineEnd){
      lineNum ++ ;
      // printf("%3d  ",lineNum);
      getline (myfile,line);

      if (lineNum >= numLineStart ){
        int list_Z     = std::stoi((line.substr(colZ[0],colZ[1]-colZ[0])));
      	int list_A     = std::stoi((line.substr(colA[0],colA[1]-colA[0])));

      	if ( A == list_A && Z * 10 == list_Z) {
    
          DigestLine(line, A, Z);          

     		  flag = 1;
      	}else if ( list_A > A) {
          InvalidIso();
          break;
        }

      }
    }
    
    myfile.close();
  }else {
    printf("Unable to open %s\n", dataSource.c_str());
  }
}

void Isotope::FindMassByName(std::string name){

  // done seperate the Mass number and the name 
  if( name == "n" ) {
    this->Name = "1n";
    this->BEA       = 0;
    this->Mass      = mn;
    this->MassError = 0;
    this->Name      = "n";
    this->A         = 1;
    this->Z         = 0;
    this->twoSpin   = 1;
    this->parity    = 1;
    return;
  }

  if( name == "p" ) name = "1H";
  if( name == "d" ) name = "2H";
  if( name == "t" ) name = "3H";
  if( name == "a" ) name = "4He";

  this->Name = name;
  BreakName(name);

  // find the nucleus in the data
  std::string line;
  int    lineNum=0;

  std::ifstream myfile;
  int    flag=0;

  setFileLinesAndCol();

  int numLineStart = fileStartLine;
  int numLineEnd  = fileEndLine;

  if ( A >= 50 && A < 100) numLineStart = lineMass050_099;
  if ( A >=100 && A < 150) numLineStart = lineMass100_149;
  if ( A >=150 && A < 200) numLineStart = lineMass150_199;
  if ( A >=200 && A < 250) numLineStart = lineMass200_249;
  if ( A >=250           ) numLineStart = lineMass250;

  myfile.open(dataSource.c_str());

  if (myfile.is_open()) {
    while (/*! myfile.eof() &&*/ flag == 0 && lineNum <numLineEnd){
      lineNum ++ ;
      //printf("%3d  ",lineNum);
      getline (myfile,line);

      if (lineNum >= numLineStart ){

        std::string nameStr = line.substr(colName[0],colName[1]-colName[0]);
        nameStr.erase(remove(nameStr.begin(), nameStr.end(), ' '), nameStr.end());

        int list_A     = std::stoi((line.substr(colA[0],colA[1]-colA[0])));
        int list_Z     = std::stoi((line.substr(colZ[0],colZ[1]-colZ[0])));

        // printf("A %d Z %d | %s \n", list_A, list_Z, nameStr.c_str());

        if ( list_A == A && list_Z % 10 == 0  &&  Name == nameStr) {

          Z = list_Z/10;

          DigestLine(line, list_A, Z);

          flag = 1;
        }else if ( list_A > this->A) {
          InvalidIso();
          break;
        }
      }
    }
    myfile.close();
  }else {
    printf("Unable to open %s\n", dataSource.c_str());
  }
}

double Isotope::CalSp(int Np, int Nn){
  Isotope nucleusD(A - Np - Nn, Z - Np);  

  if( !std::isnan(nucleusD.Mass) ){
    return nucleusD.Mass + Nn*mn + Np*mp - this->Mass;
  }else{
    return std::nan("");
  }
}

double Isotope::CalSp2(int a, int z){
  Isotope nucleusD(A - a , Z - z);
  Isotope nucleusS(a,z);  

  if( !std::isnan(nucleusD.Mass) && !std::isnan(nucleusS.Mass) ){
    return nucleusD.Mass + nucleusS.Mass - this->Mass;
  }else{
    return std::nan("");
  }
}

int Isotope::TwoJ(int nShell){

  switch(nShell){
    case  0: return  1; break; // 0s1/2
    case  1: return  3; break; // 0p3/2
    case  2: return  1; break; // 0p1/2 -- 8
    case  3: return  5; break; // 0d5/2
    case  4: return  1; break; // 1s1/2
    case  5: return  3; break; // 0d3/2 -- 20
    case  6: return  7; break; // 0f7/2 -- 28
    case  7: return  3; break; // 1p3/2
    case  8: return  1; break; // 1p1/2
    case  9: return  5; break; // 0f5/2 -- 40
    case 10: return  9; break; // 0g9/2 -- 50
    case 11: return  7; break; // 0g7/2
    case 12: return  5; break; // 1d5/2
    case 13: return 11; break; // 0h11/2
    case 14: return  3; break; // 1d3/2
    case 15: return  1; break; // 2s1/2 -- 82
    case 16: return  9; break; // 0h9/2
    case 17: return  7; break; // 1f7/2
    case 18: return 13; break; // 0i13/2
    case 19: return  3; break; // 2p3/2
    case 20: return  5; break; // 1f5/2
    case 21: return  1; break; // 1p1/2 -- 126
    case 22: return  9; break; // 1g9/2
    case 23: return 11; break; // 0i11/2
    case 24: return 15; break; // 0j15/2
    case 25: return  5; break; // 2d5/2
    case 26: return  1; break; // 3s1/2
    case 27: return  3; break; // 2d3/2
    case 28: return  7; break; // 1g7/2
  }

  return 0;
}

std::string Isotope::Orbital(int nShell){

  switch(nShell){
    case  0: return  "0s1 "; break;  //
    case  1: return  "0p3 "; break;  //
    case  2: return  "0p1 "; break;  //-- 8
    case  3: return  "0d5 "; break;  //
    case  4: return  "1s1 "; break;  //
    case  5: return  "0d3 "; break;  //-- 20
    case  6: return  "0f7 "; break;  //-- 28
    case  7: return  "1p3 "; break;  //
    case  8: return  "1p1 "; break;  //
    case  9: return  "0f5 "; break;  //-- 40
    case 10: return  "0g9 "; break;  //-- 50
    case 11: return  "0g7 "; break;  //
    case 12: return  "1d5 "; break;  //
    case 13: return  "0h11"; break;  //
    case 14: return  "1d3 "; break;  //
    case 15: return  "2s1 "; break;  //-- 82
    case 16: return  "0h9 "; break;  //
    case 17: return  "1f7 "; break;  //
    case 18: return  "0i13"; break;  //
    case 19: return  "2p3 "; break;  //
    case 20: return  "1f5 "; break;  //
    case 21: return  "1p1 "; break;  //-- 126
    case 22: return  "1g9 "; break;  //
    case 23: return  "0i11"; break;  //
    case 24: return  "0j15"; break;  //
    case 25: return  "2d5 "; break;  //
    case 26: return  "3s1 "; break;  //
    case 27: return  "2d3 "; break;  //
    case 28: return  "1g7 "; break;  //
  }

  return "nan";
}

void Isotope::ListShell(){

  if( Mass < 0 ) return;

  int n = A-Z;
  int p = Z;

  int k = std::min(n,p);
  int nMagic = 0;
  for( int i = 0; i < 7; i++){
    if( magic(i) < k && k <= magic(i+1) ){
      nMagic = i;
      break;
    }
  }

  int coreShell = magicShellID(nMagic-1);
  int coreZ1 = magic(nMagic-1);
  int coreZ2 = magic(nMagic);

  Isotope core1( 2*coreZ1, coreZ1);
  Isotope core2( 2*coreZ2, coreZ2);

  printf("------------------ Core:%3s, inner Core:%3s \n", (core2.Name).c_str(), (core1.Name).c_str());
  printf("         || ");
  int t = std::max(n,p);
  int nShell = 0;
  do{
    int occ = TwoJ(nShell)+1;
    if( nShell > coreShell) {
      printf("%4s", Orbital(nShell).c_str());
         if( nShell == 0 || nShell == 2 || nShell == 5 || nShell ==6 || nShell == 9  || nShell == 10 || nShell == 15 || nShell == 21){
        printf("|");
      }else{
        printf(",");
      } 
    }
    t = t - occ;
    nShell++;
  }while( t > 0  && nShell < 29);
  for( int i = 1; i <= 6; i++){
    if (nShell < 28) {
      printf("%4s,", Orbital(nShell).c_str());
    }else if( nShell == 28 ) {
      printf("%4s", Orbital(nShell).c_str());
    }
    nShell ++;
  }
  if (nShell < 29) printf("%4s", Orbital(nShell).c_str());
  printf("\n");


  printf(" Z = %3d || ", p);
  nShell = 0;
  do{
    int occ = TwoJ(nShell)+1;
    if( nShell > coreShell ){
      if( p > occ ) {
         printf("%-4d", occ);
         if( nShell == 0 || nShell == 2 || nShell == 5 || nShell ==6 || nShell == 9  || nShell == 10 || nShell == 15 || nShell == 21){
          printf("|");
        }else{
          printf(",");
        } 
      }else{
        printf("%-4d", p);
      }
    }
    p = p - occ;
    nShell++;
  }while( p > 0  && nShell < 29);
  printf("\n");

  printf(" N = %3d || ", n);
  nShell = 0;  
  do{
    int occ = TwoJ(nShell)+1;
    if ( nShell > coreShell ){
      if( n > occ ) {
         printf("%-4d", occ);
         if( nShell == 0 || nShell == 2 || nShell == 5 || nShell ==6 || nShell == 9  || nShell == 10 || nShell == 15 || nShell == 21){
          printf("|");
        }else{
          printf(",");
        } 
      }else{
        printf("%-4d", n);
      }
    }
    n = n - occ;
    nShell++;
  }while( n > 0  && nShell < 29);
  printf("\n");

  printf("------------------ \n");
}



void Isotope::Print(){

  if (Mass > 0){  
    
    printf(" using mass data : %s \n", dataSource.c_str());

    std::string jPiStr = "";
    if( twoSpin % 2  == 0) {
      jPiStr = std::to_string(twoSpin/2).c_str();
    }else{
      jPiStr = std::to_string(twoSpin) + "/2";
    }

    printf(" mass of \e[47m\e[31m%s\e[m nucleus (Z,A)=(%3d,%3d) is \e[47m\e[31m%12.5f\e[m MeV, J-pi \e[31m%s%s\e[m\n", 
            Name.c_str(), 
            Z, 
            A, 
            Mass,
            jPiStr.c_str(),
            parity == 1 ? "+" : "-");     

    printf(" total BE    : %12.5f MeV\n",BEA*A/1000.);
    printf(" total BE/A  : %12.5f MeV\n",BEA/1000.);
    printf(" mass in amu : %12.5f u\n",Mass/amu);
    printf(" mass excess : %12.5f MeV +- %12.5f\n", Mass + Z*0.510998950 - A*amu, MassError);
    printf("-------------- Seperation energy \n");
    printf(" S1p: %8.4f| S1n: %8.4f| S(2H ): %8.4f| S1p1n : %8.4f\n", CalSp(1, 0), CalSp(0, 1), CalSp2(2, 1), CalSp(1, 1));
    printf(" S2p: %8.4f| S2n: %8.4f| S(3He): %8.4f| S(3H) : %8.4f\n", CalSp(2, 0), CalSp(0, 2), CalSp2(3, 2), CalSp2(3, 1));
    printf(" S3p: %8.4f| S3n: %8.4f| S(4He): %8.4f|\n",               CalSp(3, 0), CalSp(0, 3), CalSp2(4, 2));
    printf(" S4p: %8.4f| S4n: %8.4f| \n",                             CalSp(4, 0), CalSp(0, 4));

  }else{
    printf("Error %6.0f, no nucleus with (Z,A) = (%3d,%3d). \n", Mass, Z, A);
  }


}

#endif
