#include "iocif.h"

#include "utils.h"

#include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/device/back_inserter.hpp>

#include <cassert>
#include <iostream>
#include <string>

#define foreach BOOST_FOREACH

namespace io = boost::iostreams;
namespace ba = boost::algorithm;

//  Our CIF implementation consists of flyweight classes.

namespace mmCIF
{

// skip routines to quickly position character pointer p at a next interesting location
const char* skip_line(const char* p, const char* end);
// skip over white-space and comments
const char* skip_white(const char* p, const char* end);
const char* skip_value(const char* p, const char* end);

std::string row::operator[](const char* inName) const
{
  std::string result;

  foreach (const field& f, m_fields)
  {
    if (strncmp(inName, f.m_name, f.m_name_end - f.m_name) == 0)
    {
      result = f.value();
      break;
    }
  }

  return result;
}

row record::front() const
{
  row result;
  result.m_data = m_start;
  result.m_field = 0;

  const char* p = m_start;

  if (m_loop)
  {
    for (uint32 i = 0; i < m_field_count; ++i)
    {
      assert(*p == '_');
      assert(*(p + m_name.length()) == '.');

      field field = {};

      field.m_name = p = p + m_name.length() + 1;
      while (p != m_end and not isspace(*p))
        ++p;

      field.m_name_end = p;

      p = skip_white(p, m_end);

      result.m_fields.push_back(field);
    }

    foreach (field& fld, result.m_fields)
    {
      fld.m_data = skip_white(p, m_end);
      fld.m_data_end = skip_value(fld.m_data, m_end);
      p = skip_white(fld.m_data_end, m_end);
    }
  }
  else
  {
    for (uint32 i = 0; i < m_field_count; ++i)
    {
      assert(*p == '_');
      assert(*(p + m_name.length()) == '.');

      field field;
      field.m_name = p = p + m_name.length() + 1;
      while (p != m_end and not isspace(*p))
        ++p;

      field.m_name_end = p;

      p = skip_white(p, m_end);
      field.m_data = p;

      p = skip_value(p, m_end);
      field.m_data_end = p;
      p = skip_white(p, m_end);

      result.m_fields.push_back(field);
    }
  }

  return result;
}

record::iterator record::begin() const
{
  return const_iterator(*this, front());
}

record::iterator record::end() const
{
  row end = { m_start, -1 };
  return const_iterator(*this, end);
}

void record::advance(row& row) const
{
  if (m_loop and not row.m_fields.empty())
  {
    const char* p = skip_white(row.m_fields.back().m_data_end, m_end);

    if (p >= m_end)
    {
      row.m_fields.clear();
      row.m_field = -1;
    }
    else
    {
      foreach (field& fld, row.m_fields)
      {
        fld.m_data = skip_white(p, m_end);
        fld.m_data_end = skip_value(fld.m_data, m_end);
        p = skip_white(fld.m_data_end, m_end);
      }

      row.m_field += 1;
    }
  }
  else
  {
    row.m_fields.clear();
    row.m_field = -1;
  }
}

std::string record::get_joined(const char* inName,
                               const char* inDelimiter) const
{
  std::string result;

  for (iterator i = begin(); i != end(); ++i)
  {
    std::string s = i->operator[](inName);
    ba::trim(s);
    result = (result.empty() ? result : result + inDelimiter) + s;
  }

  return result;
}

file::file(std::istream& is)
{
  // first extract data into a buffer
  m_buffer.reserve(10 * 1024 * 1024); // reserve 10 MB, should be sufficient

  io::copy(is, io::back_inserter(m_buffer));

  m_data = &m_buffer[0];
  m_end = m_data + m_buffer.size();

  m_buffer.push_back(0); // end with a null character, makes coding easier

  // CIF files are simple to parse

  const char* p = m_data;

  if (strncmp(p, "data_", 5) != 0)
    throw mas_exception("Is this an mmCIF file?");

  p = skip_line(p, m_end);

  record rec = { p };
  bool loop = false;

  while (p < m_end)
  {
    if (isspace(*p))  // skip over white space
    {
      ++p;
      continue;
    }

    if (*p == '#')   // line starting with hash, this is a comment, skip
    {
      p = skip_line(p, m_end);
      continue;
    }

    if (strncmp(p, "loop_", 5) == 0)
    {
      if (not m_records.empty() and m_records.back().m_end == nullptr)
        m_records.back().m_end = p;

      loop = true;
      rec.m_loop = false;
      p = skip_line(p + 5, m_end);

      continue;
    }

    const char* s = p;

    if (*p == '_')  // a label
    {
      // scan for first dot
      bool newName = loop;
      const char* n = rec.m_start;

      for (;;)
      {
        if (not newName and *p != *n)
          newName = true;

        ++p;
        ++n;

        if (p == m_end or *p == '.' or isspace(*p))
          break;
      }

      if (*p == '.')  // OK, found a record
      {
        if (newName)
        {
          // store start as end for the previous record, if any
          if (not m_records.empty() and m_records.back().m_end == nullptr)
            m_records.back().m_end = s;

          rec.m_start = s;
          rec.m_end = nullptr;
          rec.m_loop = loop;
          rec.m_field_count = 1;
          rec.m_name = std::string(s, p);

          m_records.push_back(rec);
        }
        else
          m_records.back().m_field_count += 1;

        // skip over field name
        while (p != m_end and not isspace(*p))
          ++p;
      }
      else
      {
        // store start as end for the previous record, if any
        if (not m_records.empty() and m_records.back().m_end == nullptr)
          m_records.back().m_end = s;

        // a record without a field (is that possible in mmCIF?)
        std::cerr << "record without field: " << std::string(s, p)
                  << std::endl;

        rec.m_start = s;
        rec.m_end = nullptr;
        rec.m_loop = loop;
        rec.m_field_count = 0;
        rec.m_name = std::string(s, p);

        m_records.push_back(rec);
      }

      if (not rec.m_loop)
        p = skip_value(p, m_end);

      loop = false;
      continue;
    }

    if (rec.m_loop == false)
    {
      // guess we should never reach this point
      throw mas_exception("invalid CIF file? (unexpected data, not in loop)");
    }

    p = skip_value(p, m_end);
    p = skip_white(p, m_end);

    // check for a new data_ block
    if (p != m_end and strncmp(p, "data_", 5) == 0)
      throw mas_exception("Multiple data blocks in CIF file");
  }

  if (not m_records.empty() and m_records.back().m_end == nullptr)
    m_records.back().m_end = p;

  sort(m_records.begin(), m_records.end());
}

record file::operator[](const char* inName) const
{
  record result = {};
  result.m_name = inName;

