def only(x):
	if len(x) != 1:
		raise ValueError
	return list(x)[0]

GlobalIdCounter = 0
def GetId():
	global GlobalIdCounter
	GlobalIdCounter += 1
	return GlobalIdCounter
def GetUID():
	return f"x{int(GetId())}"

def FixCasing(Str):
	NewStr = ""
	NextUpperCase = True
	for c in Str:
		if NextUpperCase:
			NextUpperCase = False
			NewStr += c.upper()
		else:
			if c == "_":
				NextUpperCase = True
			else:
				NewStr += c.lower()
	return NewStr

def FormatName(typ, name):
	if "*" in typ:
		return "m_p" + FixCasing(name)
	if "[]" in typ:
		return "m_a" + FixCasing(name)
	return "m_" + FixCasing(name)

class BaseType:
	def __init__(self, type_name):
		self._type_name = type_name
		self._target_name = "INVALID"
		self._id = GetId() # this is used to remember what order the members have in structures etc

	def Identifier(self):
		return "x"+str(self._id)
	def TargetName(self):
		return self._target_name
	def TypeName(self):
		return self._type_name
	def Id(self):
		return self._id

	def EmitDeclaration(self, name):
		return [f"{self.TypeName()} {FormatName(self.TypeName(), name)};"]
	def EmitPreDefinition(self, target_name):
		self._target_name = target_name
		return []
	def EmitDefinition(self, _name):
		return []

class MemberType:
	def __init__(self, name, var):
		self.name = name
		self.var = var

class Struct(BaseType):
	def __init__(self, type_name):
		BaseType.__init__(self, type_name)
	def Members(self):
		def sorter(a):
			return a.var.Id()
		m = []
		for name, value in self.__dict__.items():
			if name[0] == "_":
				continue
			m += [MemberType(name, value)]
		m.sort(key = sorter)
		return m

	def EmitTypeDeclaration(self, _name):
		lines = []
		lines += ["struct " + self.TypeName()]
		lines += ["{"]
		for member in self.Members():
			lines += ["\t"+l for l in member.var.EmitDeclaration(member.name)]
		lines += ["};"]
		return lines

	def EmitPreDefinition(self, target_name):
		BaseType.EmitPreDefinition(self, target_name)
		lines = []
		for member in self.Members():
			lines += member.var.EmitPreDefinition(target_name+"."+member.name)
		return lines
	def EmitDefinition(self, _name):
		lines = [f"/* {self.TargetName()} */ {{"]
		for member in self.Members():
			lines += ["\t" + " ".join(member.var.EmitDefinition("")) + ","]
		lines += ["}"]
		return lines

class Array(BaseType):
	def __init__(self, typ):
		BaseType.__init__(self, typ.TypeName())
		self.type = typ
		self.items = []
	def Add(self, instance):
		if instance.TypeName() != self.type.TypeName():
			raise ValueError("bah")
		self.items += [instance]
	def EmitDeclaration(self, name):
		return [f"int m_Num{FixCasing(name)};",
			f"{self.TypeName()} *{FormatName('[]', name)};"]
	def EmitPreDefinition(self, target_name):
		BaseType.EmitPreDefinition(self, target_name)

		lines = []
		i = 0
		for item in self.items:
			lines += item.EmitPreDefinition(f"{self.Identifier()}[{int(i)}]")
			i += 1

		if self.items:
			lines += [f"static {self.TypeName()} {self.Identifier()}[] = {{"]
			for item in self.items:
				itemlines = item.EmitDefinition("")
				lines += ["\t" + " ".join(itemlines).replace("\t", " ") + ","]
			lines += ["};"]
		else:
			lines += [f"static {self.TypeName()} *{self.Identifier()} = 0;"]

		return lines
	def EmitDefinition(self, _name):
		return [str(len(self.items))+","+self.Identifier()]

# Basic Types

class Int(BaseType):
	def __init__(self, value):
		BaseType.__init__(self, "int")
		self.value = value
	def Set(self, value):
		self.value = value
	def EmitDefinition(self, _name):
		return [f"{int(self.value)}"]
		#return ["%d /* %s */"%(self.value, self._target_name)]

class Float(BaseType):
	def __init__(self, value):
		BaseType.__init__(self, "float")
		self.value = value
	def Set(self, value):
		self.value = value
	def EmitDefinition(self, _name):
		return [f"{self.value:f}f"]
		#return ["%d /* %s */"%(self.value, self._target_name)]

class String(BaseType):
	def __init__(self, value):
		BaseType.__init__(self, "const char*")
		self.value = value
	def Set(self, value):
		self.value = value
	def EmitDefinition(self, _name):
		return ['"'+self.value+'"']

