/*
 *  Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * either version 2, or (at your option) any later version of the License.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "DeclarationsRegistry.h"

#include <algorithm>
#include <fstream>
#include <map>

#include "Debug.h"
#include "Macros_p.h"
#include "String.h"
#include "FunctionDeclaration.h"
#include "FunctionDeclaration_p.h"
#include "VariableDeclaration.h"
#include "VariableDeclaration_p.h"

using namespace Cauchy;

struct DeclarationsRegistry::Private {
  std::map< String, std::vector<FunctionDeclaration*> > definitions;
  std::map< String, VariableDeclaration* > constants;
  std::map< String, VariableDeclaration* > globals;
  std::list< String > searchpathes;
};

DeclarationsRegistry::DeclarationsRegistry() : d(new Private)
{
#ifdef _CAUCHY_INCLUDE_DIR_
  addSearchPath(_CAUCHY_INCLUDE_DIR_);
#endif
}

DeclarationsRegistry::~DeclarationsRegistry()
{
  delete d;
}

void DeclarationsRegistry::addSearchPath(const String& _path)
{
  String path = _path + "/";
  if(std::find(d->searchpathes.begin(), d->searchpathes.end(), path) == d->searchpathes.end())
  {
    d->searchpathes.push_front(path);
  }
}

bool isToken(const String& str)
{
  return str == "returns" or str == "takes" or str == "in" or str == "named";
}

const Type* nameToType(const String& str)
{
  if(str == "Number")
  {
    return Type::defaultType();
  } else if(str == "Complex")
  {
    return Type::complexType(Type::defaultType());
  } else if(str == "Number")
  {
    return Type::complexType(Type::defaultType());
  } else if( str == "Matrix")
  {
    return Type::matrixType(Type::Double);
  } else if(str == "CMatrix")
  {
    return Type::matrixType(Type::complexType(Type::defaultType()));
  } else if( str == "String")
  {
    return Type::String;
  } else if( str == "FunctionHandle" )
  {
    return Type::functionHandleType(Type::defaultType(), Type::defaultType());
  }
  return Type::Unknown;
}

bool parseTypesList(const std::vector<String>& elements, std::size_t& pos, std::vector<const Type*>& typesList)
{
  if(pos < elements.size() and elements[pos] == "Void")
  {
    ++pos;
    return true;
  }
  while(pos < elements.size() and not isToken(elements[pos]))
  {
    const Type* type = nameToType(elements[pos]);
    if(type == Type::Unknown) return false;
    typesList.push_back(type);
    ++pos;
  }
  return true;
}

bool parseString( const String& tok, const std::vector<String>& elements, std::size_t& pos, String& named, bool& hasNamed)
{
  if(pos < elements.size() and elements[pos] == tok)
  {
    hasNamed = true;
    ++pos;
    if(pos < elements.size())
    {
      named = elements[pos];
    } else {
      CAUCHY_DEBUG("Expecting name after '" << tok << "'");
      return false;
    }
    ++pos;
  }
  return true;
}

bool parseType( const String& tok, const std::vector<String>& elements, std::size_t& pos, const Type*& type, bool& hasNamed)
{
  String tn;
  bool v = parseString(tok, elements, pos, tn, hasNamed);
  type = nameToType(tn);
  return v;
}

bool DeclarationsRegistry::load(const String& file)
{
  foreach(const String& path, d->searchpathes)
  {
    String fullname = path + file;
    std::ifstream in;
    in.open(fullname.c_str());
    if(in)
    {
      CAUCHY_DEBUG("Found: " << fullname);
      std::string str;
      std::getline(in,str);
      while ( in ) {
        Cauchy::String ustr = str;
        std::vector<String> elements = ustr.split(" ");
        if(not elements.empty())
        {
          std::size_t pos = 0;
          CAUCHY_DEBUG(elements.size() << " " << elements[pos] << " " << elements[pos].length());
          if(elements[pos][0] != '#')
          {
            if(elements[pos] == "function")
            {
              ++pos;
              String functionName = elements[pos];
              ++pos;
              std::vector<const Type*> returns;
              bool hasReturns = false;
              if(pos < elements.size() and elements[pos] == "returns")
              {
                hasReturns = true;
                ++pos;
                if(not parseTypesList(elements, pos, returns)) return false;
              }
              std::vector<const Type*> arguments;
              bool hasArguments = false;
              if(pos < elements.size() and elements[pos] == "takes")
              {
                hasArguments = true;
                ++pos;
                if(not parseTypesList(elements, pos, arguments)) return false;
              }
              String header;
              bool hasIn = false;
              if(not parseString("in", elements, pos, header, hasIn)) return false;
              String named;
              bool hasNamed = false;
              if(not parseString("named", elements, pos, named, hasNamed)) return false;
              CAUCHY_DEBUG(hasReturns << hasArguments << hasIn << hasNamed);
              if(hasReturns or hasArguments or (not hasIn and not hasNamed))
              {
                FunctionDeclaration* fd = new FunctionDeclaration;
                fd->d->name = functionName;
                fd->d->arguments = arguments;
                fd->d->returns = returns;
                fd->d->header = header;
                fd->d->callName = named;
                CAUCHY_DEBUG(functionName << " Arguments: " << fd->d->arguments.size() << " Returns: " << fd->d->returns.size() << " Header: " << header << " Named: " << named);
                d->definitions[functionName].push_back(fd);
              } else {
                CAUCHY_DEBUG(functionName << " Header: " << header << " Named: " << named);
                if(not hasIn and not hasNamed) return false;
                foreach(FunctionDeclaration* def, d->definitions[functionName])
                {
                  if(hasIn) {
                    def->d->header = header;
                  }
                  if(hasNamed)
                  {
                    def->d->callName = named;
                  }
                }
              }
            } else if(elements[pos] == "constant" or elements[pos] == "global") {
              bool constant = elements[pos] == "constant";
              ++pos; // eat the constant
              String constantName = elements[pos];
              ++pos;
              // Parse the type of the constant
              const Type* t;
              bool hasType = false;
              if(not parseType("is", elements, pos, t, hasType))
              {
              }
              // Parse in which header is the constant
              String header;
              bool hasIn = false;
              if(not parseString("in", elements, pos, header, hasIn)) return false;
              // Parse how the constant is named in the header
              String named;
              bool hasNamed = false;
              if(not parseString("named", elements, pos, named, hasNamed)) return false;
              if(constant)
              {
                if(hasType)
                {
                  if(d->constants.find(constantName) != d->constants.end()) return false;
                  VariableDeclaration* fd = new VariableDeclaration;
                  fd->d->name = constantName;
                  fd->d->type = t;
                  fd->d->header = header;
                  fd->d->callName = named;
                  fd->d->constant = true;
                  d->constants[constantName] = fd;
                } else {
                  CAUCHY_DEBUG( header << named);
                  if(not hasIn or not hasNamed) return false;
                  std::map< String, VariableDeclaration* >::iterator it = d->constants.find(constantName);
                  if(hasIn and it->second) {
                    it->second->d->header = header;
                  }
                  if(hasNamed and it->second)
                  {
                    it->second->d->callName = named;
                  }
                }
              } else {
                if(hasType)
                {
                  if(d->globals.find(constantName) != d->globals.end()) return false;
                  VariableDeclaration* fd = new VariableDeclaration;
                  fd->d->name = constantName;
                  fd->d->type = t;
                  fd->d->header = header;
                  fd->d->callName = named;
                  fd->d->constant = false;
                  d->globals[constantName] = fd;
                } else {
                  CAUCHY_DEBUG( header << named);
                  if(not hasIn or not hasNamed) return false;
                  std::map< String, VariableDeclaration* >::iterator it = d->globals.find(constantName);
                  if(hasIn) {
                    it->second->d->header = header;
                  }
                  if(hasNamed)
                  {
                    it->second->d->callName = named;
                  }
                }
              }
            } else {
              CAUCHY_DEBUG("Unexpected " << elements[pos]);
              return false;
            }
          }
        }
        std::getline(in,str);
      }
      return true;
    }
  }
  return false;
}

std::vector<const FunctionDeclaration*> DeclarationsRegistry::function(const String& functionName) const
{
  std::vector<const FunctionDeclaration*> rets;
  std::map< String, std::vector<FunctionDeclaration*> >::iterator it = d->definitions.find(functionName);
  if(it != d->definitions.end())
  {
    foreach(FunctionDeclaration* fd, it->second)
    {
      rets.push_back(fd);
    }
  }
  return rets;
}

const FunctionDeclaration* DeclarationsRegistry::function(const String& functionName, const std::vector<const Type*>& arguments, int returns ) const
{
  std::map< String, std::vector<FunctionDeclaration*> >::iterator it = d->definitions.find(functionName);
  if(it == d->definitions.end())
  {
    return 0;
  } else {
    if(it->second.size() == 0)
    {
      return 0;
    } else if(it->second.size() == 1)
    {
      FunctionDeclaration* fd = it->second[0];
      if(fd->arguments().size() == arguments.size() and fd->returns().size() >= std::size_t(returns))
      {
        return fd;
      } else {
        return 0;
      }
    } else {
      FunctionDeclaration* best = 0;
      int bestConversion = 0;
      foreach(FunctionDeclaration* fd, it->second)
      {
        if(fd->arguments().size() == arguments.size() and fd->returns().size() >= std::size_t(returns))
        {
          int conversion = 0;
          for(std::size_t i = 0; i < arguments.size(); ++i)
          {
            // most conversion will lead to compilation failure... check for that
            if(fd->arguments()[i] != arguments[i])
            {
              ++conversion;
            }
          }
          if(best == 0 or conversion < bestConversion)
          {
            best = fd;
            bestConversion = conversion;
          } else if(conversion == bestConversion) {
//             CAUCHY_ABORT("Unimplemented");
          }
        }
      }
      return best;
    }
  }
  
}

const FunctionDeclaration* DeclarationsRegistry::function(const Cauchy::String& functionName, int arguments, int returns ) const
{
  std::map< String, std::vector<FunctionDeclaration*> >::iterator it = d->definitions.find(functionName);
  if(it == d->definitions.end())
  {
    return 0;
  } else {
    foreach(FunctionDeclaration* fd, it->second)
    {
      if(fd->arguments().size() == std::size_t(arguments) and fd->returns().size() == std::size_t(returns))
      {
        return fd;
      }
    }
    return 0;
  }
}

const VariableDeclaration* DeclarationsRegistry::constant(const String& name) const
{
  std::map< String, VariableDeclaration* >::iterator it = d->constants.find(name);
  if(it == d->constants.end())
  {
    return 0;
  } else {
    return it->second;
  }
}

const VariableDeclaration* DeclarationsRegistry::global(const String& name) const
{
  std::map< String, VariableDeclaration* >::iterator it = d->globals.find(name);
  if(it == d->globals.end())
  {
    return 0;
  } else {
    return it->second;
  }
}

std::list<const FunctionDeclaration*> DeclarationsRegistry::functions() const
{
  std::list<const FunctionDeclaration*> ret;
  for(std::map< String, std::vector<FunctionDeclaration*> >::iterator it = d->definitions.begin();
      it != d->definitions.end(); ++it)
  {
    ret.insert(ret.begin(), it->second.begin(), it->second.end());
  }
  return ret;  
}

std::list<const VariableDeclaration*> DeclarationsRegistry::constants() const
{
  std::list<const VariableDeclaration*> ret;
  for(std::map< String, VariableDeclaration* >::iterator it = d->constants.begin();
      it != d->constants.end(); ++it)
  {
    ret.push_back(it->second);
  }
  return ret;
}

std::list<const VariableDeclaration*> DeclarationsRegistry::globals() const
{
  std::list<const VariableDeclaration*> ret;
  for(std::map< String, VariableDeclaration* >::iterator it = d->globals.begin();
      it != d->globals.end(); ++it)
  {
    ret.push_back(it->second);
  }
  return ret;
}
