#!/usr/bin/python
# Dynalib device definition compiler

import os.path

def parseInt(string):
	string = string.lower()
	if string.startswith('0x'):
		return int(string, 16)
	else:
		return int(string)

class Device:
	class Register:
		EEPROM=0
		RAM=1

		def __init__(self, addr, size, name, flags, reg_type):
			self.addr = addr
			self.size = size
			self.name = name
			self.flags = flags
			self.reg_type = reg_type
			self.type = None

		def __lt__(self, other):
			return self.addr < other.addr

		def compile(self):
			return "public: LEValue<%d> %s;" % (self.size, self.name)

	class DummyRegister(Register):
		def compile(self):
			return "private: uint8_t __dummy[%d];" % self.size

	def __init__(self):
		self.regs = []
		self.current_section = "RAM"
		self.model_number = 0
		self.name = "unnamed"
		pass

	def reg(self, addr):
		return [ reg for reg in self.regs if reg.addr == addr][0]

	def parseFile(self, file):
		for line in open(file):
			if len(line.strip()) == 0:
				continue

			tokens = line.strip().split()

			if tokens[0] == "include":
				self.parseFile(os.path.dirname(file) + "/" + tokens[1])
			elif tokens[0] == "reg":
				self.regs.append(Device.Register(
					addr=parseInt(tokens[1]),
					size=parseInt(tokens[2]),
					name=tokens[3],
					flags=tokens[4:],
					reg_type=self.current_section
				))
			elif tokens[0] == "section":
				if tokens[1] == "RAM":
					self.current_section = Device.Register.RAM
				elif tokens[1] == "EEPROM":
					self.current_section = Device.Register.EEPROM
			elif tokens[0] == "model":
				self.model_number = parseInt(tokens[1])
			elif tokens[0] == "name":
				self.name = tokens[1]
			elif tokens[0] == "type":
				self.reg(int(tokens[1])).type = tokens[2]
			else:
				raise RuntimeError, "Unknown command '%s'" % tokens[0]

	def sanitizeRegisters(self):
		self.regs.sort()

		i = 1
		while i < len(self.regs):
			prev = self.regs[i-1]
			next = self.regs[i]

			delta = next.addr - prev.size - prev.addr

			assert delta >= 0, "Overlapping registers %s and %s" % (prev.name, next.name)

			if delta != 0:
				self.regs.insert(i, Device.DummyRegister(
					addr=prev.addr + prev.size,
					size=delta,
					name="ZZ_DUMMY_%d" % i,
					flags=[],
					reg_type=Device.Register.RAM
				))

			i += 1

	def compileRegisters(self):
		yield "\n"
		for i, reg in enumerate(self.regs):
			yield "template<>"
			yield "struct %s::Register<%s::%s>" % (self.name, self.name, reg.name)
			yield "{"

			# Compile-time information
			yield "\tenum { Address = %d, Size = %d, FirstAddress = %d };" % (reg.addr, reg.size, reg.addr)

			# Compile-time navigation
			if i == 0:
				yield "\tstruct Pred {};"
			else:
				yield "\ttypedef %s::Register<%d> Pred;" % (self.name, i-1)

			if i != len(self.regs)-1:
				yield "\ttypedef %s::Register<%d> Succ;" % (self.name, i+1)

			# Contents
			if isinstance(reg, Device.DummyRegister):
				yield "private:"
				yield "\tuint8_t dummy[%d];" % reg.size
			else:
				if reg.type:
					yield "\ttypedef typename TypedLEValue<%d, %s>::Type Type;" % (reg.size, reg.type)
					yield "\tTypedLEValue<%d, %s> %s;" % (reg.size, reg.type, reg.name)
				else:
					yield "\ttypedef typename LEValue<%d>::Type Type;" % reg.size
					yield "\tLEValue<%d> %s;" % (reg.size, reg.name)

				yield "\tinline Type get() const { return %s(); }" % reg.name
				yield "\tinline void set(Type val) { %s = val; }" % reg.name

			yield "} __attribute__((packed));"

	def compileRegisterSwitch(self):
		for reg in self.regs:
			if isinstance(reg, Device.DummyRegister):
				continue

			yield "case %s:" % reg.name
			yield '\treturn RegisterInfo("%s", %d, %d, %d, %s);' % (
				reg.name, reg.addr, reg.size, reg.reg_type,
				' | '.join(['0'] + ['RegisterInfo::FLAG_%s' % flag.upper() for flag in reg.flags])
			)

	def compile(self):
		self.sanitizeRegisters()

		return """
// dynalib device header for device %(name)s
// This file is AUTOGENERATED by dynalib_dc.py. Do not edit by hand!

#ifndef DYNALIB_DEVICES_%(name)s_H
#define DYNALIB_DEVICES_%(name)s_H

#include <dynalib/device.h>
#include <dynalib/le_value.h>

namespace dynalib
{

class %(name)s : public Device
{
public:
	inline const char* name() const
	{ return "%(name)s"; }

	inline uint16_t model() const
	{ return %(model)d; }

	enum RegisterAddress
	{
		%(register_enum)s
	};

	template<int Addr>
	class Register
	{};

	RegisterInfo registerInfo(unsigned int index) const
	{
		switch(index)
		{
			%(register_switch)s
			default:
				return RegisterInfo();
		}
	}

	unsigned int registerCount() const
	{ return %(register_count)d; }

	uint8_t readSize() const
	{ return %(read_size)d; }

	template<int First, int Last>
	struct RegionFromTo : public MergePred<Register<Last>, Last-First>
	{
		enum
		{
			FIRST = First, LAST = Last,
			Size = MergePred<Register<Last>, Last-First>::Size,
			FirstAddress = Register<First>::Address
		};
	} __attribute__((packed));

	template<int First, int... Tail>
	struct Region : public RegionFromTo<RegionHelper<First, Tail...>::First, RegionHelper<First, Tail...>::Last>
	{
	} __attribute__((packed));
};

%(registers)s

}

#endif
""" % {
	'name': self.name,
	'register_enum': '\n\t\t'.join(['%s,' % reg.name for reg in self.regs]),
	'register_switch': '\n\t\t\t'.join(self.compileRegisterSwitch()),
	'registers': '\n'.join(self.compileRegisters()),
	'register_count': len(self.regs),
	'model': self.model_number,
	'read_size': self.regs[-1].addr + self.regs[-1].size
}

	def compileImpl(self, headerPath):
		return """
// dynalib device header for device %(name)s
// This file is AUTOGENERATED by dynalib_dc.py. Do not edit by hand!

#include "%(header)s"

#include <dynalib/device_registry.h>

namespace dynalib
{
DeviceRegisterer registerer_%(name)s("%(name)s", %(model)d, new %(name)s);
}
""" % {
	'name': self.name,
	'header': headerPath,
	'model': self.model_number
}


if __name__ == "__main__":
	import sys

	dev = Device()
	dev.parseFile(sys.argv[1])

	open(sys.argv[2], 'w').write(dev.compile())
	open(sys.argv[3], 'w').write(dev.compileImpl(sys.argv[2]))