class Pointer(BaseType):
	def __init__(self, typ, target):
		BaseType.__init__(self, f"{typ().TypeName()}*")
		self.target = target
	def Set(self, target):
		self.target = target
	def EmitDefinition(self, _name):
		return ["&"+self.target.TargetName()]

class TextureHandle(BaseType):
	def __init__(self):
		BaseType.__init__(self, "IGraphics::CTextureHandle")
	def EmitDefinition(self, _name):
		return ["IGraphics::CTextureHandle()"]

# helper functions

def EmitTypeDeclaration(root):
	for l in root().EmitTypeDeclaration(""):
		print(l)

def EmitDefinition(root, name):
	for l in root.EmitPreDefinition(name):
		print(l)
	print(f"static {root.TypeName()} {name} = ")
	for l in root.EmitDefinition(name):
		print(l)
	print(";")

# Network stuff after this

class Object:
	pass

class Enum:
	def __init__(self, name, values, start = 0):
		self.name = name
		self.values = values
		self.start = start

class Flags:
	def __init__(self, name, values):
		self.name = name
		self.values = values

class NetObject:
	def __init__(self, name, variables, ex=None, validate_size=True):
		l = name.split(":")
		self.name = l[0]
		self.base = None
		self.base_struct_name = None
		if len(l) > 1:
			self.base = l[1]
			self.base_struct_name = f"CNetObj_{self.base}"
		self.struct_name = f"CNetObj_{self.name}"
		self.enum_name = f"NETOBJTYPE_{self.name.upper()}"
		self.variables = variables
		self.ex = ex
		self.validate_size = validate_size

	def emit_declaration(self):
		lines = []
		if self.base is not None:
			lines += [f"struct {self.struct_name} : public {self.base_struct_name}", "{"]
		else:
			lines += [f"struct {self.struct_name}", "{"]
		lines += [f"\tstatic constexpr int ms_MsgId = {self.enum_name};"]
		for v in self.variables:
			lines += ["\t"+line for line in v.emit_declaration()]
		lines += ["};"]
		return lines

	def members_from_this_and_parents(self, objects):
		variables = self.variables
		next_base_name = self.base
		while next_base_name is not None:
			base_item = only([i for i in objects if i.name == next_base_name])
			variables = base_item.variables + variables
			next_base_name = base_item.base
		return variables

	def emit_uncompressed_unpack_and_validate(self, objects):
		lines = []
		lines += [f"case {self.enum_name}:"]
		lines += ["{"]
		lines += [f"\t{self.struct_name} *pData = ({self.struct_name} *)m_aUnpackedData;"]
		unpack_lines = []

		variables = self.members_from_this_and_parents(objects)
		for v in variables:
			if not self.validate_size and v.default is None:
				raise ValueError(f"{v.name} in {self.name} has no default value. Member variables that do not have a default value cannot be used in a structure whose size is not validated.")
			unpack_lines += ["\t"+line for line in v.emit_uncompressed_unpack_obj()]
		for v in variables:
			unpack_lines += ["\t"+line for line in v.emit_validate_obj()]

		if len(unpack_lines) > 0:
			lines += unpack_lines
		else:
			lines += ["\t(void)pData;"]
		lines += ["} break;"]
		return lines

	def emit_dump(self, objects):
		lines = []
		lines += [f"case {self.enum_name}:"]
		lines += ["{"]
		lines += [f"\t{self.struct_name} *pObj = ({self.struct_name} *)pData;"]
		unpack_lines = []
		variables = self.members_from_this_and_parents(objects)
		offset = 0
		for v in variables:
			unpack_lines += ["\t"+line for line in v.emit_dump(offset)]
			offset += v.num_emit_dump_offsets()

		if len(unpack_lines) > 0:
			lines += unpack_lines
		else:
			lines += ["\t(void)pData;"]
		lines += ["return 0;"]
		lines += ["};"]
		return lines

class NetEvent(NetObject):
	def __init__(self, name, variables, ex=None):
		NetObject.__init__(self, name, variables, ex=ex)
		if self.base is not None:
			self.base_struct_name = f"CNetEvent_{self.base}"
		self.struct_name = f"CNetEvent_{self.name}"
		self.enum_name = f"NETEVENTTYPE_{self.name.upper()}"

