/* Copyright (c) 2015  Gerald Knizia
 * 
 * This file is part of the IboView program (see: http://www.iboview.org)
 * 
 * IboView 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, version 3.
 * 
 * IboView 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 details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with bfint (LICENSE). If not, see http://www.gnu.org/licenses/
 * 
 * Please see IboView documentation in README.txt for:
 * -- A list of included external software and their licenses. The included
 *    external software's copyright is not touched by this agreement.
 * -- Notes on re-distribution and contributions to/further development of
 *    the IboView software
 */

#include "Iv.h"
#include "IvComputeWfForm.h"
#include "IvSettings.h"
#include "QPropertyModel.h"
#include "ui_ComputeWfForm.h"
#include "IvDocument.h"
#include "CtBasisSet.h"
#include "CtAtomSet.h"

extern "C" {
   size_t getMemorySize(); // memory_size.c
}

FComputeWfForm::FComputeWfForm(FDocument *document, QWidget *parent)
   : QDialog(parent),
     ui(new Ui::ComputeWfForm),
     m_pDocument(document),
     m_bMemoryOkay(true),
     m_bOtherError(false)
{
   ui->setupUi(this);
//    ui->label_WorkSpace->setVisible(false);
//    ui->spinBox_WorkSpacePerThread->setVisible(false);
   ui->spinBox_WorkSpacePerThread->setEnabled(false);

//    ui->tabWidget->layout()->setContentsMargins(0, 0, 0, 0); // left, top, right, bottom

//    m_pDocument->GetWfOptions()->setObjectName("WfOptions");
   LinkPropertyWidgets(m_pDocument->GetWfOptions(), this, "wf_option");
   connect(ui->checkBox_RunScf, SIGNAL(toggled(bool)), this, SLOT(ToggleScfPage(bool)));
   connect(ui->checkBox_RunIbba, SIGNAL(toggled(bool)), this, SLOT(ToggleIbbaPage(bool)));

   connect(ui->comboBox_ScfMethod, SIGNAL(currentIndexChanged(int)), this, SLOT(RecomputeMemory()));
   connect(ui->comboBox_ScfOrbitalBasis, SIGNAL(currentIndexChanged(int)), this, SLOT(RecomputeMemory()));
   connect(ui->comboBox_ScfFitBasis, SIGNAL(currentIndexChanged(int)), this, SLOT(RecomputeMemory()));
   connect(ui->spinBox_NumThreads, SIGNAL(valueChanged(int)), this, SLOT(RecomputeMemory()));
   connect(ui->spinBox_WorkSpacePerThread, SIGNAL(valueChanged(int)), this, SLOT(RecomputeMemory()));
   connect(ui->spinBox_WfCharge, SIGNAL(valueChanged(int)), this, SLOT(RecomputeMemory()));
   connect(ui->spinBox_WfSpin, SIGNAL(valueChanged(int)), this, SLOT(RecomputeMemory()));
   RecomputeMemory();

   CheckEcps();

   // if there already is electronic structure stuff, don't make new IBOs by default, unless
   // the user actually asked for it explicitly.
   if (m_pDocument->GetCurrentFrame() && m_pDocument->GetCurrentFrame()->HaveOrbitals()) {
      ui->checkBox_RunScf->setChecked(false);
   }

   ui->buttonBox->setFocus();

   if (!IvRestoreWindowSize("ComputeWindow/Size", this))
      IvGuessSubDialogSize(this);
}

// void FComputeWfForm::closeEvent(QCloseEvent *event)
// {
//    IvSaveWindowSize("ComputeWindow/Size", this);
//    return QDialog::closeEvent(event);
// }


FComputeWfForm::~FComputeWfForm()
{
   IvSaveWindowSize("ComputeWindow/Size", this);
   delete ui;
}

void FComputeWfForm::ToggleScfPage(bool Checked)
{
   ui->page_WfSetup->setEnabled(Checked);
   if (Checked)
      ui->tabWidget->setCurrentIndex(0);
   else
      ui->tabWidget->setCurrentIndex(1);
}

void FComputeWfForm::ToggleIbbaPage(bool Checked)
{
   ui->page_IbbaSetup->setEnabled(Checked);
   if (Checked)
      ui->tabWidget->setCurrentIndex(1);
}

void FComputeWfForm::SetMemoryText(QString s, FStatusClass Class)
{
   ui->label_MemoryGuess->setStyleSheet(QString("QLabel{padding: .2ex; font-size: 12pt; font-weight: bold; %1} QLabel::disabled{background: #444; color:#aaa}").arg(GetStatusStyle(Class)));
   ui->label_MemoryGuess->setText(s);
}


