import re, sys
import pprint
import gen_util_constants as util_constants

pp = pprint.PrettyPrinter(indent=4, stream=sys.stderr)


Label2Value = {
  "LABEL_OPTIONAL": 1,
  "LABEL_REQUIRED": 2,
  "LABEL_REPEATED": 3,
}

Num2Type = {
  1: "double",
  2: "float",
  3: "int64",    # not zigzag (proto3 compiler does not seem to use it)
  4: "uint64",
  5: "int32",    # not zigzag (proto3 compiler does not seem to use it)
  6: "uint64",
  7: "uint32",
  8: "bool",
  9: "string",
  10: None, #"group",   # group (deprecated in proto3)
  11: None, #"message", # another messsage
  12: "bytes",   # bytes
  13: "uint32",
  14: "enum",
  15: "int32",
  16: "int64",
  17: "int32", # Uses ZigZag encoding.
  18: "int64", # Uses ZigZag encoding.
}

Num2PbType = {
  1: "double",
  2: "float",
  3: "int64",    # not zigzag (proto3 compiler does not seem to use it)
  4: "uint64",
  5: "int32",    # not zigzag (proto3 compiler does not seem to use it)
  6: "fixed64",
  7: "fixed32",
  8: "bool",
  9: "string",
  10: None, #"group",   # group (deprecated in proto3)
  11: None, #"message", # another messsage
  12: "bytes",   # bytes
  13: "uint32",
  14: "enum",
  15: "sfixed32",
  16: "sfixed64",
  17: "sint32", # Uses ZigZag encoding.
  18: "sint64", # Uses ZigZag encoding.
}

Num2WireType = {
  1: "Fixed64",
  2: "Fixed32",
  3: "Varint",
  4: "Varint",
  5: "Varint",
  6: "Fixed64",
  7: "Fixed32",
  8: "Varint",
  9: "LengthDelim",
  10: None,
  11: "LengthDelim",
  12: "LengthDelim",
  13: "Varint",
  14: "Varint",
  15: "Fixed32",
  16: "Fixed64",
  17: "Varint",
  18: "Varint",
}

SolType2BodyLen = {
  "address": 20,
  "uint"   : 32,
    "uint8" : 1,
  "uint16" : 2,
  "uint24" : 3,
  "uint32" : 4,
  "uint40" : 5,
  "uint48" : 6,
  "uint56" : 7,
  "uint64" : 8,
  "uint72" : 9,
  "uint80" : 10,
  "uint88" : 11,
  "uint96" : 12,
  "uint104" : 13,
  "uint112" : 14,
  "uint120" : 15,
  "uint128" : 16,
  "uint136" : 17,
  "uint144" : 18,
  "uint152" : 19,
  "uint160" : 20,
  "uint168" : 21,
  "uint176" : 22,
  "uint184" : 23,
  "uint192" : 24,
  "uint200" : 25,
  "uint208" : 26,
  "uint216" : 27,
  "uint224" : 28,
  "uint232" : 29,
  "uint240" : 30,
  "uint248" : 31,
  "uint256" : 32,
  "int"    : 32,
  "int8" : 1,
  "int16" : 2,
  "int24" : 3,
  "int32" : 4,
  "int40" : 5,
  "int48" : 6,
  "int56" : 7,
  "int64" : 8,
  "int72" : 9,
  "int80" : 10,
  "int88" : 11,
  "int96" : 12,
  "int104" : 13,
  "int112" : 14,
  "int120" : 15,
  "int128" : 16,
  "int136" : 17,
  "int144" : 18,
  "int152" : 19,
  "int160" : 20,
  "int168" : 21,
  "int176" : 22,
  "int184" : 23,
  "int192" : 24,
  "int200" : 25,
  "int208" : 26,
  "int216" : 27,
  "int224" : 28,
  "int232" : 29,
  "int240" : 30,
  "int248" : 31,
  "int256" : 32,
  "bytes1" : 1,
  "bytes2" : 2,
  "bytes3" : 3,
  "bytes4" : 4,
  "bytes5" : 5,
  "bytes6" : 6,
  "bytes7" : 7,
  "bytes8" : 8,
  "bytes9" : 9,
  "bytes10": 10,
  "bytes11": 11,
  "bytes12": 12,
  "bytes13": 13,
  "bytes14": 14,
  "bytes15": 15,
  "bytes16": 16,
  "bytes17": 17,
  "bytes18": 18,
  "bytes19": 19,
  "bytes20": 20,
  "bytes21": 21,
  "bytes22": 22,
  "bytes23": 23,
  "bytes24": 24,
  "bytes25": 25,
  "bytes26": 26,
  "bytes27": 27,
  "bytes28": 28,
  "bytes29": 29,
  "bytes30": 30,
  "bytes31": 31,
  "bytes32": 32,
}

