# -*- coding: utf-8 -*-
import os
import socket
import errno
import gettext

import ggz
import ui
import game

from defaults import *

_ = gettext.gettext

class GGZServer:

    def __init__(self, name):
        self.name = name

class GGZLine:

    TYPE_BLANK   = 'BLANK'
    TYPE_COMMENT = 'COMMENT'
    TYPE_SECTION = 'SECTION'
    TYPE_FIELD   = 'FIELD'
    TYPE_INVALID = 'INVALID'
    
    def __init__(self, text):
        self.text = text
        self.section = None
        try:
            self.decode(text)
        except ValueError, e:
            self.type = GGZLine.TYPE_INVALID
            print e

    def decode(self, text):
        content = text.strip()
        if len(content) == 0:
            self.type = GGZLine.TYPE_BLANK
        elif content[0] == '#':
            self.type = GGZLine.TYPE_COMMENT
        elif content[0] == '[':
            self.type = GGZLine.TYPE_SECTION
            if content[-1] != ']':
                raise ValueError('Invalid section line: %s' % repr(content))
            self.name = content[1:-1].strip()
            self.value = None
        else:
            self.type = GGZLine.TYPE_FIELD
            try:
                (name, value) = content.split('=', 1)
            except ValueError:
                raise ValueError('Invalid field line: %s' % repr(content))
            else:
                self.name = name.strip()
                self.value = value.strip()
            
    def setValue(self, value):
        assert(self.type is GGZLine.TYPE_FIELD)
        self.value = value
        self.text = '%s=%s\n' % (self.name, value)

class GGZConfig:
    
    TYPE_NORMAL = 0
    TYPE_GUEST  = 1
    TYPE_FIRST  = 2
    
    def __init__(self):
        try:
            f = file(GGZ_CONFIG_FILE)
            lines = f.readlines()
        except IOError, e:
            print 'Failed to load GGZ config: %s: %s' % (e.strerror, e.filename)
            lines = []
            
        self.lines = []

        section = None
        for text in lines:
            line = GGZLine(text)
            self.lines.append(line)

            if line.type is GGZLine.TYPE_SECTION:
                section = line.name
                line.section = section
            else:
                line.section = section

    def getField(self, section, name):
        for line in self.lines:
            if line.section != section or line.type is not GGZLine.TYPE_FIELD:
                continue
            if line.name == name:
                return line.value
        raise KeyError('No field %s in section %s' % (name, section))
    
    def setField(self, section, name, value):
        wasInSection = False
        inSection = False
        for (index, line) in enumerate(self.lines):
            wasInSection = inSection
            if line.section == section:
                inSection = True
                if line.type is GGZLine.TYPE_FIELD and line.name == name:
                    line.setValue(value)
                    return
            elif wasInSection and not inSection:
                line = GGZLine('%s=%s\n' % (name, value))
                line.section = section
                self.lines.insert(index, line)
                return
        
        # New section            
        if not wasInSection:
            if len(self.lines) > 0:
                self.lines.append(GGZLine('\n'))
            line = GGZLine('[%s]\n' % section)
            line.section = section
            self.lines.append(line)

        line = GGZLine('%s=%s\n' % (name, value))
        line.section = section
        self.lines.append(line)
    
    def removeSection(self, name):
        keptLines = []
        for line in self.lines:
            if line.section != name:
                keptLines.append(line)
        self.lines = keptLines
        
    def getServers(self):        
        try:
            value = self.getField('Servers', 'ProfileList')
        except KeyError, e:
            return []

        servers = []
        for n in value.replace('\\ ', '\x00').split(' '):
            name = n.replace('\x00', ' ')
            try:
                server = self.getServer(name)
            except KeyError, e:
                print e
            else:
                servers.append(server)
        return servers
    
    def getServer(self, name):
        server = GGZServer(name)

        try:
            server.host = self.getField(name, 'Host')
            server.port = int(self.getField(name, 'Port'))
            server.login = self.getField(name, 'Login')
            server.loginType = int(self.getField(name, 'Type'))
        except (KeyError, ValueError):
            raise KeyError('Missing/invalid basic configuration for server %s' % repr(server.name))
        
        try:
            server.password = self.getField(name, 'Password')
        except KeyError:
            server.password = None

        return server
    
    def updateServer(self, name, username, host, port):
        self.setField(name, 'Host', host)
        self.setField(name, 'Port', port)
        self.setField(name, 'Login', username)
        self.setField(name, 'Type', GGZConfig.TYPE_GUEST) # FIXME: Hardcoded
        
        try:
            servers = self.getField('Servers', 'ProfileList')
        except KeyError, e:
            self.setField('Servers', 'ProfileList', name.replace(' ', '\\ '))
        else:
            self.setField('Servers', 'ProfileList', servers + ' ' + name.replace(' ', '\\ '))

        self.save()
                
    def save(self):
        lines = []
        for line in self.lines:
            lines.append(line.text)
        try:
            f = file(GGZ_CONFIG_FILE, 'w')
            f.writelines(lines)
        except IOError, e:
            print 'Failed to save GGZ config: %s: %s' % (e.strerror, e.filename)

