#!/usr/bin/python2
# -*- coding: utf-8; mode: Python; indent-tabs-mode: t; tab-width: 4; python-indent: 4 -*-

# Copyright (C) 2012  Olga Yakovleva <yakovleva.o.v@gmail.com>

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import sys
import struct
import sexpr

class state(object):
	def __init__(self,index):
		self.index=index
		self.yes_transition=0
		self.no_transition=0
		self.letter_index=0
		self.letter=0
		self.phone_ids=[0]*4

class lts(object):
	def __init__(self,file_path):
		self.rules=dict()
		self.phones=["_epsilon_"]
		self.context_window_size=0
		entries=sexpr.read(file_path)
		if entries[0]=="set!":
			entries=entries[-1]
		for entry in entries:
			letter=ord(entry[0])
			tree=entry[1]
			states=list()
			self.rules[letter]=states
			self.load_tree(states,tree)

	def load_tree(self,states,tree):
		new_state=state(len(states))
		states.append(new_state)
		if len(tree)==1:
			phones=tree[0][-1].split("-")
			if len(phones)>4:
				raise RuntimeError("The multiphone {} is to long".format(tree[0][-1]))
			for i,phone in enumerate(phones):
				if phone in self.phones:
					new_state.phone_ids[i]=self.phones.index(phone)
				else:
					new_state.phone_ids[i]=len(self.phones)
					self.phones.append(phone)
		else:
			new_state.letter_index=(-1 if tree[0][0][0]=="p" else 1)*tree[0][0].count(".")
			self.context_window_size=max(self.context_window_size,abs(new_state.letter_index))
			if tree[0][-1]!=0:
				new_state.letter=ord(tree[0][-1])
			new_state.yes_transition=self.load_tree(states,tree[1])
			new_state.no_transition=self.load_tree(states,tree[2])
		return new_state.index

	def save(self,file_path):
		with open(file_path,"wb") as f:
			f.write(struct.pack("B",self.context_window_size))
			f.write(struct.pack("B",len(self.phones)))
			for phone in self.phones:
				s=phone.encode("utf-8")
				f.write(struct.pack("{}p".format(len(s)+1),s))
			f.write(struct.pack("B",len(self.rules)))
			for letter,states in self.rules.iteritems():
				f.write(struct.pack(">I",letter))
				f.write(struct.pack(">H",len(states)))
				for s in states:
					f.write(struct.pack("b",s.letter_index))
					if s.letter_index==0:
						f.write(struct.pack("4B",*s.phone_ids))
					else:
						f.write(struct.pack(">I2H",s.letter,s.yes_transition,s.no_transition))

if __name__=="__main__":
	lts(sys.argv[1]).save(sys.argv[2])
