import cog
import jsonpickle

# [ GENERATE SECTION ]
def generate_private_members(name, property):
	prop_type = property['type']
	if(prop_type == "string"):
		handle_string_private_member(name, property)
	elif(prop_type == "number"):
		handle_number_private_member(name, property)
	elif(prop_type == "boolean"):
		handle_boolean_private_member(name, property)
	elif(prop_type == "array"):
		handle_array_private_member(name, property)

def generate_getters_setters(name, property):
  prop_type = property['type']
  if(prop_type == "string"):
    handle_string_getset(name, property)
  elif(prop_type == "number"):
    handle_number_getset(name, property)
  elif(prop_type == "boolean"):
    handle_boolean_getset(name, property)
  elif(prop_type == "array"):
    handle_array_getset(name, property)

def generate_properties(name, property):
  prop_type = property['type']
  if(prop_type == "string"):
    handle_string_property(name, property)
  elif(prop_type == "number"):
    handle_number_property(name, property)
  elif(prop_type == "boolean"):
    handle_boolean_property(name, property)
  elif(prop_type == "array"):
    handle_array_property(name, property)

def generate_class(schema):
  cog.out("class %s" % extract_classname(schema))
  superclass = extract_superclass(schema)
  if len(superclass) > 0:
    cog.out(f'({superclass})')
  cog.outl(":")

def generate_init(schema, superclass):
  
  properties = extract_schema_property_names_expand_superclass(extract_classname(schema))

  cog.out(f'\tdef __init__(self, ')

  for idx, property in enumerate(properties):
    cog.out(f'{property}')

    if idx < len(properties)-1:
      cog.out(', ')

  cog.out('):')
  cog.outl("")

  for idx, property in enumerate(properties):
    cog.outl(f'\t\tself.{property} = {property}')
# [ END GENERATE SECTION ]

# [ PRIVATE MEMBERS HANDLERS ]
def handle_boolean_private_member(name, property):
  cog.outl(f'\t__{name} = False # boolean')

def handle_boolean_property(name, property):
  cog.outl(f'\t{name} = property(_get_{name}, _set_{name}) #boolean')

def handle_number_private_member(name, property):
  cog.outl(f'\t__{name} = 0 # number')

def handle_number_property(name, property):
  cog.outl(f'\t{name} = property(_get_{name}, _set_{name}) #number')

def handle_string_private_member(name, property):
  cog.outl(f'\t__{name} = "" #string')

def handle_array_private_member(name, property):
  for array_type in property['items']:
    if(property['items'][array_type] == 'number'):
      handle_array_number_private_member(name, property)
      return # only support 1 array type
    elif(array_type == '$ref'):
          if(property['items'][array_type] == '#'):
            handle_array_element_private_member(name, property)
            return

def handle_array_number_private_member(name, property):
  cog.outl(f'\t__{name} = [] #number array')

def handle_array_element_private_member(name, property):
  cog.outl(f'\t__{name} = [] #element array')
# [ END PRIVATE MEMBERS SECTION ]

# [ PUBLIC PROPERTY HANDLERS ]
def handle_string_property(name, property):
  cog.outl(f'\t{name} = property(_get_{name}, _set_{name}) #string')
  pass

def handle_array_property(name, property):
  for array_type in property['items']:
    if(property['items'][array_type] == 'number'):
      maxItm = extract_max_items(property)
      minItm = extract_min_items(property)
      handle_array_number_property(name, property)
      return # only support 1 array type
    elif(array_type == '$ref'):
          if(property['items'][array_type] == '#'):
            handle_array_element_property(name, property)
            return

def handle_array_number_property(name, property):
  cog.outl(f'\t{name} = property(_get_{name}, _set_{name}) #number array')

def handle_array_element_property(name, property):
  cog.outl(f'\t{name} = property(_get_{name}, _set_{name}) #element array')
# [ END PUBLIC PROPERTY SECTION ]

# [ GET|SET HANDLERS ]
def handle_boolean_getset(name, property):
  cog.outl(f'\tdef _get_{name}(self):')
  cog.outl(f'\t\treturn self.__{name}')
  cog.outl("")

  cog.outl(f'\tdef _set_{name}(self, value):')
  cog.outl(f'\t\tif not isinstance(value, bool):')
  cog.outl(f'\t\t\traise TypeError("{name} must be set to a bool")')
  cog.outl(f'\t\tself.__{name} = value')
  cog.outl("")

def handle_number_getset(name, property):
  cog.outl(f'\tdef _get_{name}(self):')
  cog.outl(f'\t\treturn self.__{name}')
  cog.outl("")

  cog.outl(f'\tdef _set_{name}(self, value):')
  cog.outl(f'\t\tif not isinstance(value, int) and not isinstance(value, float):')
  cog.outl(f'\t\t\traise TypeError("{name} must be set to a float or int")')
  cog.outl(f'\t\tself.__{name} = value')
  cog.outl("")