class GGZChannel(ggz.Channel):
    
    def __init__(self, ui, feedback):
        self.buffer = ''
        self.ui = ui
        self.feedback = feedback
        self.socket = None
        self.fd = -1
        
    def logXML(self, text):
        self.logWindow.addText(text, 'input')

    def logBinary(self, data):
        self.logWindow.addBinary(data, 'input')

    def connect(self, host, port):
        self.close()
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.fd = self.socket.fileno()
        self.socket.setblocking(False)
        self.ui.application.ioHandlers[self.fd] = self
        self.ui.controller.watchFileDescriptor(self.fd)
        
        try:
            self.socket.connect((host, port))
        except socket.error, e:
            # FIXME: Abort/retry if error
            if e.args[0] == errno.EINPROGRESS:
                print 'connecting...'
            else:
                print e
                
        name = '%s:%d' % (host, port)
        self.logWindow = self.ui.controller.addLogWindow(name, name, name)
    
    def send(self, data, isBinary = False):
        if isBinary:
            self.logWindow.addBinary(data, 'output')
        else:
            self.logWindow.addText(data, 'output')
            
        self.buffer += data
        try:
            nSent = self.socket.send(self.buffer)
        except socket.error:
            nSent = 0
        self.buffer = self.buffer[nSent:]
        
    def close(self):
        if self.socket is None:
            return
        self.ui.controller.unwatchFileDescriptor(self.fd)
        self.socket.close()
        self.socket = None
        self.fd = -1
        
    def read(self):
        try:
            (data, address) = self.socket.recvfrom(65535)
        except socket.error, e:
            try:
                self.socket.close()
            except socket.error:
                pass
            self.feedback.closed(e.args[0])
            return False
        else:
            if len(data) == 0:
                self.feedback.closed()
                return False
            self.feedback.registerIncomingData(data)
        return True

class GGZConnection(ggz.ClientFeedback):

    def __init__(self, dialog):
        self.dialog = dialog
        self.profile = dialog.profile
        self.client = ggz.Client(self)
        self.commands = []
        self.sending = False
        self.players = {}
        self.actions = []
        
    def _getPlayerIcon(self, player):
        try:
            return {'guest':  'stock_person',
                    'normal': 'stock_person',
                    'admin':  'stock_person', # FIXME
                    'bot':    'stock_notebook'}[player.type]
        except KeyError:
            return ''
        
    def _performAction(self, method, args):
        if self.client.isBusy():
            self.actions.insert(0, (method, args))
        else:
            method(*args)

    def close(self):
        self.client.close('')

    def setBusy(self, isBusy):
        self.dialog.controller.setBusy(isBusy)
        
        # Perform next queued action
        if not isBusy and len(self.actions) > 0:
            (method, args) = self.actions.pop()
            method(*args)

    def onConnected(self):
        self.dialog.controller.setSensitive(True)
        self.dialog.controller.clearError()

    def onDisconnected(self, reason):
        self.dialog.controller.setError(_('Disconnected from server'), reason)
        self.dialog.controller.setSensitive(False)

    def openChannel(self, feedback):
        print 'Open Channel'
        self.setBusy(True)
        socket = GGZChannel(self.dialog.ui, feedback)
        socket.connect(self.profile.host, self.profile.port)
        return socket

    def getLogin(self):
        return (self.profile.login, self.profile.password)
    
    def getPassword(self, username):
        self.client.setPassword(self.profile.password)
        
    def onMOTD(self, motd):
        self.dialog.controller.addText(motd, 'motd')

    def roomAdded(self, room):
        isChess = room.game is None or (room.game.protocol_engine == 'Chess' and room.game.protocol_version == '3')
        if room.game is None:
            protocol = None
        else:
            protocol = room.game.protocol_engine
        self.dialog.controller.addRoom(int(room.id), room.name, room.nPlayers, room.description, room, protocol)

    def roomUpdated(self, room):
        self.dialog.controller.updateRoom(room, room.nPlayers)

    def roomEntered(self, room):
        self.room = room
        self.dialog.controller.clearPlayers()
        self.dialog.controller.clearTables()
        
        for player in self.client.players.itervalues():
            self.dialog.controller.addPlayer(player, player.name, self._getPlayerIcon(player))
        for table in self.client.tables.itervalues():
            self.tableAdded(table)

        self.dialog.controller.enterRoom(room)

    def tableAdded(self, table):
        self.tableUpdated(table)

    def tableUpdated(self, table):
        if table.room != self.room:
            return
        description = table.description
        if len(description) == 0:
            description = _('No description')
        nUsed = 0
        for seat in table.seats:
            if seat.type == 'bot' or seat.user != '':
                nUsed += 1

        # Can connect to Chess tables with free spaces
        isChess = table.room.game is not None and table.room.game.protocol_engine == 'Chess'
        canConnect = isChess and nUsed != len(table.seats)

        self.dialog.controller.updateTable(table, '%s' % table.id, '%i/%i' % (nUsed, len(table.seats)), description, canConnect)
        for (i, seat) in enumerate(table.seats):
            self.dialog.controller.updateSeat(table, i, seat.type, seat.user)

    def tableRemoved(self, table):
        if table.room == self.room:
            self.dialog.controller.removeTable(table)

    def onJoin(self, table, isSpectator, channel):
        self.dialog.controller.joinTable(table)
        g = GGZChess(self.dialog.game, channel)
        return g

    def onLeave(self, reason):
        print 'Leave table: %s' % reason
        self.dialog.controller.joinTable(None)

    def playerAdded(self, player):
        self.dialog.controller.addPlayer(player, player.name, self._getPlayerIcon(player))

    def playerRemoved(self, player):
        self.dialog.controller.removePlayer(player)

    def onChat(self, chatType, sender, text):
        self.dialog.controller.addText('\n%s: %s' % (sender, text), 'chat')

