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

import os
import sys
import time
import scipy as sp
from optparse import OptionParser
import unicodedata as un
import itertools as itert

import keymap as km
import myutil
import keys

OUT_PATH = 'layouts'
OUT_FILE_NAME = 'layout.txt'

# [ <plain>, <shift>, <altgr>, <algr+shift>]
KEYINFO_MODES = [0, 1, 2, 3]
KEYINFO_MODE_NORMAL = 0
KEYINFO_MODE_SHIFT = 1
KEYINFO_MODE_ALTGR = 2
KEYINFO_MODE_ALTGRSHIFT = 3

LETTERINFO_INDEX_DISTANCE = 0

CONFIGURATION_SCHEME = 'balanced'
# FINGER_SCHEME = 'left' 
# FINGER_SCHEME = 'right'
FINGER_SCHEME = 'leftright' 

KEYINFO_INDEX_MODE = 0
KEYINFO_INDEX_ROW = 1
KEYINFO_INDEX_FINGER = 2
KEYINFO_INDEX_BASE = 3
KEYINFO_INDEX_PENALTY = 4
KEYINFO_INDEX_IS_RELOCATABLE = 5
KEYINFO_INDEX_IS_SPOT = 6
KEYINFO_INDEX_IS_LETTER = 7
KEYINFO_INDEX_SYMBOL = 8
KEYINFO_SIZE = 9

LEFT_HAND = ['q', 'w', 'e', 'r', 't', 'a', 's', 'd', 'f', 'g', 'v', 'b']
RIGHT_HAND = ['y', 'u', 'i', 'o', 'p', 'ü', 'õ', 'h', 'j', 'k', 'l', 'l', 'ö', 'ä', """'""", 'n', 'm', ',', '.', '-']
NOPRIME = ['v', 'b', 'n', 'm', ',', '.', '-', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', 'ü', 'õ', """'"""]

NUMBER_OF_PERMUTATIONS_AT_ONCE = 1000

CURRENT_CONSTRAINT_SET = 0
LAYOUT_CONSTRAINTS = [
	{
	'e' : LEFT_HAND[:],
	'i' : LEFT_HAND[:],
	'a' : LEFT_HAND[:],
	'u' : LEFT_HAND[:],
	'o' : LEFT_HAND[:],
	't' : LEFT_HAND[:],
	
	's' : RIGHT_HAND[:],
	'l' : RIGHT_HAND[:],
	'd' : RIGHT_HAND[:],
	'k' : RIGHT_HAND[:],
	'v' : RIGHT_HAND[:],
	'p' : RIGHT_HAND[:],
	'n' : RIGHT_HAND[:],
	'm' : RIGHT_HAND[:],
	'r' : RIGHT_HAND[:],
	'b' : RIGHT_HAND[:],
	
	'f' : RIGHT_HAND + LEFT_HAND,
	'g' : RIGHT_HAND + LEFT_HAND,
	'h' : RIGHT_HAND + LEFT_HAND,
	'j' : RIGHT_HAND + LEFT_HAND,
	'q' : RIGHT_HAND + LEFT_HAND,
	'w' : RIGHT_HAND + LEFT_HAND,
	'õ' : RIGHT_HAND + LEFT_HAND,
	'ä' : RIGHT_HAND + LEFT_HAND,
	'ö' : RIGHT_HAND + LEFT_HAND,
	'ü' : RIGHT_HAND + LEFT_HAND,
	'y' : RIGHT_HAND + LEFT_HAND,	
	
	'v' : LEFT_HAND[:],
	'z' : ['z'], 
	'x' : ['x'], 
	'c' : ['c'],
	
	'.' : NOPRIME,
	',' : NOPRIME,
	'-' : NOPRIME,
	"""'""" : NOPRIME
	},
	{
	'e' : RIGHT_HAND[:],
	'i' : RIGHT_HAND[:],
	'a' : RIGHT_HAND[:],
	'u' : RIGHT_HAND[:],
	'o' : RIGHT_HAND[:],	
	't' : RIGHT_HAND[:],
			
	's' : LEFT_HAND[:],
	'l' : LEFT_HAND[:],
	'd' : LEFT_HAND[:],
	'k' : LEFT_HAND[:],
	'p' : LEFT_HAND[:],
	'n' : LEFT_HAND[:],
	'm' : LEFT_HAND[:],
	'r' : LEFT_HAND[:],
	'b' : LEFT_HAND[:],	
	
	'f' : RIGHT_HAND + LEFT_HAND,
	'g' : RIGHT_HAND + LEFT_HAND,
	'h' : RIGHT_HAND + LEFT_HAND,
	'j' : RIGHT_HAND + LEFT_HAND,
	'q' : RIGHT_HAND + LEFT_HAND,
	'w' : RIGHT_HAND + LEFT_HAND,
	'õ' : RIGHT_HAND + LEFT_HAND,
	'ä' : RIGHT_HAND + LEFT_HAND,
	'ö' : RIGHT_HAND + LEFT_HAND,
	'ü' : RIGHT_HAND + LEFT_HAND,
	'y' : RIGHT_HAND + LEFT_HAND,	
	
	'v' : LEFT_HAND[:],
	'z' : ['z'], 
	'x' : ['x'], 
	'c' : ['c'],	
	
	'.' : NOPRIME,
	',' : NOPRIME,
	'-' : NOPRIME,
	"""'""" : NOPRIME		
	}						
]


