/**********************************************************************
opendxformat.cpp - Read in OpenDX cube format files from APBS

 Copyright (c) 2007-2008 by Geoffrey R. Hutchison
 Some Portions Copyright (C) 2008 by Marcus D. Hanwell

This file is part of the Open Babel project.
For more information, see <http://openbabel.sourceforge.net/>

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.
***********************************************************************/

#include <sstream>

#include <openbabel/babelconfig.h>
#include <openbabel/obmolecformat.h>
#include <openbabel/griddata.h>

using namespace std;

namespace OpenBabel
{

  // Ultimately, this should just read in an OBFloatGrid, rather than a "molecule"
  // since there's no actual molecular data. For now, we'll read in an OBMol with no atoms
  // and attach an OBGridData
  class OBOpenDXCubeFormat : public OpenBabel::OBMoleculeFormat
  {
  public:
    // Constructor: register "dx" extension code
    OBOpenDXCubeFormat()
    {
        OpenBabel::OBConversion::RegisterFormat( "dx", this );
    }

    // Return description.
    virtual const char* Description() //required
    {
        return
        "OpenDX cube format for APBS\n"
          "Read Only.\n";
    }

    // Return a specification url, not really a specification since
    // I couldn't find it but close enough.
    virtual const char* SpecificationURL()
    {
        return "http://apbs.sourceforge.net/doc/user-guide/index.html#id504516";
    }

    // Return MIME type, NULL in this case.
    virtual const char* GetMIMEType() { return 0; };

    // Skip to object: used for multi-object file formats.
    virtual int SkipObjects( int n, OpenBabel::OBConversion* pConv ) { return 0; }

    virtual unsigned int Flags()
    {
      return READONEONLY | WRITEONEONLY | ZEROATOMSOK;
    };

    /// The "API" interface functions
    virtual bool ReadMolecule( OpenBabel::OBBase* pOb, OpenBabel::OBConversion* pConv );
    virtual bool WriteMolecule( OpenBabel::OBBase* pOb, OpenBabel::OBConversion* pConv );

};

//------------------------------------------------------------------------------

