/******************************************************************************\

                     This file is part of the FAHViewer.

           The FAHViewer displays 3D views of Folding@home proteins.
                 Copyright (c) 2003-2013, Stanford University
                             All rights reserved.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    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.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

                For information regarding this software email:
                               Joseph Coffland
                        joseph@cauldrondevelopment.com

\******************************************************************************/

#include "Atom.h"

#include <cbang/pyon/List.h>

#include <cbang/Exception.h>
#include <cbang/String.h>

#include <cctype>

using namespace std;
using namespace cb;
using namespace FAH;


Atom::Atom(const string &type, float charge, float radius, float mass) :
  type(type), number(numberFromName(type)), charge(charge), radius(radius),
  mass(mass), index(0) {
  if (!charge) this->charge = chargeFromNumber(number);
  if (radius <= 0) this->radius = radiusFromNumber(number);
  if (mass <= 0) this->mass = massFromNumber(number);
}


float Atom::chargeFromNumber(unsigned number) {
  switch (number) {
  case HYDROGEN: return  0.370; break;
  case CARBON: return -0.119; break;
  case NITROGEN: return -0.333; break;
  case OXYGEN: return -0.380; break;
  case SULFUR: return -0.470; break;
  default:  return  0.370; break; // ???
  }
}


float Atom::radiusFromNumber(unsigned number) {
  switch (number) {
  case HYDROGEN: return 1.09; break;
  case CARBON: return 1.70; break;
  case NITROGEN: return 1.55; break;
  case OXYGEN: return 1.52; break;
  case SULFUR: return 1.80; break;
  default:  return 1.00; break; // ???
  }
}


float Atom::massFromNumber(unsigned number) {
  // TODO This isn't quite right
  switch (number) {
  case HYDROGEN: return 1.00794;
  case CARBON: return 12.0107;
  case NITROGEN: return 14.0067;
  case OXYGEN: return 15.9994;
  case SULFUR: return 32.0650;
  default: return 100; // Assume a heavy atom
  }
}


void Atom::setDataFromNumber(unsigned number) {
  if (!number) THROW("Atom number cannot be zero");

  charge = chargeFromNumber(number);
  radius = radiusFromNumber(number);
  mass = massFromNumber(number);
}

unsigned Atom::numberFromName(const string &name) {
  if (name.empty()) THROWS("Atom name cannot be empty");

  switch (toupper(name[0])) {
  case 'H': return HYDROGEN;
  case 'C': return CARBON;
  case 'N': return NITROGEN;
  case 'O': return OXYGEN;
  case 'S': return SULFUR;
  default:
    if (1 < name.length()) return numberFromName(name.substr(1));
    return HEAVY;
  }
}


static int numberToIndex(char number) {
  switch (number) {
  case Atom::HYDROGEN: return 0;
  case Atom::CARBON:   return 1;
  case Atom::NITROGEN: return 2;
  case Atom::OXYGEN:   return 3;
  case Atom::SULFUR:   return 4;
  default: return -1;
  }
}


double Atom::averageBondLength(const Atom &atom) const {
  // See: http://www.wiredchemist.com/chemistry/data/bond_energies_lengths.html
  const double table[][5] = {
    // H     C     N     O     S
    {0.74, 1.09, 1.01, 0.96, 1.34}, // H
    {1.09, 1.54, 1.47, 1.43, 1.82}, // C
    {1.01, 1.47, 1.45, 1.40, 1.43}, // N
    {0.96, 1.43, 1.40, 1.48, 1.43}, // O
    {1.34, 1.82, 1.43, 1.43, 1.49}, // S
  };

  int i = numberToIndex(number);
  int j = numberToIndex(atom.number);

  if (i == -1 || j == -1) return 2; // a wild guess

  return table[i][j];
}


SmartPointer<PyON::Value> Atom::getPyON() const {
  SmartPointer<PyON::List> list = new PyON::List(true);

  list->append(type);
  list->append(charge);
  list->append(radius);
  list->append(mass);
  list->append(number);

  return list;
}


void Atom::loadPyON(const PyON::Value &value) {
  const PyON::List &list = value.getList();

  if (0 < list.size()) type = list[0]->getString();
  else THROWS("Atom expected list of at least length 1");
  if (1 < list.size()) charge = (float)list[1]->getNumber();
  if (2 < list.size()) radius = (float)list[2]->getNumber();
  if (3 < list.size()) mass = (float)list[3]->getNumber();
  if (4 < list.size()) number = (unsigned)list[4]->getNumber();
  else number = numberFromName(type);
}