def handle_string_getset(name, property):
  cog.outl(f'\tdef _get_{name}(self):')
  cog.outl(f'\t\treturn self.__{name}')
  cog.outl("")
  cog.outl(f'\tdef _set_{name}(self, value):')
  cog.outl(f'\t\tif not isinstance(value, str):')
  cog.outl(f'\t\t\traise TypeError("{name} must be set to a string")')
  cog.outl(f'\t\tself.__{name} = value')
  cog.outl("")

def handle_array_getset(name, property):
  for array_type in property['items']:
    if(property['items'][array_type] == 'number'):
      maxItm = extract_max_items(property)
      minItm = extract_min_items(property)
      handle_number_array_getset(name, property, minItm, maxItm)
      return # only support 1 array type
    elif(array_type == '$ref'):
      if(property['items'][array_type] == '#'):
        handle_element_array_getset(name, property)
        return

def handle_number_array_getset(name, property, minItems, maxItems):
  cog.outl(f'\tdef _get_{name}(self):')
  cog.outl(f'\t\t return self.__{name}')
  cog.outl("")
  cog.outl(f'\tdef _set_{name}(self, value):')
  cog.outl(f'\t\tif not isinstance(value, list):')
  cog.outl(f'\t\t\traise TypeError("{name} must be set to an array")')
  if minItems != -1:
    cog.outl(f'\t\tif len(value) < {minItems}:')
    cog.outl(f'\t\t\traise TypeError("{name} must have at least {minItems} elements")')
  if maxItems != -1:
    cog.outl(f'\t\tif len(value) > {maxItems}:')
    cog.outl(f'\t\t\traise TypeError("{name} cannot have more than {maxItems} elements")')
  cog.outl(f'\t\tfor element in value:')
  cog.outl(f'\t\t\tif not isinstance(element, int) and not isinstance(element, float):')
  cog.outl(f'\t\t\t\traise TypeError("{name} must only contain float or int %s" % str(type(element)))')
  cog.outl(f'\t\tself.__{name} = value')
  cog.outl("")

def handle_element_array_getset(name, property):
  cog.outl(f'\tdef _get_{name}(self):')
  cog.outl(f'\t\t return self.__{name}')
  cog.outl("")
  cog.outl(f'\tdef _set_{name}(self, value):')
  cog.outl(f'\t\tif not isinstance(value, list):')
  cog.outl(f'\t\t\traise TypeError("{name} must be set to an array")')
  cog.outl(f'\t\tfor elem in value:')
  cog.outl(f'\t\t\tif not isinstance(elem, element):')
  cog.outl(f'\t\t\t\traise TypeError("{name} can only contain elements")')
  cog.outl(f'\t\tself.__{name} = value')
  cog.outl("")
  cog.outl(f'\tdef add_element(self, elem):')
  cog.outl(f'\t\tif not isinstance(elem, element):')
  cog.outl(f'\t\t\traise TypeError("{name} can only contain elements")')
  cog.outl(f'\t\tself.__elements.append(elem)')
  cog.outl("")
  cog.outl(f'\tdef remove_element(self, element):')
  cog.outl(f'\t\tself.__elements.remove(element)')
# [ END GET|SET SECTION ]

# [ ARRAY UTILITIES ]
def extract_max_items(property):
	if(property['maxItems']):
		return property['maxItems']

	return -1

def extract_min_items(property):
	if(property['minItems']):
		return property['minItems']

	return -1
# [ END ARRAY UTILITIES SECTION ]

# [ CLASS UTILITIES ]
def extract_classname(schema):
	return schema['title']

def extract_superclass(schema):
  if "$ref" in schema:
    print("$ref found")
    fullname = schema['$ref']
    split_name = fullname.split(":")
    return split_name.pop() # return the last string in the split 'schema:element' should return 'element'
  return ""

def extract_schema(classname):
  classname = classname.lower()
  file_path = f'../schemas/{classname}_schema.json'
  schema = ''

  with open(file_path, 'r') as file:
    file_content = file.read()
    schema = jsonpickle.decode(file_content)
  
  return schema

def extract_schema_property_names(classname):
  schema = extract_schema(classname)
  properties = []

  for property in schema['properties']:
    properties.append(property)

  return properties

def extract_schema_property_names_expand_superclass(classname):
  print(f'Extracting properties for {classname}')
  all_properties = []
  target_class = classname

  while len(target_class) > 0:
    schema = extract_schema(target_class)
    new_properties = extract_schema_property_names(target_class)
    new_properties.reverse() # when unreversed later, matches declaration order of parent classes
    all_properties.extend(new_properties)
    target_class = extract_superclass(schema)

  all_properties.reverse()
  return all_properties





# [ END CLASS UTILITIES SECTION ]