INTERNAL_TYPE_CATEGORY_BUILTIN = 1
INTERNAL_TYPE_CATEGORY_ENUM = 2
INTERNAL_TYPE_CATEGORY_USERTYPE = 3

TYPE_MESSAGE = 11
PB_LIB_NAME_PREFIX = ""
PB_CURRENT_PACKAGE = ""
LIBRARY_LINKING_MODE = False
ENUM_AS_CONSTANT = False
SOLIDITY_VERSION = "0.6.8"
SOLIDITY_PRAGMAS = []

# utils
def is_map_type(f, nested_type):
  if f.type_name.endswith("MapFieldEntry") and field_is_repeated(f):
    return len(list(filter(lambda t: t.name == "MapFieldEntry" and t.options and t.options.map_entry ,nested_type))) > 0
  return False

def to_camel_case(name):
  if "_" in name:
    return name.replace("_", " ").title().replace(" ", "")
  return name[:1].upper() + name[1:]

def add_prefix(prefix, name, sep = ""):
  return ("" if (prefix is None) else (prefix + sep)) + name

def parse_urllike_parameter(s):
  ret = {} #hash
  if s:
    for e in s.split('&'):
      kv = e.split('=')
      ret[kv[0]] = kv[1]
  return ret


def camel2snake(name):
  s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
  return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()

def field_is_message(f):
  return f.type == TYPE_MESSAGE and (not f.type_name.startswith(".solidity."))

def field_is_repeated(f):
  return f.label == Label2Value["LABEL_REPEATED"]

def field_has_dyn_size(f):
  # if string or bytes, dynamic
  if f.type == 9 or f.type == 12:
    return True
  elif f.type == TYPE_MESSAGE:
    # if non struct, message should be translate struct, which may have dynamic size
    # otherwise solidity native type, which should not have dynamic size
    return field_sol_type(f) == None
  else:
    return False

def field_pb_type(f):
  if f.type == TYPE_MESSAGE:
    return "message"
  return Num2PbType.get(f.type, None)

def field_sol_type(f):
  if f.type != TYPE_MESSAGE:
    return None
  elif f.type_name.startswith(".solidity."):
    return f.type_name.replace(".solidity.", "")
  else:
    return None

def prefix_lib_and_package(name):
  return PB_LIB_NAME_PREFIX + PB_CURRENT_PACKAGE + name

def prefix_lib(name):
  return PB_LIB_NAME_PREFIX + name

def gen_delegate_lib_name(msg, parent_struct_name):
  return prefix_lib_and_package(add_prefix(parent_struct_name, msg.name))

def gen_global_type_name_from_field(field):
  ftid, type_category = gen_field_type_id(field)
  if type_category == INTERNAL_TYPE_CATEGORY_BUILTIN:
    return ftid
  elif type_category == INTERNAL_TYPE_CATEGORY_ENUM:
    global ENUM_AS_CONSTANT
    return "int64" if ENUM_AS_CONSTANT else prefix_lib(ftid)
  elif type_category == INTERNAL_TYPE_CATEGORY_USERTYPE:
    return prefix_lib(ftid) + ".Data"

def is_complex_type(field):
  return "string" == field or "bytes" == field or ".Data" in field or "[]" in field

def gen_global_type_decl_from_field(field):
  tp = gen_global_type_name_from_field(field)
  if field_has_dyn_size(field):
    return tp + " memory"
  else:
    return tp

def gen_global_type_from_field(field):
  t = gen_global_type_name_from_field(field)
  if t is None:
    pp.pprint(field)
    pp.pprint("will die ======================================= ")
  if field_is_repeated(field):
    return t + "[]"
  else:
    return t

def gen_internal_struct_name(msg, parent_struct_name):
  return "Data"

def max_field_number(msg):
  num = 0
  for f in msg.field:
    if num < f.number:
      num = f.number
  return num

def str_contains(s, token):
  try:
    return s.index(token) >= 0
  except Exception as e:
    return False

def gen_struct_name_from_field(f):
  return "".join(map(lambda t: t[:1].upper() + t[1:], f.type_name[1:].split(".")))

def gen_enum_name_from_field(f):
  seps = f.type_name.split('.')
  return ('_'.join(seps[1:-1])) + "." + seps[-1]

def gen_field_type_id(field):
  val = Num2Type.get(field.type, None)
  if val != None:
    if val == "enum":
      return (gen_enum_name_from_field(field), INTERNAL_TYPE_CATEGORY_ENUM)
    return (val, INTERNAL_TYPE_CATEGORY_BUILTIN)
  val = field_sol_type(field)
  if val != None:
    return (val, INTERNAL_TYPE_CATEGORY_BUILTIN)
  return (gen_struct_name_from_field(field), INTERNAL_TYPE_CATEGORY_USERTYPE)

def gen_fieldtype(field, file):
  t = gen_global_type_name_from_field(field)
  if t[0] == ".":
    t = gen_global_enum_name(file) + t
  if field_is_repeated(field):
    return t + "[]"
  else:
    return t

