#!/usr/bin/env python
# -*- coding: utf-8 -*-

#    Parasol
#    Copyright (C) 2008 GnunuX http://www.gnunux.info/
#
#    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 os.path

class solfile:
	def __init__(self, file=None, debug='no'):
		self.file = file
		self.datatypes = {'number': '\x00','boolean': '\x01','string': '\x02','object': '\x03','null': '\x05','undefined': '\x06','array': '\x08','c_object': '\x10', 'date': '\x0B','xml': '\x0F'}
		debug='yes'
		self.debug=debug


	def _add_binary(self, head):
		if self.binary != '':
#			if self.debug != 'no':
#				print repr(self.binary)
			self.binarytab.append('%s: %s' % (head, self.binary))
			self.binary = ''

	def _checkheader(self):
		#unknown 2 bytes
		self._testbytes(2, '\x00\xbf')
		#size (cardinal)
		self.totallen = self._bytes2int('!i')
		self._add_binary('fileheader')
		#TCSO + unknown 6 bytes
		self._testbytes(10, '\x54\x43\x53\x4F\x00\x04\x00\x00\x00\x00')
		#globalname
		self.globalname = self._returnbytes(self._bytes2int('!h'))
		#unknown block of 4 bytes of 00 sometime: '\x00\x00\x00\x03'
		self._testbytes(4, '\x00\x00\x00\x00', '\x00\x00\x00\x03')
		self._add_binary('header')

	def _createheader(self, globalname):
		self._createbytes('\x54\x43\x53\x4F\x00\x04\x00\x00\x00\x00')
		self._createbytes(str(self._int2bytes(len(globalname), '!h')))
		self._createbytes(globalname)
		self._createbytes('\x00\x00\x00\x00')
		self._add_binary('header')

	def _returnbytes(self, len):
		if self.totallen != '':
			self.totallen = int(self.totallen)-int(len)
		val = self.solfile.read(len)
		self.binary += val
		return val

	def _createbytes(self, val='', sync='no'):
		self.binary += val
		self.solcontent += val
		return len(val)

	def _bytes2int(self, typeval):
		bytesint = [['!h', '!l', '!d', '!i'], [2, 4, 8, 4]]
		val = self._returnbytes(bytesint[1][bytesint[0].index(typeval)])
		return struct.unpack(typeval, val)[0]

	def _int2bytes(self, val, typeval):
		return struct.pack(typeval, val)

	def _testbytes(self, len, pattern, notpattern=''):
		issol = self._returnbytes(len)
		if issol != pattern:
			if issol == notpattern:
				if self.debug != 'no':
					print self.binarytab
					print repr(self.binary)
				print '%s is not a standard solfile' % self.file
			else:
				if self.debug != 'no':
					print self.binarytab
					print repr(self.binary)
				print 'testbytes error: %s - %s for file %s' % (repr(pattern), repr(issol), self.file)
			sys.exit(1)

	def _readcontent(self, init='no'): 
		if init == 'yes':
			name = self.globalname
			datatype = 'object'
		else:
			self._add_binary('content')
			if self.totallen == 0:
				return 2
			#length of the name
			len = self._bytes2int('!h')
			if len == 0:
				endbytes = self._returnbytes(1)
				if endbytes == '\x09':
					return 2
			#name
			name = self._returnbytes(len)
			#type
			type = self._returnbytes(1)
			if type == self.datatypes['number']:
				datatype = 'number'
				value = self._bytes2int('!d')
			elif type == self.datatypes['boolean']:
				datatype = 'boolean'
				if self._returnbytes(1) == '\x00':
					value = 'False'
				else:
					value = 'True'
			elif type == self.datatypes['string']:
				datatype = 'string'
				lenght = self._bytes2int('!h')
				value = self._returnbytes(lenght)
			elif type == self.datatypes['object']:
				datatype = 'object'
			elif type == self.datatypes['null']:
				datatype = 'null'
				value = 'NULL'
		
			elif type == self.datatypes['undefined']:
				datatype = 'undefined'
				value = ''
			elif type == self.datatypes['array']:
				datatype = 'array'
				arraylen = self._bytes2int('!l')
			elif type == self.datatypes['c_object']:
				datatype = 'c_object'
				lenght = self._bytes2int('!h')
				classname = self._returnbytes(lenght)
			elif type == self.datatypes['date']:
				datatype = 'date'
				#time = self._returnbytes(8)
				time = self._bytes2int('!d')
				timezone = self._bytes2int('!h')
				value = [time, timezone/-60]
			elif type == self.datatypes['xml']:
				datatype = 'xml'
				lenght = self._bytes2int('!l')
				value = self._returnbytes(lenght)
			else:
				if self.debug != 'no':
					print self.binarytab
					print repr(self.binary)
				print 'unknown type %s : name %s : %s for file %s' % (repr(type), repr(name), repr(self._returnbytes(3)), self.file)
				sys.exit(1)
		if datatype == 'object' or datatype == 'array' or datatype == 'c_object':
			code = ''
			returnagain = {}
			while code != 2:
				code = self._readcontent()
				if code != 2:
					key = code.keys()[0]
					returnagain[key] = code[key]
				if init == 'yes' and self.totallen != 0:
					self._testbytes(1, '\x00')
				if self.totallen == 0:
					code = 2
			if datatype == 'c_object':
				returnvalue = {name: [datatype, returnagain, classname]}
			elif datatype == 'array':
				returnvalue = {name: [datatype, returnagain, arraylen]}
			else:
				returnvalue = {name: [datatype, returnagain]}
		else:
			returnvalue = {name: [datatype, value]}
		return returnvalue

	def _createcontent(self, value, init='no'):
		for name in value.keys():
			self._add_binary('content')
			self._createbytes(self._int2bytes(len(name), '!h'))
			self._createbytes(name)
			typeval = value[name][0]
			self._createbytes(self.datatypes[typeval])
			if typeval == 'number':
				self._createbytes(self._int2bytes(value[name][1], '!d'))
			elif typeval == 'boolean':
				if value[name][1] == 'False':
					self._createbytes('\x00')
				else:
					self._createbytes('\x01')
			elif typeval == 'string':
				self._createbytes(self._int2bytes(len(value[name][1]), '!h'))
				self._createbytes(value[name][1])
			elif typeval == 'object':
				self._createcontent(value[name][1])
			elif typeval == 'array':
				self._createbytes(self._int2bytes(value[name][2], '!l'))
				self._createcontent(value[name][1])
			elif typeval == 'c_object':
				classname = value[name][2]
				self._createbytes(self._int2bytes(len(classname), '!h'))
				self._createbytes(classname)
				self._createcontent(value[name][1])
			elif typeval == 'date':
				self._createbytes(self._int2bytes(value[name][1][0], '!d'))
				self._createbytes(self._int2bytes(value[name][1][1], '!h')*-60)
			elif typeval == 'xml':
				self._createbytes(self._int2bytes(len(value[name][1]), '!l'))
				self._createbytes(value[name][1])
			elif not typeval == 'undefined' and not typeval == 'null':
				print 'unsupported: ' + typeval
			if typeval == 'object' or typeval == 'array' or typeval == 'c_object':
				self._add_binary('content')
				self._createbytes('\x00\x00\x09')
			if init == 'yes':
				self._createbytes('\x00')



	def get_sol(self):
		self.binary = ''
		self.binarytab = []
		self.totallen = ''
		if self.file == None:
			print 'self.file must not be None'
			sys.exit(1)
		self.solfile=open(self.file, 'rb')
		self._checkheader()
		solcontent = self._readcontent(init='yes')
		self.solfile.close()
		self._add_binary('content')
		return solcontent

	def set_sol(self, value):
		self.binary = ''
		self.binarytab = []
		self.totallen = ''
		self.solcontent = ''
		self.solcontenttmp = ''
		globalname = value.keys()[0]
		self._createheader(globalname)
		self._createcontent(value[globalname][1], 'yes')
		self._add_binary('content')
		lenght = len(self.solcontent)
		#start header (with len)
		self.startheader = '\x00\xbf' + self._int2bytes(lenght, '!i')
		self.binary = self.startheader
		self._add_binary('fileheader')
		self.solcontent = self.startheader + self.solcontent
		if self.file != None:
			solfile=open(self.file, 'wb')
			solfile.write(self.solcontent)
			solfile.close()

	def print_debug(self):
		return self.binarytab

