/*
   Copyright 2012 Olivier de Gaalon <olviier.jg@gmail.com>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License version 2 as published by the Free Software Foundation.

   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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

#include "templateresolver.h"
#include "cpptypes.h"
#include "templatedeclaration.h"
#include "templateparameterdeclaration.h"

using namespace Cpp;
using namespace KDevelop;

static bool isConstBased(const AbstractType::Ptr& type)
{
  if (type->modifiers() & AbstractType::ConstModifier)
    return true;
  if (ArrayType::Ptr arrayType = type.cast<ArrayType>())
    return arrayType->elementType() ? isConstBased(arrayType->elementType()) : false;
  if (PointerType::Ptr ptrType = type.cast<PointerType>())
    return ptrType->baseType() ? isConstBased(ptrType->baseType()) : false;
  return false;
}

static bool isConstBased(const IndexedTypeIdentifier& type)
{
  return type.isConstant();
}

static bool isVolatileBased(const AbstractType::Ptr& type)
{
  if (type->modifiers() & AbstractType::VolatileModifier)
    return true;
  if (ArrayType::Ptr arrayType = type.cast<ArrayType>())
    return arrayType->elementType() ? isVolatileBased(arrayType->elementType()) : false;
  if (PointerType::Ptr ptrType = type.cast<PointerType>())
    return ptrType->baseType() ? isVolatileBased(ptrType->baseType()) : false;
  return false;
}

static bool isVolatileBased(const IndexedTypeIdentifier& type)
{
  return type.isVolatile();
}

template<typename A, typename B, typename C>
static bool matchCV(const A& parameterType, const B& argumentType, C* res)
{
  if (isConstBased(parameterType))
  {
    if (!argumentType.template cast<PointerType>() && isConstBased(argumentType))
      res->constMatch = true;
    else
    {
      res->valid = false;
      return false; //Invalid, param is const and arg is either non-const or has a different ptr-depth
    }
  }
  if (isVolatileBased(parameterType))
  {
    if (!argumentType.template cast<PointerType>() && isVolatileBased(argumentType))
      res->volatileMatch = true;
    else
    {
      res->valid = false;
      return false; //Invalid, param is volatile and arg is either non-volatile or has a different ptr-depth
    }
  }
  return true;
}

TemplateResolver::TemplateResolver(const TopDUContext* topContext)
:m_topContext(topContext) { }

uint TemplateResolver::matchTemplateParameterTypes( const AbstractType::Ptr& argumentType, const AbstractType::Ptr& parameterType, QMap< IndexedString, AbstractType::Ptr >& instantiatedTypes ) const
{
  if ( !argumentType && !parameterType )
    return 1;
  if ( !argumentType || !parameterType )
    return 0;

  TemplateMatchType matchResult;
  matchTemplateParameterTypesInternal(argumentType, parameterType, instantiatedTypes, matchResult);
  return matchResult.toUint();
}

bool TemplateResolver::templateHandleConstIntegralType(const AbstractType::Ptr& argumentType, const AbstractType::Ptr& parameterType, TemplateMatchType& res) const
{
  ConstantIntegralType::Ptr argumentIntegral = argumentType.cast<ConstantIntegralType>();
  ConstantIntegralType::Ptr parameterIntegral = parameterType.cast<ConstantIntegralType>();
  if (argumentIntegral && parameterIntegral)
  {
    if (argumentIntegral->plainValue() != parameterIntegral->plainValue())
      res.valid = false;
    return true; //Handled, valid if integral types match, invalid otherwise
  }
  else if (parameterIntegral)
  {
    //Nothing but an equal integral will match an integral parameter
    res.valid = false;
    return true; //Handled, invalid.
  }
  else if (argumentIntegral && !parameterType.cast<DelayedType>())
  {
    res.valid = false; //A const integral arg can only match an equal const integral or replace a delayed type
    return true; //Handled, invalid
  }
  return false;
}

bool TemplateResolver::templateHandleDelayedType ( const AbstractType::Ptr& argumentType, const AbstractType::Ptr& parameterType, QMap< IndexedString, AbstractType::Ptr >& instantiatedTypes, TemplateMatchType& res ) const
{
  DelayedType::Ptr delayed = parameterType.cast<DelayedType>();
  if ( !delayed )
    return false; //Not delayed type, not handled

  IndexedTypeIdentifier paramDelayedId = delayed->identifier();
  //Delayed id should never have pointer depth, or it would be a pointerType and not a delayedType
  //If it's possible somehow for it to be both, it's not correctly handled here
  Q_ASSERT(!paramDelayedId.pointerDepth());
  matchCV(paramDelayedId, argumentType, &res);
  ///TODO: the code only uses the last identifier and used to verify that
  ///      only one Identifier is actually contained in the QualifiedIdentifier
  ///      in the paramDelayedId
  ///      This caused issues for __gnu_cxx::_S_mutex, _S_single, _S_atomic etc.
  ///      it's not clear whether this is actually a bug or not - someone should
  ///      investigate. But rather don't assert for now!
  IndexedString identifier = paramDelayedId.identifier().identifier().last().identifier();
  if ( instantiatedTypes.contains( identifier ) )
    instantiatedTypes[identifier] = argumentType;
  else
    res.valid = false;

  return true; //Parameter was delayed type, delayed type handled
}

bool TemplateResolver::templateHandleReferenceType(const AbstractType::Ptr& argumentType, const AbstractType::Ptr& parameterType, QMap< IndexedString, AbstractType::Ptr >& instantiatedTypes, TemplateMatchType& res) const
{
  ReferenceType::Ptr argumentRef = argumentType.cast<ReferenceType>();
  ReferenceType::Ptr parameterRef = parameterType.cast<ReferenceType>();
  if ( argumentRef && parameterRef && argumentRef->isRValue() == parameterRef->isRValue() )
  {
    ///In case of references on both sides, match the target-types
    res.referenceMatch = true;
    matchTemplateParameterTypesInternal( argumentRef->baseType(), parameterRef->baseType(), instantiatedTypes, res );
    return true; //Handled by matching base types
  }
  else if (argumentRef)
  {
    //Argument is a reference of some sort, but will match non-reference CppTemplateParameterType
    if (parameterType.cast<CppTemplateParameterType>())
      matchTemplateParameterTypesInternal( argumentRef->baseType(), parameterType, instantiatedTypes, res);
    else
      res.valid = false;
    return true; //Handled by matching argument base type against template param,
                 //or invalidated because argument is ref and param isn't
  }
  else if ( parameterRef )
  {
    res.valid = false;
    return true; //Handled, invalid as the parameter is a reference type but the argument is not
  }

  return false; //No references, not handled
}

bool TemplateResolver::templateHandlePointerType(const AbstractType::Ptr& argumentType, const AbstractType::Ptr& parameterType, QMap< IndexedString, AbstractType::Ptr >& instantiatedTypes, TemplateMatchType& res) const
{
  ///In case of pointers on both sides, match the target-types
  PointerType::Ptr argumentPointer = argumentType.cast<PointerType>();
  PointerType::Ptr parameterPointer = parameterType.cast<PointerType>();
  if ( argumentPointer && parameterPointer && (( argumentPointer->modifiers() & AbstractType::ConstModifier ) == ( parameterPointer->modifiers() & AbstractType::ConstModifier ) ) )
  {
    ++res.pointerMatchDepth;
    matchTemplateParameterTypesInternal( argumentPointer->baseType(), parameterPointer->baseType(), instantiatedTypes, res );
    return true; //Handled by matching base types
  }
  else if (argumentPointer)
  {
    if (!parameterPointer && !isConstBased(parameterType) && parameterType.cast<CppTemplateParameterType>())
    {
        matchTemplateParameterTypesInternal( argumentPointer->baseType(), parameterType, instantiatedTypes, res );
        return true; //Handled by matching argument base type
    }
    //If argument is a pointer (const or otherwise), it will not match a non-pointer const parameter
    //Even if parameter isn't const, unless it's a CppTemplateParameterType it won't match
    res.valid = false;
    return true; //Handled, invalid
  }
  else if (parameterPointer)
  {
    res.valid = false;
    return true; //Handled, invalid as parameter had addition unmatched pointer depth
  }

  return false; //Not handled, neither argument nor parameter are pointers
}

bool TemplateResolver::templateHandleArrayType(const AbstractType::Ptr& argumentType, const AbstractType::Ptr& parameterType, QMap< IndexedString, AbstractType::Ptr >& instantiatedTypes, TemplateMatchType& res) const
{
  ArrayType::Ptr argumentArray = argumentType.cast<ArrayType>();
  ArrayType::Ptr parameterArray = parameterType.cast<ArrayType>();
  if ( argumentArray && parameterArray
      && ( argumentArray->modifiers() & (AbstractType::VolatileModifier | AbstractType::ConstModifier ) )
          == ( parameterArray->modifiers() & (AbstractType::VolatileModifier | AbstractType::ConstModifier ) ) )
    {
    if ( argumentArray->modifiers() & AbstractType::ConstModifier )
      res.constMatch = true;
    if ( argumentArray->modifiers() & AbstractType::VolatileModifier )
      res.volatileMatch = true;
    res.arrayMatch = true;
    matchTemplateParameterTypesInternal( argumentArray->elementType(), parameterArray->elementType(), instantiatedTypes, res );
    return true;
  }
  else if (argumentArray)
  {
    //Argument type is array, but will match non-array CppTemplateParameterType
    if (parameterType.cast<CppTemplateParameterType>())
      matchTemplateParameterTypesInternal( argumentArray->elementType(), parameterType, instantiatedTypes, res );
    else
      res.valid = false;
    return true; //Handled, either by matching argument elementType or by invalidation because parameter type cannot match
  }
  else if (parameterArray)
  {
    res.valid = false; //Parameter is array type, argument must be array type
    return true;
  }
  return false;
}

bool TemplateResolver::templateHandleIdentifiedType(const AbstractType::Ptr& argumentType, const AbstractType::Ptr& parameterType, QMap< IndexedString, AbstractType::Ptr >& instantiatedTypes, TemplateMatchType& res) const
{
  ///Match assigned template-parameters, for example when matching QList<int> to QList<T>, assign int to T.
  const IdentifiedType* identifiedArgument = dynamic_cast<const IdentifiedType*>( argumentType.unsafeData() );
  const IdentifiedType* identifiedParameter = dynamic_cast<const IdentifiedType*>( parameterType.unsafeData() );

  if ( identifiedArgument && identifiedParameter )
  {
    Declaration* argumentDeclaration = identifiedArgument->declaration( m_topContext );
    Declaration* parameterDeclaration = identifiedParameter->declaration( m_topContext );
    if (!argumentDeclaration || !parameterDeclaration)
    {
      //TODO: Very rare case which may be a bug elsewhere and could use a test.
      return false; //Unhandled, let it be accepted or rejected later
    }
    TemplateDeclaration* argumentTemplateDeclaration = dynamic_cast<TemplateDeclaration*>( argumentDeclaration );
    TemplateDeclaration* parameterTemplateDeclaration = dynamic_cast<TemplateDeclaration*>( parameterDeclaration );
    if ( !argumentTemplateDeclaration || !parameterTemplateDeclaration )
    {
      //Is this as correct as using the indexedType of the non-template declarations?
      if (argumentDeclaration != parameterDeclaration)
        res.valid = false; //Two different non-template declarations == two different types
      return true; //Handled, with either invalid mismatched types or valid matched types
    }

    if ( argumentTemplateDeclaration->instantiatedFrom() == parameterTemplateDeclaration->instantiatedFrom() && argumentTemplateDeclaration->instantiatedFrom() )
    {
      InstantiationInformation argumentInstantiatedWith = argumentTemplateDeclaration->instantiatedWith().information();
      InstantiationInformation parameterInstantiatedWith = parameterTemplateDeclaration->instantiatedWith().information();
      if ( argumentInstantiatedWith.templateParametersSize() != parameterInstantiatedWith.templateParametersSize() )
      {
        res.valid = false;
        return true; //Handled, invalid
      }

      for ( uint a = 0; a < argumentInstantiatedWith.templateParametersSize(); ++a )
      {
        if ( !matchTemplateParameterTypes( argumentInstantiatedWith.templateParameters()[a].abstractType(), parameterInstantiatedWith.templateParameters()[a].abstractType(), instantiatedTypes) )
        {
          res.valid = false;
          return true; //Handled, invalid
        }
      }
      res.templateArgsMatch = true;
      return true; //Handled, valid
    }
  }
  else if (identifiedArgument || identifiedParameter)
  {
    //Is there any case wherein an identifiedArgument will go up against a CppTemplateParameterType?
    //If This is possible we need a test case
    Q_ASSERT(!parameterType.cast<CppTemplateParameterType>());
    res.valid = false;
    return true; //Handled, invalid. If only one is identified, it's not a match
  }

  return false;
}

void TemplateResolver::matchTemplateParameterTypesInternal ( const AbstractType::Ptr& argumentType, const AbstractType::Ptr& parameterType, QMap< IndexedString, AbstractType::Ptr >& instantiatedTypes, TemplateMatchType& res ) const
{
  if (!argumentType || !parameterType)
  {
    kWarning() << "Invalid Type Encountered";
    res.valid = false;
    return;
  }

  if (templateHandleConstIntegralType(argumentType, parameterType, res))
    return;
  if (templateHandleDelayedType(argumentType, parameterType, instantiatedTypes, res))
    return;
  if (templateHandleReferenceType(argumentType, parameterType, instantiatedTypes, res))
    return;
  if (templateHandlePointerType(argumentType, parameterType, instantiatedTypes, res))
    return;
  if (templateHandleArrayType(argumentType, parameterType, instantiatedTypes, res))
    return;

  if (!matchCV(parameterType, argumentType, &res)) {
    return;
  }

  if ( CppTemplateParameterType::Ptr templateParam = parameterType.cast<CppTemplateParameterType>() )
  {
    Declaration* decl = templateParam->declaration( m_topContext );
    if ( decl )
    {
      //Should not be possible to have a CPPTemplateParameterType with template ids..?
      Q_ASSERT(decl->identifier().templateIdentifiersCount() == 0);
      IndexedString id = decl->identifier().identifier();
      //FIXME: Sometimes when matching templates within templates, delayedType will set the identifier first
      //The other way around is also probably possible
      //This needs more work to make sure the right type is set here
      //Q_ASSERT(instantiatedTypes[id].isNull());
      instantiatedTypes[id] = argumentType;
      return;
    }
  }

  if (templateHandleIdentifiedType(argumentType, parameterType, instantiatedTypes, res))
    return;

  //This /should/ be correct for all unhandled cases
  if (parameterType->indexed() != argumentType->indexed())
    res.valid = false; //Invalid, types don't match
}
