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

#include <map>

#include "Debug.h"

using namespace Cauchy;

struct Type::Private {
  Private() : embeddedType(0), returnType(0)
  {
  }
  DataType type;
  const Type* embeddedType;
  const Type* returnType;
  static std::map<const Type*, const Type*> complexes;
  static std::map<const Type*, const Type*> matrixes;
  static std::map< std::pair< const Type*, const Type*>, const Type*> functions;
};

std::map<const Type*, const Type*> Type::Private::complexes;
std::map<const Type*, const Type*> Type::Private::matrixes;
std::map< std::pair< const Type*, const Type*>, const Type*> Type::Private::functions;

const Type* Type::Logical = new Type(Type::LOGICAL);
const Type* Type::Single = new Type(Type::SINGLE);
const Type* Type::Double = new Type(Type::DOUBLE);
const Type* Type::Int8 = new Type(Type::INT8);
const Type* Type::UInt8 = new Type(Type::UINT8);
const Type* Type::Int16 = new Type(Type::INT16);
const Type* Type::UInt16 = new Type(Type::UINT16);
const Type* Type::Int32 = new Type(Type::INT32);
const Type* Type::UInt32 = new Type(Type::UINT32);
const Type* Type::Int64 = new Type(Type::INT64);
const Type* Type::UInt64 = new Type(Type::UINT64);
const Type* Type::Range = new Type(Type::RANGE);
const Type* Type::InfiniteRange = new Type(Type::RANGE);
const Type* Type::String = new Type(Type::STRING);
const Type* Type::Unknown = new Type(Type::UNKNOWN);
const Type* Type::Void = new Type(Type::VOID);

const Type* Type::defaultType()
{
  return Double;
}

const Type* Type::complexType(const Type* et)
{
  const Type* t = Private::complexes[et];
  if(not t)
  {
    t = new Type(COMPLEX);
    t->d->embeddedType = et;
    Private::complexes[et] = t;
  }
  return t;
}

const Type* Type::matrixType(const Type* et)
{
  const Type* t = Private::matrixes[et];
  if(not t)
  {
    t = new Type(MATRIX);
    t->d->embeddedType = et;
    Private::matrixes[et] = t;
  }
  return t;
}

const Type* Type::functionHandleType(const Type* ret, const Type* arg)
{
  std::pair< const Type*, const Type*> p(ret, arg);
  const Type* t = Private::functions[p];
  if(not t)
  {
    t = new Type(FUNCTION_POINTER);
    t->d->returnType = ret;
    Private::functions[p] = t;
  }
  return t;
}

const Type* Type::optype(const Type* r1, const Type* r2)
{
  CAUCHY_DEBUG("Op type" << *r1 << " " << *r2);
  if(r1 == Unknown or r2 == Unknown ) {
    if(r1 == Unknown)
    {
      return r2;
    } else {
      return r1;
    }
  } else if(r1->isMatrix() or r2->isMatrix())
  {
    if(r1->isMatrix() and r2->isMatrix())
    {
      return matrixType(optype(r1->embeddedType(), r2->embeddedType()));
    } else if(r1->isMatrix())
    {
      if(r2->isComplex())
      {
        return matrixType(r2);
      } else {
        return r1;
      }
    } else {
      CAUCHY_ASSERT(r2->isMatrix());
      if(r1->isComplex())
      {
        return matrixType(r1);
      } else {
        return r2;
      }
    }
  } else if(r1->isComplex() or r2->isComplex())
  {
    if(r1->isComplex() and r2->isComplex())
    {
      return complexType(optype(r1->embeddedType(), r2->embeddedType()));
    } else if(r1->isComplex())
    {
      return r1;
    } else {
      CAUCHY_ASSERT(r2->isComplex());
      return r2;
    }
  } else if(r1 == Logical or r2 == Logical ) {
    if(r1 == Logical and r2 == Logical ) {
      return defaultType();
    } else if(r1 == Logical)
    {
      return r2;
    } else {
      CAUCHY_ASSERT(r2 == Logical);
      return r1;
    }
  } else {
    CAUCHY_ASSERT(r1->isNumber());
    CAUCHY_ASSERT(r2->isNumber());
    // Number types
    //
    // Matlab works differently from regular C++ when it comes to data types.
    // in Matlab UINT8(10) * 0.3333 --> UINT8(3), in C++ uint8(10) * 0.3333 = 3.333
    // In other word, in Matlab the low precision type takes over the high precision
    if(r1 == UInt8 or r2 == UInt8) return UInt8;
    if(r1 == Int8 or r2 == Int8) return Int8;
    if(r1 == UInt16 or r2 == UInt16) return UInt16;
    if(r1 == Int16 or r2 == Int16) return Int16;
    if(r1 == UInt32 or r2 == UInt32) return UInt32;
    if(r1 == Int32 or r2 == Int32) return Int32;
    if(r1 == UInt64 or r2 == UInt64) return UInt64;
    if(r1 == Int64 or r2 == Int64) return Int64;
    if(r1 == Single or r2 == Single) return Single;
    CAUCHY_ASSERT(r1 == Double and r2 == Double);
    return Double;
  }
}

Type::Type(Type::DataType type) : d(new Private)
{
  d->type = type;
  d->embeddedType = 0;
}

Type::~Type()
{
}

Type::DataType Type::dataType() const
{
  return d->type;
}

const Type* Type::embeddedType() const
{
  return d->embeddedType;
}

bool Type::isMatrix() const
{
  return d->type == MATRIX;
}

bool Type::isComplex() const
{
  return d->type == COMPLEX;
}

bool Type::isNumber() const
{
  return d->type == DOUBLE or d->type == SINGLE or d->type == UINT8 or d->type == INT8 or d->type == UINT16 or d->type == INT16 or d->type == UINT32 or d->type == INT32 or d->type == UINT64 or d->type == INT64;
}

bool Type::isFunctionPointer() const
{
  return d->type == FUNCTION_POINTER;
}

const Type* Type::returnType() const
{
  CAUCHY_ASSERT(isFunctionPointer());
  return d->returnType;
}
