// ---------------------------------------------------------------------------
// - Periodic.cpp                                                            -
// - afnix:phy service - periodic element class implementation               -
// ---------------------------------------------------------------------------
// - This program is free software;  you can redistribute it  and/or  modify -
// - it provided that this copyright notice is kept intact.                  -
// -                                                                         -
// - This program  is  distributed in  the hope  that it will be useful, but -
// - without  any  warranty;  without  even   the   implied    warranty   of -
// - merchantability or fitness for a particular purpose.  In no event shall -
// - the copyright holder be liable for any  direct, indirect, incidental or -
// - special damages arising in any way out of the use of this software.     -
// ---------------------------------------------------------------------------
// - copyright (c) 1999-2019 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Math.hpp"
#include "Boolean.hpp"
#include "Periodic.hpp"
#include "Runnable.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"

namespace afnix {

  // -------------------------------------------------------------------------
  // - private section                                                       -
  // -------------------------------------------------------------------------
    
  // the periodic table information descriptor
  struct s_pt_info {
    long d_bidx; // band gap index
  };

  // the total number of elements
  static const long PT_SIZE = 118;

  // the periodic table atomic properties - data from nist
  static const Periodic::s_atom PT_ATOM[PT_SIZE] = {
    {Periodic::ELEM_H,   "hydrogen",       "H",   1,   1.0079400, 13.5984},
    {Periodic::ELEM_HE,  "helium",        "HE",   2,   4.0026020, 24.5874},
    {Periodic::ELEM_LI,  "lithium",       "LI",   3,   6.9410000,  5.3917},
    {Periodic::ELEM_BE,  "beryllium",     "BE",   4,   9.0121820,  9.3227},
    {Periodic::ELEM_B,   "boron",          "B",   5,  10.8110000,  8.2980},
    {Periodic::ELEM_C,   "carbon",         "C",   6,  12.0107000, 11.2603},
    {Periodic::ELEM_N,   "nitrogen",       "N",   7,  14.0067000, 14.5341},
    {Periodic::ELEM_O,   "oxygen",         "O",   8,  15.9994000, 13.6181},
    {Periodic::ELEM_F,   "fluorine",       "F",   9,  18.9984032, 17.4228},
    {Periodic::ELEM_NE,  "neon",          "NE",  10,  20.1797000, 21.5645},
    {Periodic::ELEM_NA,  "sodium",        "NA",  11,  22.9897700,  5.1391},
    {Periodic::ELEM_MG,  "magnesium",     "MG",  12,  24.3050000,  7.6462},
    {Periodic::ELEM_AL,  "aluminium",     "AL",  13,  26.9815380,  5.9858},
    {Periodic::ELEM_SI,  "silicon",       "SI",  14,  28.0855000,  8.1517},
    {Periodic::ELEM_P,   "phosphorus",     "P",  15,  30.9737610, 10.4867},
    {Periodic::ELEM_S,   "sulfur",         "S",  16,  32.0650000, 10.3600},
    {Periodic::ELEM_CL,  "chlorine",      "CL",  17,  35.4530000, 12.9676},
    {Periodic::ELEM_AR,  "argon",         "AR",  18,  39.9480000, 15.7596},
    {Periodic::ELEM_K,   "potassium",      "K",  19,  39.0983000,  4.3407},
    {Periodic::ELEM_CA,  "calcium",       "CA",  20,  40.0780000,  6.1132},
    {Periodic::ELEM_SC,  "scandium",      "SC",  21,  44.9559100,  6.5615},
    {Periodic::ELEM_TI,  "titanium",      "TI",  22,  47.8670000,  6.8281},
    {Periodic::ELEM_V,   "vanadium",       "V",  23,  50.9415000,  6.7462},
    {Periodic::ELEM_CR,  "chromium",      "CR",  24,  51.9961000,  6.7665},
    {Periodic::ELEM_MN,  "manganese",     "MN",  25,  54.9380490,  7.4340},
    {Periodic::ELEM_FE,  "iron",          "FE",  26,  55.8450000,  7.9024},
    {Periodic::ELEM_CO,  "cobalt",        "CO",  27,  58.9332000,  7.8810},
    {Periodic::ELEM_NI,  "nickel",        "NI",  28,  58.6934000,  7.6398},
    {Periodic::ELEM_CU,  "copper",        "CU",  29,  63.5460000,  7.7264},
    {Periodic::ELEM_ZN,  "zinc",          "ZN",  30,  65.4090000,  9.3942},
    {Periodic::ELEM_GA,  "gallium",       "GA",  30,  69.7230000,  5.9993},
    {Periodic::ELEM_GE,  "germanium",     "GE",  32,  72.6400000,  7.8994},
    {Periodic::ELEM_AS,  "arsenic",       "AS",  33,  74.9216000,  9.7886},
    {Periodic::ELEM_SE,  "selenium",      "SE",  34,  78.9600000,  9.7524},
    {Periodic::ELEM_BR,  "bromine",       "BR",  35,  79.9040000, 11.8138},
    {Periodic::ELEM_KR,  "krypton",       "KR",  36,  83.7980000, 13.9996},
    {Periodic::ELEM_RB,  "rubidium",      "RB",  37,  85.4678000,  4.1771},
    {Periodic::ELEM_SR,  "strontium",     "SR",  38,  87.6200000,  5.6949},
    {Periodic::ELEM_Y,   "yttrium",        "Y",  39,  88.9058500,  6.2173},
    {Periodic::ELEM_ZR,  "zirconium",     "ZR",  40,  91.2240000,  6.6339},
    {Periodic::ELEM_NB,  "niobium",       "NB",  41,  92.9063800,  6.7589},
    {Periodic::ELEM_MO,  "molybdenum",    "MO",  42,  95.9400000,  7.0924},
    {Periodic::ELEM_TC,  "technetium",    "TC",  43,  98.0000000,  7.2800},
    {Periodic::ELEM_RU,  "ruthenium",     "RU",  44, 101.0700000,  7.3506},
    {Periodic::ELEM_RH,  "rhodium",       "RH",  45, 102.9055000,  7.4589},
    {Periodic::ELEM_PD,  "palladium",     "PD",  46, 106.4200000,  8.3369},
    {Periodic::ELEM_AG,  "silver",        "AG",  47, 107.8682000,  7.5762},
    {Periodic::ELEM_CD,  "cadmium",       "CD",  48, 112.4110000,  8.9938},
    {Periodic::ELEM_IN,  "indium",        "IN",  49, 114.8180000,  5.7864},
    {Periodic::ELEM_SN,  "tin",           "SN",  50, 118.7100000,  7.3439},
    {Periodic::ELEM_SB,  "antimony",      "SB",  51, 121.7600000,  8.6084},
    {Periodic::ELEM_TE,  "tellurium",     "TE",  52, 127.6000000,  9.0096},
    {Periodic::ELEM_I,   "iodine",         "I",  53, 126.9044700, 10.4513},
    {Periodic::ELEM_XE,  "xenon",         "XE",  54, 131.2930000, 12.1298},
    {Periodic::ELEM_CS,  "cesium",        "CS",  55, 132.9054500,  3.8939},
    {Periodic::ELEM_BA,  "barium",        "BA",  56, 137.3270000,  5.2117},
    {Periodic::ELEM_LA,  "lanthanum",     "LA",  57, 138.9055000,  5.5769},
    {Periodic::ELEM_CE,  "cerium",        "CE",  58, 140.1160000,  5.5387},
    {Periodic::ELEM_PR,  "praseodymium",  "PR",  59, 140.9076500,  5.4730},
    {Periodic::ELEM_ND,  "neodymium",     "ND",  60, 144.2400000,  5.5250},
    {Periodic::ELEM_PM,  "promethium",    "PM",  61, 145.0000000,  5.5820},
    {Periodic::ELEM_SM,  "samarium",      "SM",  62, 150.3600000,  5.6437},
    {Periodic::ELEM_EU,  "europium",      "EU",  63, 151.9640000,  5.6704},
    {Periodic::ELEM_GD,  "gadolinium",    "GD",  64, 157.2500000,  6.1498},
    {Periodic::ELEM_TB,  "terbium",       "TB",  65, 158.9253400,  5.8638},
    {Periodic::ELEM_DY,  "dysprosium",    "DY",  66, 162.5000000,  5.9389},
    {Periodic::ELEM_HO,  "holmium",       "HO",  67, 164.9303200,  6.0215},
    {Periodic::ELEM_ER,  "erbium",        "ER",  68, 167.2590000,  6.1077},
    {Periodic::ELEM_TM,  "thulium",       "TM",  69, 168.9342100,  6.1843},
    {Periodic::ELEM_YB,  "ytterbium",     "YB",  70, 173.0400000,  6.2542},
    {Periodic::ELEM_LU,  "lutetium",      "LU",  71, 174.9670000,  5.4259},
    {Periodic::ELEM_HF,  "hafnium",       "HF",  72, 178.4900000,  6.8251},
    {Periodic::ELEM_TA,  "tantalum",      "TA",  73, 180.9479000,  7.5496},
    {Periodic::ELEM_W,   "tungsten",       "W",  74, 183.8400000,  7.8640},
    {Periodic::ELEM_RE,  "rhenium",       "RE",  75, 186.2070000,  7.8335},
    {Periodic::ELEM_OS,  "osmium",        "OS",  76, 190.2300000,  8.4382},
    {Periodic::ELEM_IR,  "iridium",       "IR",  77, 192.2170000,  8.9670},
    {Periodic::ELEM_PT,  "platinum",      "PT",  78, 195.0780000,  8.9588},
    {Periodic::ELEM_AU,  "gold",          "AU",  79, 196.9665500,  9.2255},
    {Periodic::ELEM_HG,  "mercury",       "HG",  80, 200.5900000, 10.4375},
    {Periodic::ELEM_TL,  "thallium",      "TL",  81, 204.3833000,  6.1082},
    {Periodic::ELEM_PB,  "lead",          "PB",  82, 207.2000000,  7.4167},
    {Periodic::ELEM_BI,  "bismuth",       "BI",  83, 208.9803800,  7.2855},
    {Periodic::ELEM_PO,  "polonium",      "PO",  84, 209.0000000,  8.4140},
    {Periodic::ELEM_AT,  "astatine",      "AT",  85, 210.0000000, Math::M_NAN},
    {Periodic::ELEM_RN,  "radon",         "RN",  86, 222.0000000, 10.7485},
    {Periodic::ELEM_FR,  "francium",      "FR",  87, 223.0000000,  4.0727},
    {Periodic::ELEM_RA,  "radium",        "RA",  88, 226.0000000,  5.2784},
    {Periodic::ELEM_AC,  "actinium",      "AC",  89, 227.0000000,  5.1700},
    {Periodic::ELEM_TH,  "thorium",       "TH",  90, 232.0381000,  6.3067},
    {Periodic::ELEM_PA,  "protactinium",  "PA",  91, 231.0358800,  5.8900},
    {Periodic::ELEM_U,   "uranium",        "U",  92, 238.0289100,  6.1941},
    {Periodic::ELEM_NP,  "neptunium",     "NP",  93, 237.0000000,  6.2657},
    {Periodic::ELEM_PU,  "plutonium",     "PU",  94, 244.0000000,  6.0260},
    {Periodic::ELEM_AM,  "americium",     "AM",  95, 243.0000000,  5.9738},
    {Periodic::ELEM_CM,  "curium",        "CM",  96, 247.0000000,  5.9914},
    {Periodic::ELEM_BK,  "berkelium",     "BK",  97, 247.0000000,  6.1979},
    {Periodic::ELEM_CF,  "californium",   "CF",  98, 251.0000000,  6.2817},
    {Periodic::ELEM_ES,  "einsteinium",   "ES",  99, 252.0000000,  6.4200},
    {Periodic::ELEM_FM,  "fermium",       "FM", 100, 257.0000000,  6.5000},
    {Periodic::ELEM_MD,  "mendelevium",   "MD", 101, 258.0000000,  6.5800},
    {Periodic::ELEM_NO,  "nobelium",      "NO", 102, 259.0000000,  6.6500},
    {Periodic::ELEM_LR,  "lawrencium",    "LR", 103, 262.0000000,  4.9000},
    {Periodic::ELEM_RF,  "rutherfordium", "RF", 104, 261.0000000,  6.0000},
    {Periodic::ELEM_DB,  "dubnium",       "DB", 105, 262.0000000, Math::M_NAN},
    {Periodic::ELEM_SG,  "seaborgium",    "SG", 106, 266.0000000, Math::M_NAN},
    {Periodic::ELEM_BH,  "bohrium",       "BH", 107, 264.0000000, Math::M_NAN},
    {Periodic::ELEM_HS,  "hassium",       "HS", 108, 277.0000000, Math::M_NAN},
    {Periodic::ELEM_MT,  "meitnerium",    "MT", 109, 268.0000000, Math::M_NAN},
    {Periodic::ELEM_DS,  "darmstadtium",  "DS", 110, 281.0000000, Math::M_NAN},
    {Periodic::ELEM_RG,  "roentgenium",   "RG", 111, 272.0000000, Math::M_NAN},
    {Periodic::ELEM_CN,  "copernicium",   "CN", 112, 285.0000000, Math::M_NAN},
    {Periodic::ELEM_UUT, "ununtrium",    "UUT", 113, 284.0000000, Math::M_NAN},
    {Periodic::ELEM_UUQ, "ununquadium",  "UUQ", 114, 289.0000000, Math::M_NAN},
    {Periodic::ELEM_UUP, "ununpentium",  "UUP", 115, 288.0000000, Math::M_NAN},
    {Periodic::ELEM_UUH, "ununhexium",   "UUH", 116, 292.0000000, Math::M_NAN},
    {Periodic::ELEM_UUS, "ununseptium",  "UUS", 117, 293.0000000, Math::M_NAN},
    {Periodic::ELEM_UUO, "ununoctium",   "UUO", 118, 291.0000000, Math::M_NAN}
  };