def readTsvFile(sFile, keyList = keys.RELOCATABLES):
	
	rows = []
	for line in sFile:
		line = line.rstrip()
		rows.append(line.split("\t"))
		
	symbolPairHash = {}
	headerRow = rows[0]
	
	rowCounter = 1	
	for row in rows[1:]:
		firstSymbol = row[0]
		firstSymbol = keys.PRINT_TO_KEY.get(firstSymbol, firstSymbol)
		if firstSymbol in keyList:
			symbolPairHash[firstSymbol] = {}
		
			columnCounter = 1	
			for pairCount in row[1:]:
				secondSymbol = headerRow[columnCounter]
				secondSymbol = keys.PRINT_TO_KEY.get(secondSymbol, secondSymbol)
				if secondSymbol in keyList:
					symbolPairHash[firstSymbol][secondSymbol] = pairCount
					
				columnCounter += 1
			
		rowCounter += 1

	return symbolPairHash

def loadSymbolMatrix(symbolPairHash, keyList = keys.RELOCATABLES, ddtype = float):
	symbolCount = len(keyList)
	
	symbolMatrix = sp.zeros([symbolCount, symbolCount], dtype = ddtype)
	rowCounter = 0
	for firstSymbol in keyList:
		
		if firstSymbol in symbolPairHash:		
			columnCounter = 0	
			for secondSymbol in keyList:
				if secondSymbol in symbolPairHash[firstSymbol]:
					symbolMatrix[rowCounter, columnCounter] = symbolPairHash[firstSymbol][secondSymbol]
				columnCounter += 1
			
		rowCounter += 1
		
	return symbolMatrix

def translateConstraints(keyList = keys.RELOCATABLES):
	relocatablesHash = dict((keyList[i], i) for i in range(len(keyList)))
	
	tConstraints = []
	for constraintSet in LAYOUT_CONSTRAINTS:
		tConstraintSet = {}
		for symbol, constraintList in constraintSet.iteritems():
			tConstraintList = []
			for constraintSymbol in constraintList:				
				if constraintSymbol in relocatablesHash:
					tConstraintList.append(relocatablesHash[constraintSymbol])
			tConstraintSet[relocatablesHash[symbol]] = tConstraintList
		tConstraints.append(tConstraintSet)
			
	return tConstraints
	