def gen_enumvalue_entry(v):
  if v[0] == 0:
    return "{name}".format(
      name = v[1].name,
    )
  else:
    return ",\n    {name}".format(
      name = v[1].name,
    )

def gen_enumencoder_entry(v, name):
  return util_constants.ENUM_ENCODE_FUNCTION_INNER.format(
    name = v.name,
    value = v.number,
    enum_name = name
  )

def gen_enumdecoder_entry(v, name):
  return util_constants.ENUM_DECODE_FUNCTION_INNER.format(
    name = v.name,
    value = v.number,
    enum_name = name
  )

def gen_enumvalues(e):
  return ''.join(
    list(map(gen_enumvalue_entry, enumerate(e.value)))
  )

def gen_enum_encoders(e):
  return '\n'.join(
    list(map(lambda t: gen_enumencoder_entry(t, e.name), e.value))
  )

def gen_enum_decoders(e):
  return '\n'.join(
    list(map(lambda t: gen_enumdecoder_entry(t, e.name), e.value))
  )

def gen_enumtype(e):
  global ENUM_AS_CONSTANT
  if ENUM_AS_CONSTANT:
    return '\n'.join(
      list(map(lambda v: util_constants.ENUM_TYPE.format(
          type = e.name,
          name = v.name,
          value = v.number
        ),
      e.value))
    )
  else:
    definition = util_constants.ENUM_FUNCTION.format(
      enum_name = e.name,
      enum_values = gen_enumvalues(e)
    )
    encoder = util_constants.ENUM_ENCODE_FUNCTION.format(
      enum_name = e.name,
      enum_values = gen_enum_encoders(e)
    )
    decoder = util_constants.ENUM_DECODE_FUNCTION.format(
      enum_name = e.name,
      enum_values = gen_enum_decoders(e)
    )
    return definition + "\n" + encoder + "\n" + decoder

def gen_struct_decoder_name_from_field(field):
  ftid, _ = gen_field_type_id(field)
  return "_decode_" + ftid

def gen_struct_codec_lib_name_from_field(field):
  ftid, type_category = gen_field_type_id(field)
  if type_category != INTERNAL_TYPE_CATEGORY_USERTYPE:
    "___{} is not user type ({})___".format(field.name, ftid)
  return prefix_lib(ftid)

def gen_decoder_name(field):
  val = Num2PbType.get(field.type, None)
  if val != None:
    return "ProtoBufRuntime._decode_" + val
  else:
    val = field_sol_type(field)
    if val != None:
      return "ProtoBufRuntime._decode_sol_" + val
    return "_decode_" + "".join(map(lambda t: t[:1].upper() + t[1:], field.type_name.split(".")))

def gen_encoder_name(field):
  val = Num2PbType.get(field.type, None)
  if val != None:
    return "ProtoBufRuntime._encode_" + val
  else:
    val = field_sol_type(field)
    if val != None:
      return "ProtoBufRuntime._encode_sol_" + val
    return gen_struct_codec_lib_name_from_field(field) + "._encode_nested"

def gen_wire_type(field):
  return Num2WireType.get(field.type, None)

def gen_soltype_estimate_len(sol_type):
  val = SolType2BodyLen.get(sol_type, 0)
  return val + 3

def gen_global_enum_name(msg):
  return msg.name.replace(".", "_").upper() + "_" + "GLOBAL_ENUMS"

def change_pb_libname_prefix(new_name):
  global PB_LIB_NAME_PREFIX
  PB_LIB_NAME_PREFIX = new_name

def change_package_name(new_name):
  global PB_CURRENT_PACKAGE
  if new_name:
    PB_CURRENT_PACKAGE = "".join(map(lambda t: t[:1].upper() + t[1:], new_name.split(".")))
  else:
    PB_CURRENT_PACKAGE = ""

def current_package_name():
  global PB_CURRENT_PACKAGE
  return PB_CURRENT_PACKAGE

def is_lib_linking_mode():
  global LIBRARY_LINKING_MODE
  return LIBRARY_LINKING_MODE

def set_library_linking_mode():
  global LIBRARY_LINKING_MODE
  LIBRARY_LINKING_MODE = True
  global SOLIDITY_PRAGMAS
  SOLIDITY_PRAGMAS = ["pragma experimental ABIEncoderV2"]

def set_internal_linking_mode():
  global LIBRARY_LINKING_MODE
  LIBRARY_LINKING_MODE = False
  global SOLIDITY_PRAGMAS
  SOLIDITY_PRAGMAS = []

def set_solc_version(version):
  global SOLIDITY_VERSION
  SOLIDITY_VERSION = version

def set_enum_as_constant(on):
  global ENUM_AS_CONSTANT
  ENUM_AS_CONSTANT = on

def gen_visibility(is_decoder):
  if not LIBRARY_LINKING_MODE:
    return "internal"
  return "public" #"internal" if is_decoder else ""