  // the periodic table band gap properties
  static const Periodic::s_bgap PT_BGAP[] = {
    {1.17, 4.73E-4, 636.0}, // silicon
    {0.67, 4.56E-4, 210.0}  // germanium
  };

  // the periodic table atomic properties - data from nist
  static const s_pt_info PT_INFO[PT_SIZE] = {
    {-1}, //   1 - hydrogen
    {-1}, //   2 - helium
    {-1}, //   3 - lithium
    {-1}, //   4 - beryllium
    {-1}, //   5 - boron
    {-1}, //   6 - carbon
    {-1}, //   7 - nitrogen
    {-1}, //   8 - oxygen
    {-1}, //   9 - fluorine
    {-1}, //  10 - neon
    {-1}, //  11 - sodium
    {-1}, //  12 - magnesium
    {-1}, //  13 - aluminium
    { 0}, //  14 - silicon
    {-1}, //  15 - phosphorus
    {-1}, //  16 - sulfur
    {-1}, //  17 - chlorine
    {-1}, //  18 - argon
    {-1}, //  19 - potassium
    {-1}, //  20 - calcium
    {-1}, //  21 - scandium
    {-1}, //  22 - titanium
    {-1}, //  23 - vanadium
    {-1}, //  24 - chromium
    {-1}, //  25 - manganese
    {-1}, //  26 - iron
    {-1}, //  27 - cobalt
    {-1}, //  28 - nickel
    {-1}, //  29 - copper
    {-1}, //  30 - zinc
    {-1}, //  31 - gallium
    { 1}, //  32 - germanium
    {-1}, //  33 - arsenic
    {-1}, //  34 - selenium
    {-1}, //  35 - bromine
    {-1}, //  36 - krypton
    {-1}, //  37 - rubidium
    {-1}, //  38 - strontium
    {-1}, //  39 - yttrium
    {-1}, //  40 - zirconium
    {-1}, //  41 - niobium
    {-1}, //  42 - molybdenum
    {-1}, //  43 - technetium
    {-1}, //  44 - ruthenium
    {-1}, //  45 - rhodium
    {-1}, //  46 - palladium
    {-1}, //  47 - silver
    {-1}, //  58 - cadmium
    {-1}, //  49 - indium
    {-1}, //  50 - tin
    {-1}, //  51 - antimony
    {-1}, //  52 - tellurium
    {-1}, //  53 - iodine
    {-1}, //  54 - xenon
    {-1}, //  55 - cesium
    {-1}, //  56 - barium
    {-1}, //  57 - lanthanum
    {-1}, //  58 - cerium
    {-1}, //  59 - praseodymium
    {-1}, //  60 - neodymium
    {-1}, //  61 - promethium
    {-1}, //  62 - samarium
    {-1}, //  63 - europium
    {-1}, //  64 - gadolinium
    {-1}, //  65 - terbium
    {-1}, //  66 - dysprosium
    {-1}, //  67 - holmium
    {-1}, //  68 - erbium
    {-1}, //  69 - thulium
    {-1}, //  70 - ytterbium
    {-1}, //  71 - lutetium
    {-1}, //  72 - hafnium
    {-1}, //  73 - tantalum
    {-1}, //  74 - tungsten
    {-1}, //  75 - rhenium
    {-1}, //  76 - osmium
    {-1}, //  77 - iridium
    {-1}, //  78 - platinum
    {-1}, //  79 - gold
    {-1}, //  80 - mercury
    {-1}, //  81 - thallium
    {-1}, //  82 - lead
    {-1}, //  83 - bismuth
    {-1}, //  84 - polonium
    {-1}, //  85 - astatine
    {-1}, //  86 - radon
    {-1}, //  87 - francium
    {-1}, //  88 - radium
    {-1}, //  89 - actinium
    {-1}, //  90 - thorium
    {-1}, //  91 - protactinium
    {-1}, //  92 - uranium
    {-1}, //  93 - neptunium
    {-1}, //  94 - plutonium
    {-1}, //  95 - americium
    {-1}, //  96 - curium
    {-1}, //  97 - berkelium
    {-1}, //  98 - californium
    {-1}, //  99 - einsteinium
    {-1}, // 100 - fermium
    {-1}, // 101 - mendelevium
    {-1}, // 102 - nobelium
    {-1}, // 103 - lawrencium
    {-1}, // 104 - rutherfordium
    {-1}, // 105 - dubnium
    {-1}, // 106 - seaborgium
    {-1}, // 107 - bohrium
    {-1}, // 108 - hassium 
    {-1}, // 109 - meitnerium
    {-1}, // 110 - darmstadtium
    {-1}, // 111 - roentgenium
    {-1}, // 112 - copernicium
    {-1}, // 113 - ununtrium
    {-1}, // 114 - ununquadium
    {-1}, // 115 - ununpentium
    {-1}, // 116 - ununhexium
    {-1}, // 117 - ununseptium
    {-1}  // 118 - ununoctium
  };