  std::vector<record>::const_iterator i = lower_bound(m_records.begin(),
                                                      m_records.end(), result);
  if (i != m_records.end() and i->m_name == inName)
    result = *i;

  return result;
}

std::string file::get(const char* inName) const
{
  const char* p = strchr(inName, '.');
  assert(p != nullptr);
  if (p == nullptr)
    throw std::logic_error("incorrect name");

  record r = operator[](std::string(inName, p).c_str());
  return r.front()[std::string(p + 1).c_str()];
}

std::string file::get_joined(const char* inName, const char* inDelimiter) const
{
  const char* p = strchr(inName, '.');
  assert(p != nullptr);
  if (p == nullptr)
    throw std::logic_error("incorrect name");

  record test;
  test.m_name.assign(inName, p);

  std::string result;

  std::vector<record>::const_iterator i = lower_bound(m_records.begin(),
                                                      m_records.end(), test);
  if (i != m_records.end() and i->m_name == test.m_name)
    result = i->get_joined(p + 1, inDelimiter);

  return result;
}

// skip to first character after the next NL character
const char* skip_line(const char* p, const char* end)
{
  while (p != end)
  {
    if (*p++ == '\n')
      break;
  }

  return p;
}

// skip over white space and comments
const char* skip_white(const char* p, const char* end)
{
  while (p != end)
  {
    if (isspace(*p))
    {
      ++p;
      continue;
    }

    if (*p == '#')
    {
      do ++p; while (p < end and *p != '\n');
      continue;
    }

    break;
  }

  return p;
}

// skip over values for a record
const char* skip_value(const char* p, const char* end)
{
  for (;;)
  {
    if (isspace(*p))
    {
      ++p;
      continue;
    }

    if (*p == ';' and *(p - 1) == '\n')
    {
      do p = skip_line(p, end); while (p < end and *p != ';');
      ++p;
      break;
    }

    if (*p == '\'')
    {
      do ++p; while (p != end and *p != '\'');
      ++p;
      break;
    }

    if (*p == '\"')
    {
      do ++p; while (p != end and *p != '\"');
      ++p;
      break;
    }

    if (*p == '#')
    {
      p = skip_line(p, end);
      continue;
    }

    while (p != end and not isspace(*p))
      ++p;

    break;
  }

  return p;
}

}

//void ReadCIF(std::istream& in, MProtein& out)
//{
//  file cif(&buffer[0], buffer.size() - 1);
//
//
//
//  cout << "id: " << cif["_entry"].front()["id"].value() << std::endl;
//
//  foreach (const row& row, cif["_atom_type"])
//  {
//    cout << row["symbol"].value() << std::endl;
//  }
//
//  foreach (const row& row, cif["_atom_site"])
//  {
//    cout << "ATOM  " << row["Cartn_x"].value() << ' ' << row["Cartn_y"].value() << ' ' << row["Cartn_z"].value() << std::endl;
//  }
//}
//
