/*
 *
 *  Copyright (C) 2010 MeVis Medical Solutions AG All Rights Reserved.
 *
 *  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.1 of the License, or (at your option) any later version.
 *
 *  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.
 *
 *  Further, this software is distributed without any warranty that it is
 *  free of the rightful claim of any third person regarding infringement
 *  or the like.  Any license provided herein, whether implied or
 *  otherwise, applies only to this software file.  Patent licenses, if
 *  any, provided herein do not apply to combinations of this program with
 *  other software, or any other product whatsoever.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  Contact information: MeVis Medical Solutions AG, Universitaetsallee 29,
 *  28359 Bremen, Germany or:
 *
 *  http://www.mevis.de
 *
 */

//----------------------------------------------------------------------------------
/*!
// \file    PythonQtMethodInfo.cpp
// \author  Florian Link
// \author  Last changed by $Author: florian $
// \date    2006-05
*/
//----------------------------------------------------------------------------------

#include "PythonQtMethodInfo.h"
#include "PythonQtClassInfo.h"
#include <iostream>

QHash<QByteArray, PythonQtMethodInfo*> PythonQtMethodInfo::_cachedSignatures;
QHash<int, PythonQtMethodInfo::ParameterInfo> PythonQtMethodInfo::_cachedParameterInfos;
QHash<QByteArray, QByteArray> PythonQtMethodInfo::_parameterNameAliases;

PythonQtMethodInfo::PythonQtMethodInfo(const QMetaMethod& meta, PythonQtClassInfo* classInfo)
{
#ifdef PYTHONQT_DEBUG
  QByteArray sig = PythonQtUtils::signature(meta));
  sig = sig.mid(sig.indexOf('('));
  QByteArray fullSig = QByteArray(meta.typeName()) + " " + sig;
  std::cout << "caching " << fullSig.data() << std::endl;
#endif

  ParameterInfo type;
  fillParameterInfo(type, QByteArray(meta.typeName()), classInfo);
  _parameters.append(type);
  QList<QByteArray> names = meta.parameterTypes();
  Q_FOREACH (const QByteArray& name, names) {
    fillParameterInfo(type, name, classInfo);
    _parameters.append(type);
  }
}

PythonQtMethodInfo::PythonQtMethodInfo(const QByteArray& typeName, const QList<QByteArray>& args)
{
  ParameterInfo type;
  fillParameterInfo(type, typeName, NULL);
  _parameters.append(type);
  Q_FOREACH (const QByteArray& name, args) {
    fillParameterInfo(type, name, NULL);
    _parameters.append(type);
  }
}

const PythonQtMethodInfo* PythonQtMethodInfo::getCachedMethodInfo(const QMetaMethod& signal, PythonQtClassInfo* classInfo)
{
  QByteArray sig(PythonQtUtils::signature(signal));
  sig = sig.mid(sig.indexOf('('));
  QByteArray fullSig = QByteArray(signal.typeName()) + " " + sig;
  PythonQtMethodInfo* result = _cachedSignatures.value(fullSig);
  if (!result) {
    result = new PythonQtMethodInfo(signal, classInfo);
    _cachedSignatures.insert(fullSig, result);
  }
  return result;
}

const PythonQtMethodInfo* PythonQtMethodInfo::getCachedMethodInfoFromArgumentList(int numArgs, const char** args)
{
  QByteArray typeName = args[0];
  QList<QByteArray> arguments;
  QByteArray fullSig = typeName;
  fullSig += "(";
  for (int i =1;i<numArgs; i++) {
    if (i>1) {
      fullSig += ",";
    }
    QByteArray arg(args[i]);
    fullSig += arg;
    arguments << arg;
  }
  fullSig += ")";
  PythonQtMethodInfo* result = _cachedSignatures.value(fullSig);
  if (!result) {
    result = new PythonQtMethodInfo(typeName, arguments);
    _cachedSignatures.insert(fullSig, result);
  }
  return result;
}