  // this procedure find the element index by name
  static long pt_toeix (const String& name) {
    for (long i = 0; i < PT_SIZE; i++) {
      if (PT_ATOM[i].d_name == name) return i;
    }
    // not found
    return -1;
  }

  // this procedure find the element index by type
  static long pt_toeix (const Periodic::t_elem elem) {
    for (long i = 0; i < PT_SIZE; i++) {
      if (PT_ATOM[i].d_elem == elem) return i;
    }
    // not found
    return -1;
  }

  // -------------------------------------------------------------------------
  // - public section                                                         -
  // -------------------------------------------------------------------------

  // return true if an element exists by name

  bool Periodic::exists (const String& name) {
    return (pt_toeix (name) == -1) ? false : true;
  }

  // -------------------------------------------------------------------------
  // - class section                                                         -
  // -------------------------------------------------------------------------

  // create a periodic element by name

  Periodic::Periodic (const String& name) {
    // get the element by name
    long eix = pt_toeix (name);
    if (eix == -1) {
      throw Exception ("periodic-error", "cannot find element", name);
    }
    // set the element data
    d_atom = PT_ATOM[eix];
    // check for band gap information
    long bix = PT_INFO[eix].d_bidx;
    if (bix != -1) d_bgap = PT_BGAP[bix];
  }