class NetMessage(NetObject):
	def __init__(self, name, variables, ex=None, teehistorian=True):
		NetObject.__init__(self, name, variables, ex=ex)
		if self.base is not None:
			self.base_struct_name = f"CNetMsg_{self.base}"
		self.struct_name = f"CNetMsg_{self.name}"
		self.enum_name = f"NETMSGTYPE_{self.name.upper()}"
		self.teehistorian = teehistorian

	def emit_unpack_msg(self):
		lines = []
		lines += [f"case {self.enum_name}:"]
		lines += ["{"]
		lines += [f"\t{self.struct_name} *pData = ({self.struct_name} *)m_aUnpackedData;"]

		unpack_lines = []
		for v in self.variables:
			unpack_lines += ["\t"+line for line in v.emit_unpack_msg()]
		for v in self.variables:
			unpack_lines += ["\t"+line for line in v.emit_unpack_msg_check()]

		if len(unpack_lines) > 0:
			lines += unpack_lines
		else:
			lines += ["\t(void)pData;"]
		lines += ["} break;"]
		return lines

	def emit_declaration(self):
		extra = []
		extra += ["\t"]
		extra += ["\tbool Pack(CMsgPacker *pPacker) const"]
		extra += ["\t{"]
		#extra += ["\t\tmsg_pack_start(%s, flags);"%self.enum_name]
		for v in self.variables:
			extra += ["\t\t"+line for line in v.emit_pack()]
		extra += ["\t\treturn pPacker->Error() != 0;"]
		extra += ["\t}"]

		lines = NetObject.emit_declaration(self)
		lines = lines[:-1] + extra + lines[-1:]
		return lines

class NetObjectEx(NetObject):
	def __init__(self, name, ex, variables, validate_size=True):
		NetObject.__init__(self, name, variables, ex=ex, validate_size=validate_size)

class NetEventEx(NetEvent):
	def __init__(self, name, ex, variables):
		NetEvent.__init__(self, name, variables, ex=ex)

class NetMessageEx(NetMessage):
	def __init__(self, name, ex, variables, teehistorian=True):
		NetMessage.__init__(self, name, variables, ex=ex)


class NetVariable:
	def __init__(self, name, *, default=None):
		self.name = name
		self.default = None if default is None else str(default)
	def emit_declaration(self):
		return []
	def emit_validate_obj(self):
		return []
	def emit_uncompressed_unpack_obj(self):
		return []
	def emit_pack(self):
		return []
	def emit_unpack_msg(self):
		return []
	def emit_unpack_msg_check(self):
		return []
	def num_emit_dump_offsets(self):
		return 1
	def emit_dump(self, offset):
		return [f"str_format(aRawData, sizeof(aRawData), \"\\t\\t%3d %12d\\t%08x\", {offset}, ((const int *)pData)[{offset}], ((const int *)pData)[{offset}]);"]

class NetString(NetVariable):
	def emit_declaration(self):
		return [f"const char *{self.name};"]
	def emit_uncompressed_unpack_obj(self):
		return self.emit_unpack_msg()
	def emit_unpack_msg(self):
		return [f"pData->{self.name} = pUnpacker->GetString();"]
	def emit_pack(self):
		return [f"pPacker->AddString({self.name}, -1);"]

class NetStringHalfStrict(NetVariable):
	def emit_declaration(self):
		return [f"const char *{self.name};"]
	def emit_uncompressed_unpack_obj(self):
		return self.emit_unpack_msg()
	def emit_unpack_msg(self):
		return [f"pData->{self.name} = pUnpacker->GetString(CUnpacker::SANITIZE_CC);"]
	def emit_pack(self):
		return [f"pPacker->AddString({self.name}, -1);"]

class NetStringStrict(NetVariable):
	def emit_declaration(self):
		return [f"const char *{self.name};"]
	def emit_uncompressed_unpack_obj(self):
		return self.emit_unpack_msg()
	def emit_unpack_msg(self):
		return [f"pData->{self.name} = pUnpacker->GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES);"]
	def emit_pack(self):
		return [f"pPacker->AddString({self.name}, -1);"]

class NetIntAny(NetVariable):
	def emit_declaration(self):
		return [f"int {self.name};"]
	def emit_uncompressed_unpack_obj(self):
		if self.default is None:
			return [f"pData->{self.name} = pUnpacker->GetUncompressedInt();"]
		return [f"pData->{self.name} = pUnpacker->GetUncompressedIntOrDefault({self.default});"]
	def emit_unpack_msg(self):
		if self.default is None:
			return [f"pData->{self.name} = pUnpacker->GetInt();"]
		return [f"pData->{self.name} = pUnpacker->GetIntOrDefault({self.default});"]
	def emit_pack(self):
		return [f"pPacker->AddInt({self.name});"]
	def emit_dump(self, offset):
		return NetVariable(self.name).emit_dump(offset) + \
			[f"dbg_msg(\"snapshot\", \"%s\\t{self.name}=%d\", aRawData, pObj->{self.name});"]