class soldico:
	def __init__(self, contentsol):
		self.contentsol=contentsol
		if type(contentsol) != dict:
			print 'not a dictionary'
			sys.exit(1)

	def display(self): 
		return self.contentsol
	def add(self, filtername, filterseparator, datatype, data):
		if data == None:
			defaultvalue = {'number': 0,'boolean': False,'string': '','object': {},'null': '','undefined': '','array': {},'c_object': '', 'date': '','xml': ''}
			data = defaultvalue[datatype]
		contenttmp = self.contentsol
		filtervals = filtername.split(filterseparator)
		for filterval in filtervals[:-1]:
			contenttmp = contenttmp[filterval][1]
		contenttmp[filtervals[-1]] = [datatype, data]
	def modify(self, filtername, filterseparator, data):
		contenttmp = self.contentsol
		filtervals = filtername.split(filterseparator)
		for filterval in filtervals[:-1]:
			contenttmp = contenttmp[filterval][1]
		print contenttmp[filtervals[-1]]
		datatype = contenttmp[filtervals[-1]][0]
		contenttmp[filtervals[-1]] = [datatype, data]
	def delete(self, filtername, filterseparator):
		contenttmp = self.contentsol
		filtervals = filtername.split(filterseparator)
		for filterval in filtervals[:-1]:
			contenttmp = contenttmp[filterval][1]
		del contenttmp[filtervals[-1]]