  // create a periodic element by type

  Periodic::Periodic (const t_elem elem) {
    // get the element by name
    long eix = pt_toeix (elem);
    if (eix == -1) {
      throw Exception ("internal-error", "cannot find element by type");
    }
    // set the element data
    d_atom = PT_ATOM[eix];
    // check for band gap information
    long bix = PT_INFO[eix].d_bidx;
    if (bix != -1) d_bgap = PT_BGAP[bix];
  }

  // copy constructs this object

  Periodic::Periodic (const Periodic& that) {
    that.rdlock ();
    try {
      d_atom = that.d_atom;
      d_bgap = that.d_bgap;
      that.unlock ();
    } catch (...) {
      that.unlock ();
      throw;
    }
  }

  // assign an object to this one

  Periodic& Periodic::operator = (const Periodic& that) {
    // check for self assignation
    if (this == &that) return *this;
    // lock and assign
    wrlock ();
    that.rdlock ();
    try {
      d_atom = that.d_atom;
      d_bgap = that.d_bgap;
      unlock ();
      that.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      that.unlock ();
      throw;
    }
  }

  // return the class name

  String Periodic::repr (void) const {
    return "Periodic";
  }

  // return a clone of this object

  Object* Periodic::clone (void) const {
    return new Periodic (*this);
  }