    // Global variable used to register Gaussian cube format.
    OBOpenDXCubeFormat theGaussianCubeFormat;

//------------------------------------------------------------------------------
bool OBOpenDXCubeFormat::ReadMolecule( OBBase* pOb, OBConversion* pConv )
{
    OBMol* pmol = pOb->CastAndClear<OBMol>();
    if(pmol == 0)
      return false;

    istream& ifs = *pConv->GetInStream();

    const char* title = pConv->GetTitle();
    char buffer[BUFF_SIZE];

    stringstream errorMsg;

    if (!ifs)
      return false; // We are attempting to read past the end of the file

    pmol->SetTitle(title);

    while (ifs.good() && ifs.getline(buffer,BUFF_SIZE)) {
      if (buffer[0] == '#')
        continue; // comment line
      if (EQn(buffer, "object", 6))
        break;
    }
    if (!ifs)
      return false; // ran out of lines

    vector<string> vs;
    tokenize(vs, buffer);

    // Number of grid points (voxels)
    vector<int> voxels(3);
    if (!EQn(buffer, "object", 6) || vs.size() != 8)
      return false;
    else {      
      voxels[0] = atoi(vs[5].c_str());
      voxels[1] = atoi(vs[6].c_str());
      voxels[2] = atoi(vs[7].c_str());
    }
    
    double x, y, z;
    if (!ifs.getline(buffer, BUFF_SIZE) || !EQn(buffer, "origin", 6))
      return false;
    else {
      tokenize(vs, buffer);
      if (vs.size() != 4)
        return false;
      x = atof(vs[1].c_str());
      y = atof(vs[2].c_str());
      z = atof(vs[3].c_str());
    }
    vector3 origin(x, y, z);

    // now three lines with the x, y, and z axes
    vector<vector3> axes;
    for (unsigned int i = 0; i < 3; ++i) {
      if (!ifs.getline(buffer, BUFF_SIZE) || !EQn(buffer, "delta", 5))
        return false;
      else {
        tokenize(vs, buffer);
        if (vs.size() != 4)
          return false;
        x = atof(vs[1].c_str());
        y = atof(vs[2].c_str());
        z = atof(vs[3].c_str());
        axes.push_back(vector3(x, y, z));
      }
    }    

    // Two remaining header lines before the data:
    /*
      object 2 class gridconnections counts nx ny nz
      object 3 class array type double rank 0 times n data follows
    */
    if (!ifs.getline(buffer, BUFF_SIZE) || !EQn(buffer, "object", 6))
      return false;
    if (!ifs.getline(buffer, BUFF_SIZE) || !EQn(buffer, "object", 6))
      return false;

    pmol->BeginModify();
    pmol->SetDimension(3);

    OBGridData *gd = new OBGridData;
    gd->SetAttribute("OpenDX");

    // get all values as one vector<double>
    char *endptr;
    vector<double> values;
    int n = voxels[0]*voxels[1]*voxels[2];
    int line = 0;
    values.reserve(n);
    while (ifs.getline(buffer, BUFF_SIZE))
    {
      ++line;
      if (EQn(buffer, "attribute", 9))
        break; // we're finished with reading data -- although we should probably have a voxel check in here too

      tokenize(vs, buffer);
      if (vs.size() == 0)
      {
        errorMsg << "Problem reading the OpenDX grid file: cannot"
                 << " read line " << line
                 << ", there does not appear to be any data in it.\n"
                 << buffer << "\n";
        obErrorLog.ThrowError(__FUNCTION__, errorMsg.str(), obError);
        return false;
      }

      for (unsigned int l = 0; l < vs.size(); ++l)
      {
        values.push_back(strtod(static_cast<const char*>(vs[l].c_str()), &endptr));
      }
    }

    gd->SetNumberOfPoints(voxels[0], voxels[1], voxels[2]);
    gd->SetLimits(origin, axes[0], axes[1], axes[2]);
    gd->SetUnit(OBGridData::ANGSTROM);
    gd->SetOrigin(fileformatInput); // i.e., is this data from a file or determined by Open Babel
    gd->SetValues(values); // set the values
    pmol->SetData(gd); // store the grids in the OBMol
    pmol->EndModify();

    // Trailing control lines
    /*
     attribute "dep" string "positions"
     object "regular positions regular connections" class field
     component "positions" value 1
     component "connections" value 2
     component "data" value 3
    */
    if (!ifs.getline(buffer, BUFF_SIZE) || !EQn(buffer, "object", 6))
      return false;
    if (!ifs.getline(buffer, BUFF_SIZE) || !EQn(buffer, "component", 9))
      return false;
    if (!ifs.getline(buffer, BUFF_SIZE) || !EQn(buffer, "component", 9))
      return false;
    if (!ifs.getline(buffer, BUFF_SIZE) || !EQn(buffer, "component", 9))
      return false;

    // clean out any remaining blank lines
    while(ifs.peek() != EOF && ifs.good() &&
          (ifs.peek() == '\n' || ifs.peek() == '\r'))
      ifs.getline(buffer,BUFF_SIZE);

    return true;
  }

//------------------------------------------------------------------------------
  bool OBOpenDXCubeFormat::WriteMolecule(OBBase* pOb, OBConversion* pConv)
  {
    OBMol* pmol = dynamic_cast<OBMol*>(pOb);
    if(pmol==NULL)
      return false;

    ostream &ofs = *pConv->GetOutStream();
    OBMol &mol = *pmol;

    char buffer[BUFF_SIZE];
    string str;
    stringstream errorMsg;

    OBGridData *gd = (OBGridData*)mol.GetData(OBGenericDataType::GridData);
    if (gd == NULL) {
      errorMsg << "The molecule has no grid.";
      obErrorLog.ThrowError(__FUNCTION__, errorMsg.str(), obWarning);
      return false;
    }

    // APBS-style OpenDX Multigrid
    // First some comments
    ofs << "# Data from Open Babel " << BABEL_VERSION << "\n";
    str = mol.GetTitle();
    if (str.empty())
      ofs << "# Molecule Title: *****" << "\n";
    else
      ofs << "# Molecule Title: " << str << "\n";

    int nx, ny, nz;
    double origin[3], xAxis[3], yAxis[3], zAxis[3];
    gd->GetAxes(xAxis, yAxis, zAxis);
    gd->GetNumberOfPoints(nx, ny, nz);
    gd->GetOriginVector(origin);

    // data line 1: # of points in x, y, z (nx, ny, nz)
    snprintf(buffer, BUFF_SIZE, "object 1 class gridposition counts %5d %5d %5d", nx, ny, nz);
    ofs << buffer << "\n";

    // data line 2: origin (x, y, z)
    snprintf(buffer, BUFF_SIZE,"origin %12.6f %12.6f %12.6f",
        origin[0], origin[1], origin[2]);
    ofs << buffer << "\n";

    // data line 3: x-displacement
    snprintf(buffer, BUFF_SIZE,"delta %12.6f %12.6f %12.6f",
        xAxis[0], xAxis[1], xAxis[2]);
    ofs << buffer << "\n";

    // data line 4: y-displacement
    snprintf(buffer, BUFF_SIZE,"delta %12.6f %12.6f %12.6f",
        yAxis[0], yAxis[1], yAxis[2]);
    ofs << buffer << "\n";

    // data line 5: z-displacement
    snprintf(buffer, BUFF_SIZE,"delta %12.6f %12.6f %12.6f",
        zAxis[0], zAxis[1], zAxis[2]);
    ofs << buffer << "\n";

    // data line 6: # of points in x, y, z (nx, ny, nz)    
    snprintf(buffer, BUFF_SIZE, "object 2 class gridconnections counts %5d %5d %5d", nx, ny, nz);
    ofs << buffer << "\n";

    // data line 7: total # of points
    snprintf(buffer, BUFF_SIZE, "object 3 class array type double rank 0 times %5d data follows", nx*ny*nz);
    ofs << buffer << "\n";

    // The cube(s)
    double value;
    unsigned int count = 1;
    for (int i = 0; i < nx; ++i)
    {
      for (int j = 0; j < ny; ++j)
      {
        for (int k = 0; k < nz; ++k)
        {
          value = gd->GetValue(i, j, k);
          snprintf(buffer, BUFF_SIZE," %12.5E", value);
          if (count % 3 == 0)
            ofs << buffer << "\n";
          else
            ofs << buffer;
          count++;
        } // z-axis
      } // y-axis
    } // x-axis

    if (count % 3 != 0)
      ofs << "\n";
    ofs << "attribute \"dep\" string \"positions\"\n";
    ofs << "object \"regular positions regular connections\" class field\n";
    ofs << "component \"positions\" value 1\n";
    ofs << "component \"connections\" value 2\n";
    ofs << "component \"data\" value 3\n";

    return true;
  }
}
