#!/usr/bin/env python
# -*- coding:utf-8 -*-
# 日本語です。
# Converts an Excel file with directions for guideboards into a LaTeX file.
# Usage:
#   bin/makeSigns.py signs.xlsx >signs.inc
#
# Excel file (signs.xlsx) format
#   comment:    # to comment out
#   ID:         id for the guideboard
#   location:   location of the guideboard
#   command:    LaTeX command
#   boardDirection: directions that the boards faces
#               (N, NE, E, SE, S, SW, W, NW, separated by commas)
#   maps:       filenames of maps used for each direction, seprated by commaas
#               none: no map is shown
#               If one file is given, the file is used for all the directions.
#               Otherwise, the same number of maps as the number of directions
#               should be given.
#   text at fixed location #1
#   text at fixed location #2
#   text at the top
#   text between the map and table
#   text at the bottom
#   List of following pairs
#   text0:       LaTeX string (such as building names)
#   direction0:  direction for the buildings
#               Only one from N, NE, E, SE, S, SW, W, NW.
#               For "toEntrance" command, direction preceeded with "*" 
#               such as "*E" indicates the direction of the entrance.
#   text1, direction1, text2, direction2, ...
#
# 2016-10-09 Taku Yamanaka (Osaka Univ.)
# v1.0 2016-10-09
# v1.1 2016-10-09
#       Added NE, SE, SW, and NW in directions.
#       Added ForwardRight, ForwardLeft, ...
# v2.0  2016-10-13 Taku
#       Support placing texts at fixed locations using picture env.
#       Added texts at top, middle, and bottom.
# v2.1  2016-10-18 Taku
#       Added "command" column in Excel file.

import sys
import codecs
import xlrd

class TextDirec:
    def __init__(self, text, direc):
        self.text = text
        self.direc = direc