def get_layout(mapping):
	"""load the keyboard and create letter to stats mapping"""
	"""returns a tuple: 1) a list of symbols and their properties from the layout 2) a hashtable letter to index number in the list"""

	symbolList = []
	symbolHash = {}
	counter = 0
	emptyKeyData = [None] * KEYINFO_SIZE

	for i in range(len(km.KEYBOARD)):
		row = km.KEYBOARD[i]

		for j in range(len(row)):			
			button = km.LAYOUT[mapping][i][j]
			buttonKeyData = emptyKeyData[:]
			finger = km.BUTTON_TO_FINGER[FINGER_SCHEME][i][j]
			buttonKeyData[KEYINFO_INDEX_BASE] = row[0]
			buttonKeyData[KEYINFO_INDEX_FINGER] = finger
			buttonKeyData[KEYINFO_INDEX_ROW] = i

			if button[-1] == False:
				buttonKeyData[KEYINFO_INDEX_IS_SPOT] = False				
			else:
				buttonKeyData[KEYINFO_INDEX_IS_SPOT] = True

			for modeIndex in range(len(button)):
				key = button[modeIndex]
				
				if key == False: continue
				
				symbolKeyData = buttonKeyData[:]
				symbolKeyData[KEYINFO_INDEX_MODE] = modeIndex
#				symbolKeyData[KEYINFO_INDEX_PENALTY] = configuration[CONFIGURATION_SCHEME][PENALTY][MAIN][0] +\
#					configuration[CONFIGURATION_SCHEME][PENALTY][MAIN][0] * configuration[CONFIGURATION_SCHEME][PENALTY][HAND][ FINGER_TO_HAND[finger] ] +\
#					configuration[CONFIGURATION_SCHEME][PENALTY][MAIN][1] * configuration[CONFIGURATION_SCHEME][PENALTY][ROW][i] +\
#					configuration[CONFIGURATION_SCHEME][PENALTY][MAIN][2] * configuration[CONFIGURATION_SCHEME][PENALTY][FINGER][finger] +\
#					configuration[CONFIGURATION_SCHEME][PENALTY][MAIN][3] * configuration[CONFIGURATION_SCHEME][PENALTY][MODE][modeIndex]

				if key != None:
					symbolHash[key] = counter

					if key in keys.RELOCATABLES:
						symbolKeyData[KEYINFO_INDEX_IS_RELOCATABLE] = True
					else:
						symbolKeyData[KEYINFO_INDEX_IS_RELOCATABLE] = False

					if len(key) != 1 or un.category(unicode(key, 'utf-8')) in ['Ll', 'Lu']:
						symbolKeyData[KEYINFO_INDEX_IS_LETTER] = True
					else:
						symbolKeyData[KEYINFO_INDEX_IS_LETTER] = False

				symbolKeyData[KEYINFO_INDEX_SYMBOL] = key
				symbolList.append(symbolKeyData)
				counter += 1
	return (symbolList, symbolHash)

def bigPair(spArray):	
	sortedArg = sp.argsort(spArray, axis = None)[::-1]
	xs, ys = sp.unravel_index(sortedArg, spArray.shape)
	for coord in zip(xs, ys):
		yield coord
		
def permute(permitted):
	pr = itert.product(*permitted)	
	pLen = len(permitted)
	return itert.ifilter(lambda a: len(set(a)) == pLen, pr)

def effort1(statsMatrix, locationMatrix, permutationMatrix):		
	pairEfforts = (locationMatrix.ravel()[(
		permutationMatrix[:, None] + (permutationMatrix[:,:, None] * locationMatrix.shape[1])).ravel()
		]).reshape(permutationMatrix.shape[0], permutationMatrix.shape[1], permutationMatrix.shape[1])
	return sp.sum(pairEfforts * statsMatrix, axis = (1,2)) 

def getStatEffortMatrix(statsMatrix, effortMatrix):
	lenx, leny = statsMatrix.shape
	
	result = sp.zeros((lenx * lenx, leny * leny), dtype = long)
	for i in range(lenx):
		for k in range(leny):
			for j in range(lenx):
				for l in range(leny):
					result[i * lenx + k, j * leny + l] = statsMatrix[i,j] * effortMatrix[k,l]
			
	return result
	
		