class GGZChess:
    """
    """
    
    def __init__(self, game, channel):
        self.game = game
        self.channel = channel
        self.protocol = ggz.Chess(self)

    def registerIncomingData(self, data):
        for o in data:
            self.protocol.decode(o)

    def onSeat(self, seatNum, version):
        self.seatNum = seatNum
        print ('onSeat', seatNum, version)
        
    def seatIsFull(self, seatType):
        return seatType == self.protocol.GGZ_SEAT_PLAYER or seatType == self.protocol.GGZ_SEAT_BOT

    def onPlayers(self, whiteType, whiteName, blackType, blackName):
        print ('onPlayers', whiteType, whiteName, blackType, blackName)
        self.whiteName = whiteName
        self.blackName = blackName

    def onClockRequest(self):
        print ('onTimeRequest',)
        self.channel.send(self.protocol.sendClock(self.protocol.CLOCK_NONE, 0), True)
    
    def onClock(self, mode, seconds):
        print ('onClock', mode, seconds)

    def onStart(self):
        print ('onStart',)
        # Create remote player
        if self.seatNum == 0:
            name = self.blackName
        else:
            name = self.whiteName
        self.remotePlayer = game.ChessPlayer(name)
        self.remotePlayer.onPlayerMoved = self.onPlayerMoved # FIXME: HACK HACK HACK!

        p = self.game.addHumanPlayer('Human')
        if self.seatNum == 0:
            self.game.setWhite(p)
            self.game.setBlack(self.remotePlayer)
        else:
            self.game.setWhite(self.remotePlayer)
            self.game.setBlack(p)

        self.game.start()

    def onPlayerMoved(self, player, move):
        #FIXME: HACK HACK HACK!
        if player is not self.remotePlayer:
            self.channel.send(self.protocol.sendMove(move.canMove.upper()), True)

    def onMove(self, move):
        print ('onMove', move)
        # FIXME: Only remote players should be used
        self.remotePlayer.move(move.lower())

class GGZNetworkDialog(ui.NetworkFeedback):
    """
    """
    
    def __init__(self, ui):
        self.ui = ui
        self.buffer = ''
        self.profile = None
        self.decoder = None
        
    def addProfile(self, profile):
        """Called by ui.NetworkFeedback"""
        (name, username, host, port) = profile

        # Make name unique
        n = name
        dupCount = 1
        while True:
            try:
                self.ui.ggzConfig.getServer(n)
            except KeyError:
                break
            else:
                dupCount += 1
                n = '%s (%d)' % (name, dupCount)
        name = n
        
        self.ui.ggzConfig.updateServer(name, username, host, port)
        
        # FIXME: Return value is temporary
        return self.ui.ggzConfig.getServer(name)

    def setProfile(self, profile):
        """Called by ui.NetworkFeedback"""
        if profile is None:
            name = '(none)'
        else:
            name = profile.name
        print 'Profile=%s' % name #FIXME Make proper log

        if self.decoder is not None:
            self.decoder.close()
        self.decoder = None

        self.controller.clearError()       
        self.controller.setSensitive(False)
        self.controller.clearRooms()
        self.controller.clearPlayers()
        self.controller.clearTables()
        self.controller.clearText()
        
        self.profile = profile
        if profile is None:
            return
       
        self.decoder = GGZConnection(self)
        self.decoder.client.start()
    
    def enterRoom(self, room):
        """Called by ui.NetworkFeedback"""
        print 'Entering room %s' % room.id
        self.decoder._performAction(self.decoder.client.enterRoom, (room,))
        
    def _addGame(self):
        self.game = self.ui.application.addGame('Network Game')
        if self.game is None:
            # FIXME: Notify user game aborted
            return False
        return True
        
    def startTable(self):
        """Called by ui.NetworkFeedback"""
        if self.decoder.room.game is None:
            return
        
        if not self._addGame():
            return
        
        print 'Starting table'
        self.decoder.client.startTable(self.decoder.room.game.id, '', self.profile.login)

    def joinTable(self, table):
        """Called by ui.NetworkFeedback"""
        if not self._addGame():
            return
        
        print 'Starting table %s' % table.id
        self.decoder.client.joinTable(table)

    def leaveTable(self):
        """Called by ui.NetworkFeedback"""
        self.decoder.client.leaveTable()

    def sendChat(self, text):
        """Called by ui.NetworkFeedback"""
        self.decoder.client.sendChat(text)
