/*
 *  Copyright (C) 2005-2018 Team Kodi
 *  This file is part of Kodi - https://kodi.tv
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  See LICENSES/README.md for more information.
 */

import Helper
import SwigTypeParser

public class PythonTools
{
   /**
    * This array defines a mapping of the api spec type to the python parse format character.
    *  By default, if a lookup here results in 'null' then the format char is 'O'
    */
   private static Map ltypeToFormatChar = [
      'p.char':"s", bool:"b",
      int:"i", 'unsigned int' : 'I',
      long:"l", 'unsigned long' : 'k',
      'double':"d", 'float':"f",
      'long long' : "L"
   ]

   /**
    * if the parameter can be directly read from python then its type should be in the ltypeToFormatChar
    *  otherwise we need an intermediate pyobject
    */
   public static boolean parameterCanBeUsedDirectly(Node param) { return ltypeToFormatChar[SwigTypeParser.convertTypeToLTypeForParam(param.@type)] != null }

   /**
    * This method will take the parameter list from the method node passed
    * and will convert it to a Python argument string for PyArg_ParseTupleAndKeywords
    */
   public static String makeFormatStringFromParameters(Node method)
   {
      if (!method)
        return ''
      List params = method.parm
      String format = ""
      boolean previousDefaulted = false
      params.eachWithIndex { param, i ->
         String defaultValue = param.@value
         String paramtype = SwigTypeParser.convertTypeToLTypeForParam(param.@type)
         String curFormat = ltypeToFormatChar[paramtype];
         if (curFormat == null) // then we will assume it's an object
            curFormat = "O";

         if (defaultValue != null && !previousDefaulted)
         {
            format +="|"
            previousDefaulted = true
         }
         format += curFormat
      }
      return format;
   }

   /**
    * This method gets the FULL class name as a variable including the
    * namespace. If converts all of the '::' references to '_' so
    * that the result can be used in part, or in whole, as a variable name
    */
   public static String getClassNameAsVariable(Node clazz) { return Helper.findFullClassName(clazz).replaceAll('::','_') }

   public static String getPyMethodName(Node method, MethodType methodType)
   {
      String clazz = Helper.findFullClassName(method)?.replaceAll('::','_')

      // if we're not in a class then this must be a method node
      assert (clazz != null || methodType == MethodType.method), 'Cannot use a non-class function as a constructor or destructor ' + method

      // it's ok to pass a 'class' node if the methodType is either constructor or destructor
      assert (method.name() != 'class' || (methodType == MethodType.constructor || methodType == MethodType.destructor))

      // if this is a constructor node then the methodtype best reflect that
      assert (method.name() != 'constructor' || methodType == MethodType.constructor), 'Cannot use a constructor node and not identify the type as a constructor' + method

      // if this is a destructor node then the methodtype best reflect that
      assert (method.name() != 'destructor' || methodType == MethodType.destructor), 'Cannot use a destructor node and not identify the type as a destructor' + method

      if (clazz == null)
        return method.@sym_name

      if (methodType == MethodType.constructor)
        return clazz + "_New"

      if (methodType == MethodType.destructor)
        return clazz + "_Dealloc"

      if (method.@name.startsWith("operator "))
      {
        if ("[]" == method.@name.substring(9))
          return clazz + "_operatorIndex_"

        if ("()" == method.@name.substring(9))
          return clazz + "_callable_"
      }

      return clazz + "_" + method.@sym_name;
   }

  public static String makeDocString(Node docnode)
  {
    if (docnode?.name() != 'doc')
      throw new RuntimeException("Invalid doc Node passed to PythonTools.makeDocString (" + docnode + ")")

    String[] lines = (docnode.@value).split(Helper.newline)
    def ret = ''
    lines.eachWithIndex { val, index ->
      val = ((val =~ /\\n/).replaceAll('')) // remove extraneous \n's
      val = val.replaceAll("\\\\","\\\\\\\\") // escape backslash
      val = ((val =~ /\"/).replaceAll("\\\\\"")) // escape quotes
      ret += ('"' + val + '\\n"' + (index != lines.length - 1 ? Helper.newline : ''))
    }

    return ret
  }

  public static Node findValidBaseClass(Node clazz, Node module, boolean warn = false)
  {
    // I need to find the base type if there is a known class with it
    assert clazz.baselist.size() < 2, "${clazz} has multiple baselists - need to write code to separate out the public one."
    String baseclass = 'NULL'
    List knownbases = []
    if (clazz.baselist)
    {
      if (clazz.baselist[0].base) clazz.baselist[0].base.each {
          Node baseclassnode = Helper.findClassNodeByName(module,it.@name,clazz)
          if (baseclassnode) knownbases.add(baseclassnode)
          else if (warn && !Helper.isKnownBaseType(it.@name,clazz))
            System.out.println("WARNING: the base class ${it.@name} for ${Helper.findFullClassName(clazz)} is unrecognized within ${module.@name}.")
        }
    }
    assert knownbases.size() < 2,
      "The class ${Helper.findFullClassName(clazz)} has too many known base classes. Multiple inheritance isn't supported in the code generator. Please \"#ifdef SWIG\" out all but one."
    return knownbases.size() > 0 ? knownbases[0] : null
  }
}