  // get the atomic properties

  Periodic::s_atom Periodic::getatom (void) const {
    rdlock ();
    try {
      s_atom result = d_atom;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the band gap properties

  Periodic::s_bgap Periodic::getbgap (void) const {
    rdlock ();
    try {
      s_bgap result = d_bgap;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }


  // -------------------------------------------------------------------------
  // - object section                                                        -
  // -------------------------------------------------------------------------

  // the quark zone
  static const long QUARK_ZONE_LENGTH = 1;
  static QuarkZone  zone (QUARK_ZONE_LENGTH);

  // the object supported quarks
  static const long QUARK_EXISTP = zone.intern ("exists-p");

  // create a new object in a generic way

  Object* Periodic::mknew (Vector* argv) {
    long argc = (argv == nullptr) ? 0 : argv->length ();
    // check for 1 argument
    if (argc == 1) {
      String name = argv->getstring (0);
      return new Periodic (name);
    }
    // wrong arguments
    throw Exception ("argument-error", 
		     "too many arguments with periodic constructor");
  }

  // return true if the given quark is defined

  bool Periodic::isquark (const long quark, const bool hflg) const {
    rdlock ();
    if (zone.exists (quark) == true) {
      unlock ();
      return true;
    }
    bool result = hflg ? Object::isquark (quark, hflg) : false;
    unlock ();
    return result;
  }

  // apply this object with a set of arguments and a quark

  Object* Periodic::apply (Runnable* robj, Nameset* nset, const long quark,
			   Vector* argv) {
    // get the number of arguments
    long argc = (argv == nullptr) ? 0 : argv->length ();
    
    // dispatch 1 argument
    if (argc == 1) {
      if (quark == QUARK_EXISTP) {
	String name = argv->getstring (0);
	return new Boolean (exists (name));
      }
    }
    // call the object method
    return Object::apply (robj, nset, quark, argv);
  }
}