void FComputeWfForm::RecomputeMemory()
{
   m_bMemoryOkay = true;
   m_bOtherError = true; // will be cleared at end of 'try{..}'
   try {
      FFrame
         *pFrame = m_pDocument->GetCurrentFrame();
      ct::FAtomSet
         *pOrigAtoms = 0;
      if (pFrame)
         pOrigAtoms = pFrame->pGetAtoms();
      if (pOrigAtoms == 0)
         return SetMemoryText("[no geometry]", STATUS_Confused);
      // make a copy of the atom set and set the current bases.
      ct::FAtomSet
         Atoms(*pOrigAtoms);
      FWfOptions
         *pWfOptions = m_pDocument->GetWfOptions();
      pWfOptions->AssignBasisSets(&Atoms);

      // currently open-shell is not supported for KS... check number of electrons
      // and extra spin. Should be fixed soon.
      int
         nElecTotal = (Atoms.NuclearCharge() - pWfOptions->GetCharge());
//       IvEmit("!!nElecTotal = %1", nElecTotal);
      if (nElecTotal < 0)
         return SetMemoryText("[nElec < 0]", STATUS_Confused);
      if (pWfOptions->GetExtraSpin() != 0 || (unsigned(nElecTotal) % 2) != 0)
         return SetMemoryText("[open-shell not working]", STATUS_Confused);

      // instanciate the fitting sets to see how large they are.
      ct::FBasisSet
         OrbBasis(Atoms, ct::BASIS_Orbital),
         FitBasis(Atoms, ct::BASIS_JkFit);
      size_t
         nAo = OrbBasis.nFn(),
         nFit = FitBasis.nFn();
//       IvEmit("Basis sizes: nAo = %1  nFit = %2", nAo, nFit);

      // that is the estimate for the fully caching small-molecule 3ix DF-RKS.
      size_t
         nThreads = (size_t)pWfOptions->GetNumThreads(),
         nWorkSpaceShared = sizeof(double) * (8*nAo*nAo + nFit*nFit),
         nMemEst = nWorkSpaceShared + sizeof(double) * (nFit*((nAo*(nAo+1))/2)),
//          nMemEst = sizeof(double) * (8*nAo*nAo + nFit*nFit + nFit*((nAo*(nAo+1))/2)),
         nSysMem = getMemorySize(); // amount of physical memory on the system.

      size_t
         nMinWorkSpacePerThread = size_t(20)<<20; // 20 MB
      nMinWorkSpacePerThread += sizeof(double)*(std::max(size_t(2), size_t(FitBasis.nFnOfLargestShell())) * nAo*nAo);
      // ^- 2 fock matrices per thread in AccXc and nFnOfLargestShell() for FormIntMNF
      //    as intermediate while building the cached integrals. The latter could certainly
      //    be reduced if I find some time. But for the moment this will have to work...
      nMemEst += nThreads * nMinWorkSpacePerThread;
      pWfOptions->SetWorkSpaceMb((nMinWorkSpacePerThread + nWorkSpaceShared/nThreads)>>20);

//       nMemEst += size_t(pWfOptions->GetNumThreads()) * (size_t(pWfOptions->GetWorkSpaceMb()) << 20);

//       nSysMem = size_t(8) << 30;

      if (nSysMem == 0)
         nSysMem = size_t(8) << 30; // assume 8 GB.

//       IvEmit("This system has %1 GB of memory. Does it?", double(nSysMem)/double(1<<30));
      double
         f = (double(nMemEst) + double(size_t(2)<<30)) / double(nSysMem);
      // convert to MB
      nMemEst >>= 20;
      FStatusClass
         Class = STATUS_Confused;
      if (f < 0.3)
         Class = STATUS_Idle;
      else if (f < 0.8)
         Class = STATUS_Warning;
      else {
         Class = STATUS_Error;
         m_bMemoryOkay = false;
      }
      SetMemoryText(QString("%1 MB").arg((int)nMemEst), Class);
      m_bOtherError = false;

   } catch (std::runtime_error &e) {
      SetMemoryText("[failed to load basis set]", STATUS_Confused);
      m_bOtherError = true;
   }
}

void FComputeWfForm::CheckEcps()
{
   try {
      FFrame
         *pFrame = m_pDocument->GetCurrentFrame();
      ct::FAtomSet
         *pOrigAtoms = 0;
      if (pFrame)
         pOrigAtoms = pFrame->pGetAtoms();
      if (pOrigAtoms == 0)
         return SetMemoryText("[no geometry]", STATUS_Confused);
      bool NeedsEcps = false;
      for (size_t iAt = 0; iAt < pOrigAtoms->size(); ++ iAt) {
         if ((*pOrigAtoms)[iAt].AtomicNumber > 36)
            NeedsEcps = true;
      }

      if (NeedsEcps) {
         ui->textBrowser_WfNotes->setText("<div style=\"font-size: 11pt; color:#ccc\"><p><div style=\"font-size: 13pt; color:white\">"
            "Sorry, MicroScf currently cannot do this calculation :(.</div> "
            "It can only compute Kohn-Sham wave functions up to element 36 (Kr), due to "
            "a lack of ECP integrals.</p>"
            "<p>Note: Chemical analysis of an imported wave function should still work.</p></div>");
         ui->checkBox_RunScf->setChecked(false);
         ui->checkBox_RunScf->setEnabled(false);
      }
   } catch (std::runtime_error &e) {
      ui->textBrowser_WfNotes->setText("[failed to instanciate FAtomSet]");
   }
}





bool FComputeWfForm::GetRunScf() const
{
   return ui->checkBox_RunScf->isChecked();
}

bool FComputeWfForm::GetRunIbba() const
{
   return ui->checkBox_RunIbba->isChecked();
}

bool FComputeWfForm::IsMemoryOkay() const
{
   return m_bMemoryOkay;
}