def tableSearch(statsMatrix, effortMatrix, keyList = keys.RELOCATABLES):
	dims = statsMatrix.shape
	symbolIndexInAction = {}
	incrementalStats = sp.zeros(dims, dtype = long)
	constraints = translateConstraints(keyList)[CURRENT_CONSTRAINT_SET]
	emptyPermutationMatrix = sp.tile(sp.arange(len(keyList), dtype = int), (NUMBER_OF_PERMUTATIONS_AT_ONCE, 1))
			
	bigPairs = iter(bigPair(statsMatrix))
	swapTime = 0
	effortTime = 0
	
	while True:
		bigCoord1, bigCoord2 = bigPairs.next()
		print keyList[bigCoord1], keyList[bigCoord2], statsMatrix[bigCoord1, bigCoord2]
		hasNew = False
		if bigCoord1 not in symbolIndexInAction:
			symbolIndexInAction[bigCoord1] = True
			incrementalStats[bigCoord1, symbolIndexInAction.keys()] = statsMatrix[bigCoord1, symbolIndexInAction.keys()]
			incrementalStats[symbolIndexInAction.keys(), bigCoord1] = statsMatrix[symbolIndexInAction.keys(), bigCoord1]
#			incrementalStats[bigCoord1, :] = statsMatrix[bigCoord1]
#			incrementalStats[:, bigCoord1] = statsMatrix[:, bigCoord1]
			hasNew = True
		if bigCoord2 not in symbolIndexInAction:
			symbolIndexInAction[bigCoord2] = True
			incrementalStats[bigCoord2, symbolIndexInAction.keys()] = statsMatrix[bigCoord2, symbolIndexInAction.keys()]
			incrementalStats[symbolIndexInAction.keys(), bigCoord2] = statsMatrix[symbolIndexInAction.keys(), bigCoord2]
#			incrementalStats[bigCoord2] = statsMatrix[bigCoord2]
#			incrementalStats[:, bigCoord2] = statsMatrix[:, bigCoord2]
			hasNew = True
			
		if not hasNew: continue
		
		currentSymbolIndexesInAction = symbolIndexInAction.keys()
		
#		currentConstraints = {}				
#		for symbolIndex in symbolIndexInAction.keys():
#			currentConstraints[symbolIndex] = constraints[symbolIndex]
			
		currentConstraintList = map(lambda k: constraints[k], currentSymbolIndexesInAction)
		
		swapsIterator = permute(currentConstraintList)
		permutationMatrix = emptyPermutationMatrix.copy()
		
		minEffort = 100000000000
		
#		print currentConstraintList
		for permuteList in swapsIterator:
#			print permuteList
			
			swapstart = time.time()
			permutationMatrix[0][currentSymbolIndexesInAction] = permuteList
			i = 1
			for permuteList in itert.islice(swapsIterator, NUMBER_OF_PERMUTATIONS_AT_ONCE - 1):
#				print permuteList	
				permutationMatrix[i][currentSymbolIndexesInAction] = permuteList
				i += 1
			swapTime += (time.time() - swapstart)
			
			effortStart = time.time()
			effortList = effort1(incrementalStats, effortMatrix, permutationMatrix)
#			print effortList
			minEffortIndex = sp.argmin(effortList)
			effortTime += (time.time() - effortStart)
						
			currentMinEffort = effortList[minEffortIndex]
			if (currentMinEffort < minEffort):

#				print effortList
#				print '%s < %s' % (currentMinEffort, minEffort)
#				print permutationMatrix[minEffortIndex], minEffortIndex
#				print currentSymbolIndexesInAction
				minEffort = currentMinEffort
				minPermutation = permutationMatrix[minEffortIndex].copy()				
		
#		print minPermutation, minEffort
		if (len(symbolIndexInAction) > 6): break
		
		
	final = sp.arange(len(keyList), dtype = int)
	
	for i in range(len(keyList)):		
		if minPermutation[i] != i:
			final[minPermutation[i]] = i
			print 'set %s to %s, %s' % (minPermutation[i], i, final[minPermutation[i]])
	
	print minPermutation
	print final
		
	for key in keyList:
		print key,
	print
	
	for i in range(len(keyList)):
		print keyList[final[i]],
	print
	
	print sp.sum(incrementalStats * effortMatrix[minPermutation][:, minPermutation])
	
	print 'swap time: %s' % myutil.hrtime(swapTime)
	print 'effort time: %s' % myutil.hrtime(effortTime)
		