class Panel:
    compassDirs = 8
    compassNo = {'N':0, 'NE':1, 'E':2, 'SE':3, 
                 'S':4, 'SW':5, 'W':6, 'NW':7}
    compassStr = ('N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW')
    commandStr = ('\\Forward',  '\\ForwardRight', 
                  '\\Right',    '\\BackRight', 
                  '\\Back',     '\\BackLeft', 
                  '\\Left',     '\\ForwardLeft')

    def __init__(self, ID, location, command, boardDirections, maps, texts, textDirs):
        self.ID = ID
        self.location = location
        self.command = command
        tmpBoardDirs = boardDirections.split(',')
        self.boardDirections = []
        for bd in tmpBoardDirs:
            self.boardDirections.append(bd.strip())
        self.maps = maps.split(',')
        self.texts = texts
        self.textDirs = textDirs

        if len(self.maps) > 1 and len(self.maps) != len(self.boardDirections):
            print >>sys.stderr, 'Error: ID=', self.ID,
            print >>sys.stderr, 'The number of maps (', len(self.maps), 
            print >>sys.stderr, ') should match the number of boardDirections (',
            print >>sys.stderr, len(self.boardDirections), ').'

        for boardDirec in self.boardDirections:
            if boardDirec not in self.compassNo:
                print >>sys.stderr, 'Error: ID=', self.ID,
                print >>sys.stderr, 'Direction', boardDirec, 'is invalid.'
                print >>sys.stderr, '      Please use one of N, NE, E, SE, S, SW, W, and NW.'

        for txtDir in self.textDirs:
            direc = txtDir.direc.replace('*', '')
            if direc not in self.compassNo:
                print >>sys.stderr, 'Error: ID=', self.ID,
                print >>sys.stderr, 'Direction', direc, 'is invalid.'
                print >>sys.stderr, '      Please use one of N, NE, E, SE, S, SW, W, and NW.'

    def dump(self):
        print >>sys.stderr, self.ID, self.location, self.command
        print >>sys.stderr, self.boardDirections, self.maps
        for txtdir in textDirs:
            print >>sys.stderr, txtdir.text, txtdir.direc

    def relativeDirNo(self, boardDirec, direc):
        return (self.compassNo[direc] - self.compassNo[boardDirec]) \
                 % self.compassDirs

    def signTeXString(self, boardDirec, txtDir):
        newDirNo = self.relativeDirNo(boardDirec, txtDir.direc)
        return '\t\t' + self.commandStr[newDirNo] + '{' + txtDir.text + '}\\n'

    def makeTeXStringForDir(self, textDirs, boardDirec):
        texString = ''
        nStrings = 0

        for txtDir in textDirs:
            texStr = self.signTeXString(boardDirec, txtDir)
            if len(self.boardDirections) > 1 and 'Back' in texStr:
                pass
            else:
                texString += texStr
                nStrings += 1

        return texString, nStrings

    def printMapArrows(self):
        jmap = 0
        for boardDirec in self.boardDirections:
            print '\\mapArrows{' + self.ID + boardDirec + '}{' + self.location + '}'

            mapFile = self.maps[jmap].strip()
            print '\t{' + mapFile + '}'

            for txt in texts:
                print '\t{' + txt + '}'

            texString, nStrings =  self.makeTeXStringForDir(self.textDirs, boardDirec)
            print '\t{%'
            print texString
            print '\t}%'
            
            if len(self.maps) > 1:
                jmap += 1

    def findEntrance(self):
        entranceDirec = []
        otherDirec = []

        for txtDir in self.textDirs:
            if txtDir.direc[0] == '*':
                entranceDirec.append(txtDir)

            else:
                otherDirec.append(txtDir)

        if len(entranceDirec) != 1:
            raise ValueError\
                ('Only one cell should have * to show the entrance.')

        return entranceDirec, otherDirec

    def printToEntrance(self):
        entranceDirection, otherDirections = self.findEntrance()

        for boardDirec in self.boardDirections:
            print '\\toEntrance{' + self.ID + boardDirec + '}{' + self.location + '}'
            entDirec = entranceDirection[0].direc[1:]  # w/o '*'
            relDirNo = self.relativeDirNo(boardDirec, entDirec)
            if relDirNo != 2 and relDirNo != 6:
                raise ValueError\
                    ('Entrance should be either on left or right for ID=' + self.ID)

            text = entranceDirection[0].text

            fontsize = ''
            if len(text) > 1:
                fontsize = '\\entnormal '

            print '\t{' + self.commandStr[relDirNo][1:] + '}{' + fontsize + text + '}'

            for txt in texts[2:]:
                print '\t{' + txt + '}'

            texString, nStrings = self.makeTeXStringForDir(otherDirections, boardDirec)
            
            print '\t{%'
            print selftexString
            print '\t}%'

    def splitLR(self, boardDirec):
        # split directions to be placed on the left and right
        leftCol = []
        rightCol = []

        for txtDir in self.textDirs:
            relDirNo = self.relativeDirNo(boardDirec, txtDir.direc)

            if relDirNo < self.compassDirs/2:
                rightCol.append(txtDir)
            else:
                leftCol.append(txtDir)

        return leftCol, rightCol
            
    def print2ColArrows(self):
        for boardDirec in self.boardDirections:
            print '\\twoColArrows{' + self.ID + boardDirec + '}{' + self.location + '}'
            leftCol, rightCol = self.splitLR(boardDirec)

            for txt in texts:
                print '\t{' + txt + '}'
        
            texString, nStrings = self.makeTeXStringForDir(directions, boardDirec)
            for directions in (leftCol, rightCol):
                print '\t{%'
                print texString
                print '\t}%'

    def textsForDirections(self):
        # input:  self.textDirs
        # output: groupedDirections[i] : concatenated texts for direction i

        textDirecList = []
        for i in range(self.compassNo):
            textDirecList.append([])

        for txtDir in self.textDirs:
            dirNo = self.compassNo[txtDir.direc]
            textDirecList[dirNo].append(txtDir.text)

        txtForDirec = []
        for textList in textDirecList:
            txtForDirec.append(' '.join(textList))

        return txtForDirec

    def printGroupedArrows(self):
        txtForDirec = self.textsForDirections()

        groupedDirections = []
        for boardDirec in self.boardDirections:
            boardDirecNo = self.compassNo[boardDirec]
            for dirNo in range(boardDirecNo-3, boardDirecNo+4):
                dirNo = dirNo % self.compassNo
                dirStr = self.compassStr[dirNo]
                txt = txtforDirec[dirNo]

                if txt != '':
                    groupedDirections.append(TextDirec(txt, dirStr))

            texString, nStrings \
                    = self.makeTeXStringForDir(groupedDirections, boardDirec)
            
            tabularFormat = 'l' + '|c'*(nStrings - 2) + '|r'*(nStrings - 1)

            print '\\leftRightArrows{' + self.ID + boardDirec + '}{' + self.location + '}'

            for txt in texts[:2]:   # texts at fixed locations only
                print '\t{' + txt + '}'
        
            print '\t{' + tabularFormat + '}'
            print '\t{%'
            print texString
            print '\t}%'


    def printPanels(self):
        if self.command == 'mapArrows':
            self.printMapArrows()

        elif self.command == 'toEntrance':
            self.printToEntrance()

        elif self.command == 'twoColArrows':
            self.print2ColArrows()

        elif self.command == 'groupedArrows':
            self.printGroupedArrows()

        else:
            print >>sys.stderr, 'ERROR-Unknown command', self.command



def readExcelRow(sheet, row):
    items = []
    for col in range(sheet.ncols):
        cell = sheet.cell(row, col).value
        items.append(cell)

    rawid, location, command, boardDirections, maps = items[1:jtext0]
    ID = str(rawid)

    texts = items[jtext0:jtext1]

    textDirs = []
    for j in range(jtext1, sheet.ncols, 2):
        text, direc = items[j:j+2]
        if text != '' and direc != '':
            # textDirs.append((text, direc))
            textDirs.append(TextDirec(text, direc))

    return ID, location, command, boardDirections, maps, texts, textDirs

#--------- main -----------
sys.stdout = codecs.getwriter('utf_8')(sys.stdout)
sys.stderr = codecs.getwriter('utf_8')(sys.stderr)

command = sys.argv[1]   # command to select
excelFile = sys.argv[2]

book = xlrd.open_workbook(excelFile)
sheet = book.sheet_by_index(0)

jcommand = 3 # LaTeX command
jtext0 = 6  # cell number (0 origin) of text for the first fixed location
jtext1 = 11 # cell number of text0 for directions

for row in range(sheet.nrows):
    if sheet.cell(row, 0).value == '' and \
       sheet.cell(row, jcommand).value == command:
        ID, location, command, boardDirections, maps, texts, textDirs \
                = readExcelRow(sheet, row)
        panel = Panel(ID, location, command, boardDirections, maps, texts, textDirs)
        # panel.dump()
        panel.printPanels()
