/*
 *  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 "GeneratorPrivateBase.h"

#include "../AST/FunctionDefinition.h"
#include "../FunctionDeclaration.h"
#include "../Macros_p.h"
#include "../Type.h"

Cauchy::String GeneratorVisitorPrivate::matrixTypeName(const Cauchy::Type* type)
{
  switch(type->dataType())
  {
    case Cauchy::Type::SINGLE:
      return "Eigen::MatrixXf";
    case Cauchy::Type::DOUBLE:
      return "Eigen::MatrixXd";
    case Cauchy::Type::COMPLEX:
      switch(type->embeddedType()->dataType())
      {
        case Cauchy::Type::SINGLE:
          useComplexes();
          return "Eigen::MatrixXcf";
        case Cauchy::Type::DOUBLE:
          useComplexes();
          return "Eigen::MatrixXcd";
        default:
          break;
      }
    default:
      break;
  }
  CAUCHY_ABORT("Unimplemented " + type->dataType());
  useUnknown();
  return "Unknown";
}

Cauchy::String GeneratorVisitorPrivate::typeName(const Cauchy::Type* type)
{
  switch(type->dataType())
  {
    case Cauchy::Type::LOGICAL:
      return "bool";
    case Cauchy::Type::SINGLE:
      return "float";
    case Cauchy::Type::DOUBLE:
      return "double";
    case Cauchy::Type::COMPLEX:
      useComplexes();
      return "std::complex<" + typeName(type->embeddedType()) + ">";
    case Cauchy::Type::RANGE:
      useInclude("Cauchy/Eigen3/Range.h");
      return matrixTypeName(Cauchy::Type::Double);
    case Cauchy::Type::MATRIX:
      return matrixTypeName(type->embeddedType());
    case Cauchy::Type::STRING:
      return "std::string";
    case Cauchy::Type::FUNCTION_POINTER:
      return "double (*)(double)";
    case Cauchy::Type::UNKNOWN:
      useUnknown();
      return "Cauchy::Unknown";
    case Cauchy::Type::VOID:
    default:
      break;
  }
  CAUCHY_ABORT("Unimplemented");
  return "Unknown";
}

void GeneratorVisitorPrivate::indent()
{
  ++indentation;
  indentationCache += "  ";
}

void GeneratorVisitorPrivate::unindent()
{
  --indentation;
  indentationCache = indentationCache.substr(0, indentation * 2);
}

void GeneratorVisitorPrivate::useInclude(const Cauchy::String& _header)
{
  if(_header.isEmpty()) return;
  if(std::find(includes.begin(), includes.end(), _header) != includes.end()) return;
  includes.push_back(_header);
  header += "#include <" + _header + ">\n";
}

void GeneratorVisitorPrivate::useComplexes()
{
  useInclude("Cauchy/Eigen3/ComplexOperators.h");
}

void GeneratorVisitorPrivate::useUnknown()
{
  useInclude("Cauchy/Eigen3/Unknown.h");
}

Cauchy::String GeneratorVisitorPrivate::functionDeclaration(const Cauchy::FunctionDeclaration* functionDeclaration, const Cauchy::AST::FunctionDefinition* functionDefinition)
{
  Cauchy::String declaration;
  if(functionDeclaration->returns().size() >= 1)
  {
    declaration = typeName(functionDeclaration->returns()[0]) + " ";
  } else {
    declaration = "void ";
  }
  
  declaration += functionDeclaration->name() + "(";
  
  for(std::size_t i = 0; i < functionDeclaration->arguments().size(); ++i)
  {
    declaration += typeName(functionDeclaration->arguments()[i]);
    if(functionDefinition)
    {
      declaration += " " + functionDefinition->arguments()[i];
    }
    if(i != functionDeclaration->arguments().size() - 1)
    {
      declaration += ", ";
    }
  }
  
  if(functionDeclaration->returns().size() > 1)
  {
    if(not functionDeclaration->arguments().empty())
    {
      declaration += ", ";
    }
    for(std::size_t i = 1; i < functionDeclaration->returns().size(); ++i)
    {
      declaration += typeName(functionDeclaration->returns()[i]) + "*";
      if(functionDefinition)
      {
        declaration += " " + functionDefinition->returns()[i] + "_ = 0";
      } else {
        declaration += " ret" + Cauchy::String::number((int)i) + " = 0";
      }
      if(i != functionDeclaration->returns().size() - 1)
      {
        declaration += ", ";
      }
    }
  }
  return declaration + ")";
}