class NetIntRange(NetIntAny):
	def __init__(self, name, min_val, max_val, *, default=None):
		NetIntAny.__init__(self,name,default=default)
		self.min = str(min_val)
		self.max = str(max_val)
	def emit_validate_obj(self):
		return [f"pData->{self.name} = ClampInt(\"{self.name}\", pData->{self.name}, {self.min}, {self.max});"]
	def emit_unpack_msg_check(self):
		return [f"if(pData->{self.name} < {self.min} || pData->{self.name} > {self.max}) {{ m_pMsgFailedOn = \"{self.name}\"; break; }}"]
	def emit_dump(self, offset):
		min_fmt=f"min={self.min}"
		min_arg = ''
		try:
			int(self.min)
		except ValueError:
			min_fmt = f"min={self.min}(%d)"
			min_arg = f", (int){self.min}"
		max_fmt=f"max={self.max}"
		max_arg = ''
		try:
			int(self.max)
		except ValueError:
			max_fmt = f"max={self.max}(%d)"
			max_arg = f", (int){self.max}"
		return NetVariable(self.name).emit_dump(offset) + \
			[f"dbg_msg(\"snapshot\", \"%s\\t{self.name}=%d ({min_fmt} {max_fmt})\", aRawData, pObj->{self.name}{min_arg}{max_arg});"]

class NetBool(NetIntRange):
	def __init__(self, name, *, default=None):
		default = None if default is None else int(default)
		NetIntRange.__init__(self,name,0,1,default=default)

class NetTick(NetIntAny):
	def __init__(self, name, *, default=None):
		NetIntAny.__init__(self,name,default=default)
	def emit_dump(self, offset):
		return NetVariable(self.name).emit_dump(offset) + \
			[f"dbg_msg(\"snapshot\", \"%s\\t{self.name}=%d (NetTick)\", aRawData, pObj->{self.name});"]

class NetArray(NetVariable):
	def __init__(self, var, size):
		NetVariable.__init__(self,var.name,default=var.default)
		self.base_name = var.name
		self.var = var
		self.size = size
		self.name = self.base_name + f"[{int(self.size)}]"
	def emit_declaration(self):
		self.var.name = self.name
		return self.var.emit_declaration()
	def emit_uncompressed_unpack_obj(self):
		lines = []
		for i in range(self.size):
			self.var.name = self.base_name + f"[{int(i)}]"
			lines += self.var.emit_uncompressed_unpack_obj()
		return lines
	def emit_validate_obj(self):
		lines = []
		for i in range(self.size):
			self.var.name = self.base_name + f"[{int(i)}]"
			lines += self.var.emit_validate_obj()
		return lines
	def emit_unpack_msg(self):
		lines = []
		for i in range(self.size):
			self.var.name = self.base_name + f"[{int(i)}]"
			lines += self.var.emit_unpack_msg()
		return lines
	def emit_pack(self):
		lines = []
		for i in range(self.size):
			self.var.name = self.base_name + f"[{int(i)}]"
			lines += self.var.emit_pack()
		return lines
	def emit_unpack_msg_check(self):
		lines = []
		for i in range(self.size):
			self.var.name = self.base_name + f"[{int(i)}]"
			lines += self.var.emit_unpack_msg_check()
		return lines
	def num_emit_dump_offsets(self):
		return self.size
	def emit_dump(self, offset):
		result = []
		for i in range(0, self.size):
			result += NetVariable(self.var).emit_dump(offset + i)
			result += [f"dbg_msg(\"snapshot\", \"%s\\t{self.base_name}[{int(i)}]=%d\", aRawData, pObj->{self.base_name}[{int(i)}]);"]
		return result

class NetTwIntString(NetArray):
	def __init__(self, name, size_chars):
		if size_chars % 4 != 0:
			raise ValueError(f"NetTwIntString '{name}' size must be divisible by 4 but is {size_chars}")
		NetArray.__init__(self, NetIntAny(name), size_chars // 4)
	def emit_dump(self, offset):
		result = []
		for i in range(0, self.size):
			result += [f"aInts[0] = pObj->{self.base_name}[{int(i)}];"]
			result += ["IntsToStr(aInts, std::size(aInts), aStr, std::size(aStr));"]
			result += NetVariable(self.var).emit_dump(offset + i)
			result += [f"dbg_msg(\"snapshot\", \"%s\\t{self.base_name}[{int(i)}]=%d\\tIntToStr: %s\", aRawData, pObj->{self.base_name}[{int(i)}], aStr);"]
		return result