def quit_with_error(message):
	print message
	sys.exit(1)

from optparse import OptionParser
parser = OptionParser('usage: %prog [options] filename')
parser.add_option('-c', '--create', action='store_true', dest='createit', default=False,
	help='Create a new file')
parser.add_option('-v', '--verify', action="store_true", dest='verifyit', default=False,
	help='Test to read the given file testing if he is compatible')
parser.add_option('-f', '--filter', dest='filtername',
	help='Add filter (for example : settings_secureCrossDomainCacheSize)')
parser.add_option('-s', '--separator', dest='separator',
	help='Separator for filter')
parser.add_option('-D', '--debug', action="store_true", dest='debugit', default=False,
	help='Print debug information')
parser.add_option('-a', '--action', dest='actionsol', help='Action: add or del'),
parser.add_option('-t', '--type', dest='datatype',
	help='Add booleon'),
parser.add_option('-d', '--data', dest='data', help='Data')
(options, args) = parser.parse_args()

if len(args) != 1:
	quit_with_error('Need filename. Please, read the help')

filename = args[0]

if os.path.isfile(filename) and options.createit == True or not os.path.isfile(filename) and options.createit == False:
	quit_with_error('Problem with file. Please, read the help')

if options.createit == True and options.verifyit == True:
	quit_with_error('Cannot create and verify. Please, read the help')

if options.actionsol != None:
	if options.actionsol not in ['del', 'add', 'mod']:
		quit_with_error('Unknown option for action. Please, read the help')
	if options.filtername == None:
		quit_with_error('Need filter. Please, read the help')
	if options.actionsol == 'add' and options.datatype == None:
		quit_with_error('Need type. Please, read the help')

if options.datatype != None:
	if options.datatype not in ['array', 'boolean', 'c_object', 'date', 'number', 'null', 'object', 'string', 'undefined', 'xml']:
		quit_with_error('Unknown option for type. Please, read the help')
	if options.actionsol != 'add':
		quit_with_error('Datatype option need --action add. Please, read the help')

if options.data != None and options.datatype == None:
	 quit_with_error('Need datatype. Please, read the help')

if options.separator == None:
	filterseparator = "_"
else:
	if len(options.separator) != 1:
		quit_with_error('Only one caracter for separator. Please, read the help')
	filterseparator = options.separator

if options.createit == True:
	content = {'settings': ['object', {}]}
else:
	readsolfile = solfile(filename)
	content = readsolfile.get_sol()
manipcontent = soldico(content)

if options.verifyit == True:
	debugreadfile = readsolfile.print_debug()
	testsolfile = solfile()
	testsolfile.set_sol(content)
	debugwritefile = testsolfile.print_debug()
	if options.debugit == True:
		print debugreadfile
		print "---"
		print debugwritefile
	if debugreadfile.sort() == debugwritefile.sort():
		print 'ok'
		sys.exit(0)
	else:
		print 'error with %s ! please send me this file !' % filename
		sys.exit(1)

if options.actionsol != None:
	if options.actionsol == 'del':
		manipcontent.delete(options.filtername, filterseparator)
	elif options.actionsol == 'add':
		manipcontent.add(options.filtername, filterseparator, options.datatype, options.data)
	elif options.actionsol == 'mod':
		manipcontent.modify(options.filtername, filterseparator, options.data)
	readsolfile.set_sol(content)
else:
	if options.createit == True:
		writesolfile = solfile(filename)
		writesolfile.set_sol(content)
	else:
		if options.debugit == True:
			print testsolfile.print_debug()
		print manipcontent.display()