void PythonQtMethodInfo::fillParameterInfo(ParameterInfo& type, const QByteArray& orgName, PythonQtClassInfo* classInfo)
{
  QByteArray name = orgName;

  type.enumWrapper = NULL;
  type.innerNamePointerCount = 0;
  type.isQList = false;
  type.passOwnershipToCPP = false;
  type.passOwnershipToPython = false;
  type.newOwnerOfThis = false;

  int len = name.length();
  if (len>0) {
    if (name.startsWith("PythonQtPassOwnershipToCPP<")) {
      type.passOwnershipToCPP = true;
      name = name.mid(27, len-28);
      len -= 28;
    } else
    if (name.startsWith("PythonQtPassOwnershipToPython<")) {
      type.passOwnershipToPython = true;
      name = name.mid(30, len-31);
      len -= 31;
    } else
    if (name.startsWith("PythonQtNewOwnerOfThis<")) {
      type.newOwnerOfThis = true;
      name = name.mid(23, len-24);
      len -= 24;
    }

    if (strncmp(name.constData(), "const ", 6)==0) {
      name = name.mid(6);
      len -= 6;
      type.isConst = true;
    } else {
      type.isConst = false;
    }
    char pointerCount = 0;
    bool hadReference = false;
    // remove * and & from the end of the string
    while (name.at(len-1) == '*') {
      len--;
      pointerCount++;
    }
    while (name.at(len-1) == '&') {
      len--;
      hadReference = true;
    }
    if (len!=name.length()) {
      name = name.left(len);
    }
    type.pointerCount = pointerCount;
    type.isReference = hadReference;

    QByteArray alias = _parameterNameAliases.value(name);
    if (!alias.isEmpty()) {
      name = alias;
    }

    type.typeId = nameToType(name);
    if (type.typeId == Unknown) {
      type.typeId = QMetaType::type(name.constData());
#if( QT_VERSION >= QT_VERSION_CHECK(5,0,0) )
      if (type.typeId == QMetaType::UnknownType) {
#else
      if (type.typeId == QMetaType::Void) {
#endif
        type.typeId = Unknown;
      }
    }
    type.name = name;

    if (name.startsWith("QList<")) {
      type.isQList = true;
    }
    if (name.contains("<")) {
      QByteArray innerName = getInnerTemplateTypeName(name);
      if (innerName.endsWith("*")) {
        type.innerNamePointerCount = 1;
        innerName.truncate(innerName.length() - 1);
      }
      type.innerName = innerName;
    }

    if (type.typeId == PythonQtMethodInfo::Unknown || type.typeId >= QMetaType::User) {
      bool isLocalEnum;
      // TODOXXX: make use of this flag!
      type.enumWrapper = PythonQtClassInfo::findEnumWrapper(type.name, classInfo, &isLocalEnum);
    }
  } else {
    type.typeId = QMetaType::Void;
    type.pointerCount = 0;
    type.isConst = false;
    type.isReference = false;
  }
}

int PythonQtMethodInfo::getInnerTemplateMetaType(const QByteArray& typeName)
{
  int idx = typeName.indexOf("<");
  if (idx > 0) {
    int idx2 = typeName.lastIndexOf(">");
    if (idx2 > 0) {
      QByteArray innerType = typeName.mid(idx + 1, idx2 - idx - 1).trimmed();
      return QMetaType::type(innerType.constData());
    }
  }
  return QMetaType::Void;
}

QByteArray PythonQtMethodInfo::getInnerTemplateTypeName(const QByteArray& typeName)
{
  int idx = typeName.indexOf("<");
  if (idx > 0) {
    int idx2 = typeName.lastIndexOf(">");
    if (idx2 > 0) {
      return typeName.mid(idx + 1, idx2 - idx - 1).trimmed();
    }
  }
  return QByteArray();
}

QByteArray PythonQtMethodInfo::getInnerListTypeName(const QByteArray& typeName)
{
  QByteArray name = getInnerTemplateTypeName(typeName);
  if (name.isEmpty() && typeName.endsWith("List")) {
    name = typeName.left(typeName.length() - 4);
  }
  return name;
}

int PythonQtMethodInfo::nameToType(const char* name)
{
  if (_parameterTypeDict.isEmpty()) {
    // we could also use QMetaType::nameToType, but that does a string compare search
    // and does not support QVariant

    // QMetaType names
    _parameterTypeDict.insert("long", QMetaType::Long);
    _parameterTypeDict.insert("int", QMetaType::Int);
    _parameterTypeDict.insert("short", QMetaType::Short);
    _parameterTypeDict.insert("char", QMetaType::Char);
    _parameterTypeDict.insert("signed long", QMetaType::Long);
    _parameterTypeDict.insert("signed int", QMetaType::Int);
    _parameterTypeDict.insert("signed short", QMetaType::Short);
    _parameterTypeDict.insert("signed char", QMetaType::Char);
    _parameterTypeDict.insert("ulong", QMetaType::ULong);
    _parameterTypeDict.insert("unsigned long", QMetaType::ULong);
    _parameterTypeDict.insert("uint", QMetaType::UInt);
    _parameterTypeDict.insert("unsigned int", QMetaType::UInt);
    _parameterTypeDict.insert("ushort", QMetaType::UShort);
    _parameterTypeDict.insert("unsigned short", QMetaType::UShort);
    _parameterTypeDict.insert("uchar", QMetaType::UChar);
    _parameterTypeDict.insert("unsigned char", QMetaType::UChar);
    _parameterTypeDict.insert("bool", QMetaType::Bool);
    _parameterTypeDict.insert("float", QMetaType::Float);
    _parameterTypeDict.insert("double", QMetaType::Double);
    _parameterTypeDict.insert("qreal", QMetaType::Double);
    _parameterTypeDict.insert("QChar", QMetaType::QChar);
    _parameterTypeDict.insert("QByteArray", QMetaType::QByteArray);
    _parameterTypeDict.insert("QString", QMetaType::QString);
    _parameterTypeDict.insert("", QMetaType::Void);
    _parameterTypeDict.insert("void", QMetaType::Void);
    _parameterTypeDict.insert("QtMsgType", QMetaType::Int);

    // GL types
    _parameterTypeDict.insert("GLenum", QMetaType::UInt);
    _parameterTypeDict.insert("GLboolean", QMetaType::UChar);
    _parameterTypeDict.insert("GLbitfield", QMetaType::UInt);
    _parameterTypeDict.insert("GLbyte", QMetaType::Char);
    _parameterTypeDict.insert("GLubyte", QMetaType::UChar);
    _parameterTypeDict.insert("GLshort", QMetaType::Short);
    _parameterTypeDict.insert("GLushort", QMetaType::UShort);
    _parameterTypeDict.insert("GLint", QMetaType::Int);
    _parameterTypeDict.insert("GLuint", QMetaType::UInt);
    _parameterTypeDict.insert("GLsizei", QMetaType::UInt);
    _parameterTypeDict.insert("GLclampf", QMetaType::Float);
    _parameterTypeDict.insert("GLfloat", QMetaType::Float);
    _parameterTypeDict.insert("GLclampd", QMetaType::Double);
    _parameterTypeDict.insert("GLdouble", QMetaType::Double);
    _parameterTypeDict.insert("GLvoid", QMetaType::Void);
    if (QT_POINTER_SIZE == 8) {
      _parameterTypeDict.insert("qgl_GLintptr", QMetaType::LongLong);
      _parameterTypeDict.insert("qgl_GLsizeiptr", QMetaType::LongLong);
      _parameterTypeDict.insert("size_t", QMetaType::ULongLong);
    } else {
      _parameterTypeDict.insert("qgl_GLintptr", QMetaType::Int);
      _parameterTypeDict.insert("qgl_GLsizeiptr", QMetaType::Int);
      _parameterTypeDict.insert("size_t", QMetaType::UInt);
    }

#ifdef PYTHONQT_SUPPORT_ML_TYPES
    _parameterTypeDict.insert("MLuint8",  QMetaType::UChar);
    _parameterTypeDict.insert("MLint8",   QMetaType::Char);
    _parameterTypeDict.insert("MLuint16", QMetaType::UShort);
    _parameterTypeDict.insert("MLint16",  QMetaType::Short);
    _parameterTypeDict.insert("MLuint32", QMetaType::UInt);
    _parameterTypeDict.insert("MLint32",  QMetaType::Int);
    _parameterTypeDict.insert("MLuint64", QMetaType::ULongLong);
    _parameterTypeDict.insert("MLint64",  QMetaType::LongLong);
    _parameterTypeDict.insert("MLuint",   QMetaType::ULongLong);
    _parameterTypeDict.insert("MLint",    QMetaType::LongLong);
    _parameterTypeDict.insert("MLfloat",  QMetaType::Float);
    _parameterTypeDict.insert("MLdouble", QMetaType::Double);
#endif

    // QVariant names
    _parameterTypeDict.insert("Q_LLONG", QMetaType::LongLong);
    _parameterTypeDict.insert("Q_ULLONG", QMetaType::ULongLong);
    _parameterTypeDict.insert("qlonglong", QMetaType::LongLong);
    _parameterTypeDict.insert("qulonglong", QMetaType::ULongLong);
    _parameterTypeDict.insert("qint64", QMetaType::LongLong);
    _parameterTypeDict.insert("quint64", QMetaType::ULongLong);
    _parameterTypeDict.insert("QVariantHash", QMetaType::QVariantHash);
    _parameterTypeDict.insert("QVariantMap", QMetaType::QVariantMap);
    _parameterTypeDict.insert("QVariantList", QMetaType::QVariantList);
    _parameterTypeDict.insert("QHash<QString,QVariant>", QMetaType::QVariantHash);
    _parameterTypeDict.insert("QMap<QString,QVariant>", QMetaType::QVariantMap);
    _parameterTypeDict.insert("QList<QVariant>", QMetaType::QVariantList);
    _parameterTypeDict.insert("QStringList", QMetaType::QStringList);
    _parameterTypeDict.insert("QBitArray", QMetaType::QBitArray);
    _parameterTypeDict.insert("QDate", QMetaType::QDate);
    _parameterTypeDict.insert("QTime", QMetaType::QTime);
    _parameterTypeDict.insert("QDateTime", QMetaType::QDateTime);
    _parameterTypeDict.insert("QUrl", QMetaType::QUrl);
    _parameterTypeDict.insert("QLocale", QMetaType::QLocale);
    _parameterTypeDict.insert("QRect", QMetaType::QRect);
    _parameterTypeDict.insert("QRectF", QMetaType::QRectF);
    _parameterTypeDict.insert("QSize", QMetaType::QSize);
    _parameterTypeDict.insert("QSizeF", QMetaType::QSizeF);
    _parameterTypeDict.insert("QLine", QMetaType::QLine);
    _parameterTypeDict.insert("QLineF", QMetaType::QLineF);
    _parameterTypeDict.insert("QPoint", QMetaType::QPoint);
    _parameterTypeDict.insert("QPointF", QMetaType::QPointF);
    _parameterTypeDict.insert("QRegExp", QMetaType::QRegExp);
    _parameterTypeDict.insert("QFont", QMetaType::QFont);
    _parameterTypeDict.insert("QPixmap", QMetaType::QPixmap);
    _parameterTypeDict.insert("QBrush", QMetaType::QBrush);
    _parameterTypeDict.insert("QColor", QMetaType::QColor);
    _parameterTypeDict.insert("QCursor", QMetaType::QCursor);
    _parameterTypeDict.insert("QPalette", QMetaType::QPalette);
    _parameterTypeDict.insert("QIcon", QMetaType::QIcon);
    _parameterTypeDict.insert("QImage", QMetaType::QImage);
    _parameterTypeDict.insert("QRegion", QMetaType::QRegion);
    _parameterTypeDict.insert("QBitmap", QMetaType::QBitmap);
    _parameterTypeDict.insert("QSizePolicy", QMetaType::QSizePolicy);
    _parameterTypeDict.insert("QKeySequence", QMetaType::QKeySequence);
    _parameterTypeDict.insert("QPen", QMetaType::QPen);
    _parameterTypeDict.insert("QTextLength", QMetaType::QTextLength);
    _parameterTypeDict.insert("QTextFormat", QMetaType::QTextFormat);
    _parameterTypeDict.insert("QMatrix", QMetaType::QMatrix);
    _parameterTypeDict.insert("QVariant", PythonQtMethodInfo::Variant);
    // own special types... (none so far, could be e.g. ObjectList
  }
  QHash<QByteArray, int>::const_iterator it = _parameterTypeDict.find(name);
  if (it!=_parameterTypeDict.end()) {
    return it.value();
  } else {
    return PythonQtMethodInfo::Unknown;
  }
}

void PythonQtMethodInfo::cleanupCachedMethodInfos()
{
  QHashIterator<QByteArray, PythonQtMethodInfo *> i(_cachedSignatures);
  while (i.hasNext()) {
    delete i.next().value();
  }
  _cachedSignatures.clear();
  _cachedParameterInfos.clear();
}

void PythonQtMethodInfo::addParameterTypeAlias(const QByteArray& alias, const QByteArray& name)
{
  _parameterNameAliases.insert(alias, name);
}

const PythonQtMethodInfo::ParameterInfo& PythonQtMethodInfo::getParameterInfoForMetaType(int type)
{
  QHash<int, ParameterInfo>::ConstIterator it = _cachedParameterInfos.find(type);
  if (it != _cachedParameterInfos.constEnd()) {
    return it.value();
  }
  ParameterInfo info;
  fillParameterInfo(info, QMetaType::typeName(type));
  _cachedParameterInfos.insert(type, info);
  return _cachedParameterInfos[type];
}

//-------------------------------------------------------------------------------------------------

void PythonQtSlotInfo::deleteOverloadsAndThis()
{
  PythonQtSlotInfo* cur = this;
  while(cur->nextInfo()) {
    PythonQtSlotInfo* next = cur->nextInfo();
    delete cur;
    cur = next;
  }
}


QString PythonQtSlotInfo::fullSignature(bool skipReturnValue, int optionalArgsIndex) const
{ 
  int firstArgOffset = isInstanceDecorator()?2:1;
  QString result;
  QByteArray sig = slotName();
  QList<QByteArray> names = _meta.parameterNames();

  bool isStatic = false;
  bool isConstructor = false;
  bool isDestructor = false;

  if (_type == ClassDecorator) {
    if (sig.startsWith("new_")) {
      sig = sig.mid(4);
      isConstructor = true;
    }
    else if (sig.startsWith("py_q_")) {
      sig = sig.mid(5);
    }
    else if (sig.startsWith("delete_")) {
      sig = sig.mid(7);
      isDestructor = true;
    } else if(sig.startsWith("static_")) {
      isStatic = true;
      sig = sig.mid(7);
      int idx = sig.indexOf("_");
      if (idx>=0) {
        sig = sig.mid(idx+1);
      }
    }
  }

  result += sig;
  result += "(";

  for (int i = firstArgOffset; i<_parameters.count(); i++) {
    if ((optionalArgsIndex + firstArgOffset) == i) {
      result += " [";
    }
    if (i!=firstArgOffset) {
      result += ", ";
    }
    //if (_parameters.at(i).isConst) {
    //  result += "const ";
    //}
    if (_parameters.at(i).name == "bool" && _parameters.at(i).pointerCount == 1) {
      result += "PythonQt.BoolResult";
    } else {
      result += _parameters.at(i).name;
    }
    //if (_parameters.at(i).pointerCount) {
    //  QByteArray stars;
    //  stars.fill('*', _parameters.at(i).pointerCount);
    //  result += stars;
    //}
    if (!names.at(i-1).isEmpty()) {
      result += " ";
      result += names.at(i-1);
    }
  }
  if (optionalArgsIndex != -1) {
    result += "]";
  }
  result += ")";

  if (isStatic) {
    result = QString("static ") + result;
  } 
  if (isConstructor) {
//    result = QString("constructor ") + result;
  } 
  if (isDestructor) {
    result = QString("~") + result;
  } 

  if (!skipReturnValue) {
    if (!_parameters.at(0).name.isEmpty()) {
      result += " -> ";
      result += _parameters.at(0).name;
    }
  }
  return result;
}


QByteArray PythonQtSlotInfo::slotName(bool removeDecorators) const
{
  QByteArray name = PythonQtUtils::methodName(_meta);
  if (removeDecorators) {
    if (name.startsWith("py_q_")) {
      name = name.mid(5);
    } else 
    if (name.startsWith("static_")) {
      name = name.mid(7);
      int idx = name.indexOf("_");
      if (idx>=0) {
        name = name.mid(idx+1);
      }
    }
  }
  return name;
}

QByteArray PythonQtSlotInfo::signature() const
{
  return PythonQtUtils::signature(_meta);
}

QStringList PythonQtSlotInfo::overloads(bool skipReturnValue) const
{
  const PythonQtSlotInfo* info = this;
  QList<const PythonQtSlotInfo*> list;
  do {
    list << info;
    info = info->nextInfo();
  } while (info);

  QList<const PythonQtSlotInfo*> all = list;

  QStringList results;
  while (!list.isEmpty()) {
    const PythonQtSlotInfo* current = list.takeFirst();
    int minSameArgs = 1000;
    QList<PythonQtMethodInfo::ParameterInfo> currentArguments = current->arguments();
    int maxArgs = currentArguments.size();
    const PythonQtSlotInfo* maxArgSlot = current;
    QList<const PythonQtSlotInfo*> slotsWithSameArgs;
    slotsWithSameArgs << current;
    QMutableListIterator<const PythonQtSlotInfo*> it(all);
    while (it.hasNext()) {
      const PythonQtSlotInfo* other = it.next();
      // same slot...
      if (other == current) {
        continue;
      }
      // different return types...
      if (other->parameters().at(0).name != current->parameters().at(0).name) {
        continue;
      }
      QList<PythonQtMethodInfo::ParameterInfo> otherArguments = other->arguments();
      int paramCount = qMin(currentArguments.size(), otherArguments.size());
      int sameArgs = 0;
      for (int i = 0; i<paramCount; i++) {
        if (currentArguments.at(i).name == otherArguments.at(i).name) {
          sameArgs++;
        } else {
          break;
        }
      }
      if (sameArgs > 0 && sameArgs == paramCount) {
        slotsWithSameArgs << other;
        minSameArgs = qMin(sameArgs, minSameArgs);
        if (otherArguments.size() > maxArgs) {
          maxArgs = otherArguments.size();
          maxArgSlot = other;
          current = other;
          currentArguments = otherArguments;
        }
      }
    }
    if (slotsWithSameArgs.size() > 1) {
      results << maxArgSlot->fullSignature(skipReturnValue, minSameArgs);
      foreach(const PythonQtSlotInfo* o, slotsWithSameArgs) {
        list.removeOne(o);
      }
    } else {
      results << current->fullSignature(skipReturnValue);
    }
  }
  return results;
}

QList<PythonQtMethodInfo::ParameterInfo> PythonQtSlotInfo::arguments() const
{
  QList<PythonQtMethodInfo::ParameterInfo> result;
  for (int i = isInstanceDecorator()?2:1; i<_parameters.size(); i++) {
    result << _parameters[i];
  }
  return result;
}

QByteArray PythonQtSlotInfo::getImplementingClassName() const
{
  if (isInstanceDecorator()) {
    return _parameters.at(1).name;
  } else if (isClassDecorator()) {
    QByteArray name = PythonQtUtils::methodName(_meta);
    if (name.startsWith("static_")) {
      name = name.mid(7);
      int idx = name.indexOf("_");
      name = name.mid(0, idx);
      return name;
    } else {
      return name;
    }
  } else {
    return _meta.enclosingMetaObject()->className();
  }
}