/*
 * Project: MoleCuilder
 * Description: creates and alters molecular systems
 * Copyright (C)  2010-2012 University of Bonn. All rights reserved.
 * Copyright (C)  2013 Frederik Heber. All rights reserved.
 * 
 *
 *   This file is part of MoleCuilder.
 *
 *    MoleCuilder 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.
 *
 *    MoleCuilder 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 MoleCuilder.  If not, see .
 */
/*
 * ParserPsi3UnitTest.cpp
 *
 *  Created on: Mar 3, 2010
 *      Author: metzler
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
#include "ParserPsi3UnitTest.hpp"
#include 
#include 
#include 
#include 
#include "CodePatterns/Log.hpp"
#include "Atom/atom.hpp"
#include "Atom/AtomObserver.hpp"
#include "Element/element.hpp"
#include "Element/periodentafel.hpp"
#include "CodePatterns/Assert.hpp"
#include "Descriptors/AtomTypeDescriptor.hpp"
#include "Parser/ChangeTracker.hpp"
#include "Parser/Psi3Parser.hpp"
#include "World.hpp"
#ifdef HAVE_TESTRUNNER
#include "UnitTestMain.hpp"
#endif /*HAVE_TESTRUNNER*/
using namespace std;
// Registers the fixture into the 'registry'
CPPUNIT_TEST_SUITE_REGISTRATION( ParserPsi3UnitTest );
static string hydrogenPsi3_RHF ="% Created by MoleCuilder\n\
psi: (\n\
\tlabel = \"cc-pVTZ SCF H2\"\n\
\tjobtype = sp\n\
\twfn = scf\n\
\tmaxiter = 80\n\
\treference = rhf\n\
\tbasis = \"cc-pVTZ\"\n\
\tfreeze_core = no\n\
\tunits = angstrom\n\
\tgeometry = (\n\
\t( H\t0\t0\t-0.37 )\n\
\t( H\t0\t0\t0.37 )\n\
\t)\n\
\torigin = (0.0\t0.0\t0.0)\n\
)\n"; // tested with ???
static string hydrogenPsi3_ROHF ="% Created by MoleCuilder\n\
psi: (\n\
\tlabel = \"cc-pVTZ SCF H2\"\n\
\tjobtype = sp\n\
\twfn = scf\n\
\tmaxiter = 80\n\
\treference = rohf\n\
\tbasis = \"cc-pVTZ\"\n\
\tfreeze_core = no\n\
\tunits = angstrom\n\
\tgeometry = (\n\
\t( H\t0\t0\t-0.37 )\n\
\t( H\t0\t0\t0.37 )\n\
\t)\n\
\torigin = (0.0\t0.0\t0.0)\n\
)\n"; // tested with ???
static string hydrogenPsi3_UHF ="% Created by MoleCuilder\n\
psi: (\n\
\tlabel = \"cc-pVTZ SCF H2\"\n\
\tjobtype = sp\n\
\twfn = scf\n\
\tmaxiter = 80\n\
\treference = uhf\n\
\tbasis = \"cc-pVTZ\"\n\
\tmultp = 3\n\
\tcharge = 2\n\
\tfreeze_core = no\n\
\tunits = angstrom\n\
\tgeometry = (\n\
\t( H\t0\t0\t-0.37 )\n\
\t( H\t0\t0\t0.37 )\n\
\t)\n\
\torigin = (0.0\t0.0\t0.0)\n\
)\n"; // tested with ???
static string hydrogenPsi3_TWOCON ="% Created by MoleCuilder\n\
psi: (\n\
\tlabel = \"cc-pVTZ SCF H2\"\n\
\tjobtype = sp\n\
\twfn = scf\n\
\tmaxiter = 80\n\
\treference = twocon\n\
\tbasis = \"cc-pVTZ\"\n\
\tmultp = 3\n\
\tcharge = 2\n\
\tsocc = ( 1 1 0 0 0 0 0 0 )\n\
\tdocc = ( 0 0 0 0 0 0 0 0 )\n\
\tsubgroup = c2v\n\
\tunique_axis = x\n\
\tfreeze_core = no\n\
\tunits = angstrom\n\
\tgeometry = (\n\
\t( H\t0\t0\t-0.37 )\n\
\t( H\t0\t0\t0.37 )\n\
\t)\n\
\torigin = (0.0\t0.0\t0.0)\n\
)\n"; // tested with ???
void ParserPsi3UnitTest::setUp()
{
  // failing asserts should be thrown
  ASSERT_DO(Assert::Throw);
  parser = new FormatParser();
  params = &parser->getParams();
  World::getInstance();
  setVerbosity(2);
  // we need hydrogens and oxygens in the following tests
  CPPUNIT_ASSERT(World::getInstance().getPeriode()->FindElement(1) != NULL);
}
void ParserPsi3UnitTest::tearDown()
{
  params = NULL;
  delete parser;
  ChangeTracker::purgeInstance();
  World::purgeInstance();
  AtomObserver::purgeInstance();
}
/************************************ tests ***********************************/
void ParserPsi3UnitTest::ParameterDefaultTest() {
  // check default values
  CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::labelParam) == std::string("unknown job"));
  CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::jobtypeParam) == params->ValidJobtypes[Psi3Parser_Parameters::SP]);
  CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::wavefunctionParam) == params->ValidWavefunction[Psi3Parser_Parameters::SCF]);
  CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::maxiterParam) == std::string("80"));
  CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::referenceParam) == params->ValidReference[Psi3Parser_Parameters::RHF]);
  CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::basisParam) == std::string("cc-pVTZ"));
  CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::freeze_coreParam) == params->ValidFreezeCore[Psi3Parser_Parameters::YES]);
  CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::unitsParam) == params->ValidUnits[Psi3Parser_Parameters::angstrom]);
  CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::dertypeParam) == params->ValidDerivativeType[Psi3Parser_Parameters::NONE]);
  CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::originParam) == std::string("(0.0\t0.0\t0.0)"));
  CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::multiplicityParam) == std::string("1"));
  CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::chargeParam) == std::string("0"));
  CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::soccParam) == std::string("()"));
  CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::doccParam) == std::string("()"));
  CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::subgroupParam) == std::string(""));
  CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::unique_axisParam) == params->ValidUniqueAxis[Psi3Parser_Parameters::X]);
}
void ParserPsi3UnitTest::ParameterCloneTest() {
  FormatParser_Parameters *clone = params->clone();
  CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::jobtypeParam) == params->ValidJobtypes[Psi3Parser_Parameters::SP]);
  std::stringstream setvalue(params->ParamNames[Psi3Parser_Parameters::jobtypeParam]+"="+params->ValidJobtypes[Psi3Parser_Parameters::OPT]);
  setvalue >> *params;
  CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::jobtypeParam) == params->ValidJobtypes[Psi3Parser_Parameters::OPT]);
  params->makeClone(*clone);
  CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::jobtypeParam) == params->ValidJobtypes[Psi3Parser_Parameters::SP]);
}
void ParserPsi3UnitTest::ParameterSetterTest() {
  // test a jobtype
  {
    std::stringstream setvalue(
        params->ParamNames[Psi3Parser_Parameters::jobtypeParam]
        +" = "+params->ValidJobtypes[Psi3Parser_Parameters::OPT]
    );
    setvalue >> *params;
    CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::jobtypeParam) == params->ValidJobtypes[Psi3Parser_Parameters::OPT]);
  }
  // test a wavefunction
  {
    std::stringstream setvalue(
        params->ParamNames[Psi3Parser_Parameters::wavefunctionParam]
        +" = "+params->ValidWavefunction[Psi3Parser_Parameters::MP2]
    );
    setvalue >> *params;
    CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::wavefunctionParam) == params->ValidWavefunction[Psi3Parser_Parameters::MP2]);
  }
  // test a reference
  {
    std::stringstream setvalue(
        params->ParamNames[Psi3Parser_Parameters::referenceParam]
        +" = "+params->ValidReference[Psi3Parser_Parameters::ROHF]
    );
    setvalue >> *params;
    CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::referenceParam) == params->ValidReference[Psi3Parser_Parameters::ROHF]);
  }
  // test a unique_axis
  {
    std::stringstream setvalue(
        params->ParamNames[Psi3Parser_Parameters::unique_axisParam]
        +" = "+params->ValidUniqueAxis[Psi3Parser_Parameters::Y]
    );
    setvalue >> *params;
    CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::unique_axisParam) == params->ValidUniqueAxis[Psi3Parser_Parameters::Y]);
  }
  // test a units
  {
    std::stringstream setvalue(
        params->ParamNames[Psi3Parser_Parameters::unitsParam]
        +" = "+params->ValidUnits[Psi3Parser_Parameters::bohr]
    );
    setvalue >> *params;
    CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::unitsParam) == params->ValidUnits[Psi3Parser_Parameters::bohr]);
  }
  // test a dertype
  {
    std::stringstream setvalue(
        params->ParamNames[Psi3Parser_Parameters::dertypeParam]
        +" = "+params->ValidDerivativeType[Psi3Parser_Parameters::NONE]
    );
    setvalue >> *params;
    CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::dertypeParam) == params->ValidDerivativeType[Psi3Parser_Parameters::NONE]);
  }
  // test a freeze_core
  {
    std::stringstream setvalue(
        params->ParamNames[Psi3Parser_Parameters::freeze_coreParam]
        +" = "+params->ValidFreezeCore[Psi3Parser_Parameters::LARGE]
    );
    setvalue >> *params;
    CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::freeze_coreParam) == params->ValidFreezeCore[Psi3Parser_Parameters::LARGE]);
  }
  // test int
  {
    std::stringstream setvalue(
        params->ParamNames[Psi3Parser_Parameters::maxiterParam]
        +" = 500"
    );
    setvalue >> *params;
//    std::cout << "maxiter is "
//        << params->getString(Psi3Parser_Parameters::maxiterParam) << std::endl;
    CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::maxiterParam) == std::string("500"));
  }
  // test whether unknown key fails
  std::cout << "The following Assertion warning is desired and does not indicate a failure of the test." << std::endl;
  {
    std::stringstream setvalue("sd = no");
#ifndef NDEBUG
    ASSERT_DO(Assert::Throw);
    CPPUNIT_ASSERT_THROW(setvalue >> *params, Assert::AssertionFailure);
#else
    setvalue >> *params;
#endif
//    std::cout << "Hessian is still "
//        << params->getString(Psi3Parser_Parameters::hessianParam) << std::endl;
    CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::jobtypeParam) == params->ValidJobtypes[Psi3Parser_Parameters::OPT]);
  }
}
void ParserPsi3UnitTest::readPsi3Test() {
  stringstream input(hydrogenPsi3_RHF);
  // set some other parameter for jobtype
  params->setParameter(
      Psi3Parser_Parameters::jobtypeParam,
      params->ValidJobtypes[Psi3Parser_Parameters::OPT]
          );
  parser->load(&input);
  // check for jobtype from file
  CPPUNIT_ASSERT(params->getParameter(Psi3Parser_Parameters::jobtypeParam) == params->ValidJobtypes[Psi3Parser_Parameters::SP]);
  // check for 2 hydrogens
  CPPUNIT_ASSERT_EQUAL(2, World::getInstance().numAtoms());
  // check that positions are right
  Vector PositionSum;
  std::vector atoms = const_cast(World::getInstance()).
      getAllAtoms();
  for (std::vector::const_iterator iter = atoms.begin();
      iter != atoms.end();
      ++iter)
    PositionSum += (*iter)->getPosition();
  CPPUNIT_ASSERT_EQUAL( PositionSum, Vector(0.,0.,0.) );
}
void ParserPsi3UnitTest::writePsi3Test() {
  // build up hydrogen molecule
  string first;
  string second;
  atom *Walker = NULL;
  Walker = World::getInstance().createAtom();
  Walker->setType(1);
  Walker->setPosition(Vector(0.,0.,0.));
  Walker = World::getInstance().createAtom();
  Walker->setType(1);
  Walker->setPosition(Vector(0.,0,0.74));
  CPPUNIT_ASSERT_EQUAL(2, World::getInstance().numAtoms());
  // set general parameters: label and freeze_core
  params->setParameter(
      Psi3Parser_Parameters::labelParam,
      "cc-pVTZ SCF H2"
          );
  params->setParameter(
      Psi3Parser_Parameters::freeze_coreParam,
      params->ValidFreezeCore[Psi3Parser_Parameters::NO]
          );
  params->setParameter(
      Psi3Parser_Parameters::unitsParam,
      params->ValidUnits[Psi3Parser_Parameters::angstrom]
          );
  // create two stringstreams, one stored, one created
  std::vector atoms = const_cast(World::getInstance()).
      getAllAtoms();
  {
    // compare both configs for RHF
    stringstream output;
    params->setParameter(
        Psi3Parser_Parameters::referenceParam,
        params->ValidReference[Psi3Parser_Parameters::RHF]
            );
    parser->save(&output, atoms);
    stringstream input(hydrogenPsi3_RHF);
    // check for non-empty streams
    input.peek();
    output.peek();
    CPPUNIT_ASSERT(input.good() && output.good());
    // check equality of streams per line (for debugging)
    for (; std::getline(input, first) && std::getline(output, second); ) {
      //std::cout << "Comparing '" << first << "' to '" << second << "'" << std::endl;
      CPPUNIT_ASSERT(first == second);
    }
  }
  {
    // compare both configs for ROHF
    stringstream output;
    params->setParameter(
        Psi3Parser_Parameters::referenceParam,
        params->ValidReference[Psi3Parser_Parameters::ROHF]
            );
    parser->save(&output, atoms);
    stringstream input(hydrogenPsi3_ROHF);
    // check for non-empty streams
    input.peek();
    output.peek();
    CPPUNIT_ASSERT(input.good() && output.good());
    // check equality of streams per line (for debugging)
    for (; std::getline(input, first) && std::getline(output, second); ) {
      //std::cout << "Comparing '" << first << "' to '" << second << "'" << std::endl;
      CPPUNIT_ASSERT(first == second);
    }
  }
  {
    params->setParameter(Psi3Parser_Parameters::multiplicityParam, "2");
    params->setParameter(Psi3Parser_Parameters::chargeParam, "2");
    // compare both configs for UHF
    stringstream output;
    params->setParameter(
        Psi3Parser_Parameters::referenceParam,
        params->ValidReference[Psi3Parser_Parameters::UHF]
            );
    parser->save(&output, atoms);
    params->setParameter(Psi3Parser_Parameters::chargeParam, "0");
    params->setParameter(Psi3Parser_Parameters::multiplicityParam, "1");
    stringstream input(hydrogenPsi3_UHF);
    // check for non-empty streams
    input.peek();
    output.peek();
    CPPUNIT_ASSERT(input.good() && output.good());
    // check equality of streams per line (for debugging)
    for (; std::getline(input, first) && std::getline(output, second); ) {
      //std::cout << "Comparing '" << first << "' to '" << second << "'" << std::endl;
      CPPUNIT_ASSERT(first == second);
    }
  }
  {
    params->setParameter(Psi3Parser_Parameters::multiplicityParam, "1");
    params->setParameter(Psi3Parser_Parameters::chargeParam, "2");
    params->setParameter(Psi3Parser_Parameters::soccParam, "( 1 1 0 0 0 0 0 0 )");
    params->setParameter(Psi3Parser_Parameters::doccParam, "( 0 0 0 0 0 0 0 0 )");
    params->setParameter(Psi3Parser_Parameters::subgroupParam, "c2v");
    params->setParameter(Psi3Parser_Parameters::unique_axisParam, "x");
    // compare both configs for TWOCON
    stringstream output;
    params->setParameter(
        Psi3Parser_Parameters::referenceParam,
        params->ValidReference[Psi3Parser_Parameters::TWOCON]
            );
    parser->save(&output, atoms);
    params->setParameter(Psi3Parser_Parameters::multiplicityParam, "1");
    params->setParameter(Psi3Parser_Parameters::chargeParam, "0");
    stringstream input(hydrogenPsi3_TWOCON);
    // check for non-empty streams
    input.peek();
    output.peek();
    CPPUNIT_ASSERT(input.good() && output.good());
    // check equality of streams per line (for debugging)
    for (; std::getline(input, first) && std::getline(output, second); ) {
      //std::cout << "Comparing '" << first << "' to '" << second << "'" << std::endl;
      CPPUNIT_ASSERT(first == second);
    }
  }
}