// ---------------------------------------------------------------------------
// - 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-2012 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 total number of elements
  static const long PT_SIZE = 86;

  // 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}
  };

  // the periodic table band gap properties
  static const Periodic::s_bgap PT_BGAP[PT_SIZE] = {
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // hydrogen
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // helium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // lithium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // beryllium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // boron
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // carbon
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // nitrogen
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // oxygen
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // fluorine
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // neon
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // sodium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // magnesium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // aluminium
    {1.16,            7.04E-4,      1108.0}, // silicon
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // phosphorus
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // sulfur
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // chlorine
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // argon
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // potassium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // calcium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // scandium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // titanium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // vanadium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // chromium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // manganese
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // iron
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // cobalt
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // nickel
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // copper
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // zinc
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // gallium
    {0.67,            4.56E-4,       210.0}, // germanium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // arsenic
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // selenium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // bromine
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // krypton
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // rubidium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // strontium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // yttrium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // zirconium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // niobium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // molybdenum
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // technetium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // ruthenium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // rhodium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // palladium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // silver
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // cadmium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // indium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // tin
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // antimony
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // tellurium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // iodine
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // xenon
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // cesium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // barium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // lanathanum
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // cerium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // praseodymium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // neodymium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // promethium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // samarium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // europium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // gadolinium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // terbium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // dysprosium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // holmium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // erbium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // thulium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // ytterbium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // lutetium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // hafnium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // tantalum
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // tungsten
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // rhenium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // osmium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // iridium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // platinum
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // gold
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // mercury
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // thallium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // lead
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // bismuth
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // polonium
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}, // astatine
    {Math::M_NAN, Math::M_NAN, Math::M_NAN}  // radon
  };

  // this procedure find the element index by name
  static long pt_enmtoeix (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_etptoeix (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_enmtoeix (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_enmtoeix (name);
    if (eix == -1) {
      throw Exception ("periodic-error", "cannot find element", name);
    }
    d_atom = PT_ATOM[eix];
    d_bgap = PT_BGAP[eix];
  }

  // create a periodic element by type

  Periodic::Periodic (const t_elem elem) {
    // get the element by name
    long eix = pt_etptoeix (elem);
    if (eix == -1) {
      throw Exception ("internal-error", "cannot find element by type");
    }
    d_atom = PT_ATOM[eix];
    d_bgap = PT_BGAP[eix];
  }

  // 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 == nilp) ? 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 == nilp) ? 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);
  }
}