def benchmarkPermutationMethods(statsMatrix, effortMatrix, statEffortMatrix, permutation):
	lenx, leny = statsMatrix.shape
	seq = sp.arange(lenx, dtype = int)
	initIndex0 = seq * lenx
		
	ROUNDS = 10000
	time1 = time.time()
	for i in range(ROUNDS):
		initEffort = sp.sum(statsMatrix * effortMatrix[seq[:, None], seq])
	time2 = time.time()
	
	time3 = time.time()
	for i in range(ROUNDS):
		initIndex = initIndex0 + seq	
		initEffort2 = sp.sum(statEffortMatrix[initIndex[:, None], initIndex])
	time4 = time.time()
	
	time5 = time.time()
	for i in range(ROUNDS):	
		initEffort3 = sp.sum(statsMatrix * (effortMatrix.ravel()[(seq + (seq * effortMatrix.shape[1]).reshape(-1,1)).ravel()]).reshape(seq.shape[0], seq.shape[0]))
	time6 = time.time()	
	
	print '%s vs %s vs %s' % (myutil.hrtime(time2 - time1), myutil.hrtime(time4 - time3), myutil.hrtime(time6 - time5))
	
	print 'initEffort: %s = %s = %s' % (initEffort, initEffort2, initEffort3)
				
def main(argv=None):
	
	if argv is None:
		argv = sys.argv[1:]
		
	parser = OptionParser(usage = "usage: %prog [options] FILE1 ...", version = "%prog 0.1")
	parser.add_option('-s', '--stats-file', dest = 'statsfile', help = "the file containing the matrix of symbol pair counts")
	parser.add_option('-w', '--weights-file', dest = 'weightsfile', help = "the file containing the matrix of symbol pair weights for default layout")
	parser.add_option('-o', '--out-path', dest = 'outpath', default = OUT_PATH, help = "output file directory path")
	parser.add_option('-p', '--out-file-name', dest = 'outfilename',  default = OUT_FILE_NAME, help = "output file name")

	(options, inputFiles) = parser.parse_args(args = argv)
	
	if (not os.path.exists(options.outpath)):
		os.mkdir(options.outpath)
		
	outFilePath = options.outpath + '/' + options.outfilename		
	
	startTime = time.time()
	
	statsFilePath = inputFiles[0]
	effortsFilePath = inputFiles[1]
		
	effortsFile = open(effortsFilePath, 'r')
	effortsHash = readTsvFile(effortsFile)
	effortsFile.close()
		
	statsFile = open(statsFilePath, 'r')
	statsHash = readTsvFile(statsFile)
	statsFile.close()
	
	statsMatrix = loadSymbolMatrix(statsHash, keyList = keys.RELOCATABLES, ddtype = int)
	effortMatrix = loadSymbolMatrix(effortsHash, keyList = keys.RELOCATABLES, ddtype = long)
	statEffortMatrix = getStatEffortMatrix(statsMatrix, effortMatrix)
	constraints = translateConstraints(keyList = keys.RELOCATABLES)
#	print constraints
#	sp.savetxt('../inout/statsMatrix.csv', statsMatrix, fmt = '%d' ,delimiter = '\t')
#	sp.savetxt('../inout/effortMatrix.csv', effortMatrix, fmt= '%d', delimiter = '\t')
	
	totalEffort = sp.sum(statsMatrix * effortMatrix)
	print 'initEffort: %s' % (totalEffort)
	
	symbolList, symbolHash = get_layout('start')
	
#	swaps = permute([[1,6,8], [4,6,8], [2,3,4]])
#	for swap in swaps:
#		print swap
		
#	tableSearch(statsMatrix, effortMatrix, keyList = keys.RELOCATABLES)


#	benchmarkPermutationMethods(statEffortMatrix, effortMatrix, statEffortMatrix, )
	
	print "Took time: %s" % myutil.hrtime(time.time() - startTime)
		
	
if __name__ == "__main__":
	sys.exit(main())
