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

# A simple GTK based client for socktroll, a simple chat protocoll.
# Copyright (C) 2009  Patrik Lembke <blambi@chebab.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/>.


"""This socktroll client will try to do everything the GTK Glib way."""

import pygtk
pygtk.require('2.0')
import gtk, gobject
import socket

class SocktrollError( ValueError ): pass

class SocktrollProtocol:   
    def __init__( self, host="", port = 6000, ui = None ):
        if host == "":
            raise SocktrollError, "No host specifed"

        if ui == None:
            raise SocktrollError, "You must specify a UI.."

        self.ui = ui       
        self.host = host
        self.port = port

        self.sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
        self.sock.connect( ( host, port ) )

        self.connected = True
        self.authed = False
        
        # bind to event loop
        self.glue = gobject.io_add_watch( self.sock, gobject.IO_IN,
                                          self.__handle__ )

    def __handle__( self, source, condition ):
        message = source.recv( 1024 ).strip()

        # protocol parsing
        command = message.split( ' ' )[0]
        args = message.split( ' ' )[1:]

        if command not in ( 'no', 'ok', 'msg', 'action', 'names', '+', '-',
                            'illegal', 'rename' ):
            # Not a valid socktroll command
            mtype = "error"
            data = { 'type': 'nonstd',
                     'command': command,
                     'args': ' '.join( args ) }
        
        elif command == 'msg':
            mtype = "message"
            data = { 'from': args[0], 'message': ' '.join( args[1:] ) }

        elif command == 'no':
            mtype = "nick reject"
            if args[0] == 'bad':
                rtype = "not valid"
            else:
                rtype = "taken"
            data = { 'why': rtype }

        elif command == 'ok':
            mtype = "nick ok"
            data = { 'nick': args[0] }
            self.authed = True

        elif command == 'action':
            mtype = "action"
            data = { 'message': ' '.join( args ) }

        elif command == 'names':
            mtype = "names"
            data = { 'nicks': args }

        elif command == '+':
            mtype = "join"
            data = { 'who': args[0] }

        elif command == '-':
            mtype = "part"
            data = { 'who': args[0] }

        elif command == 'rename':
            mtype = "rename"
            data = { 'old': args[0], 'new': args[1] }

        elif command == 'illegal':
            mtype = "error"
            data = { 'type': 'badcmd' }

        else:
            mtype = "undef"
            data = { 'command': command, 'data': ' '.join( args ) }
        
        self.ui.socktroll_event( mtype, data )
        
        if len( message ) > 0:
            return True
        else:
            return False
                       
    def close( self ):
        self.connected = False
        self.authed = False
        gobject.source_remove( self.glue ) # unbind from event loop      
        self.sock.close()

    def send( self, mtype, data ):
        if not self.connected:
            raise SocktrollError, "Gah, you must first be connected"

        # Process the message so we send the right kind.
        if mtype not in ( 'nick', 'message', 'quit', 'names', 'action' ):
            raise SocktrollError, "unknown command"
        
        if mtype == "nick":
            message = "nick %s" %( data['nick'] )

        elif mtype == "quit":
            #message = "quit"
            raise SocktrollError, "Not implemented yet."

        elif self.authed:
            if mtype == "message":
                message = "msg %s" % data['message']
                
            elif mtype == "names":
                message = "names"

            elif mtype == "action":
                message = "action %s" % data['message']

        else:
            raise SocktrollError, "you must select a nick"
        
        self.sock.sendall( message + "\n" )


class ChatUI:
    def __init__( self, host, port = 6000 ):
        # create the window
        self.win = gtk.Window( gtk.WINDOW_TOPLEVEL )
        self.win.set_size_request( 450, 400 )
        self.win.set_border_width( 5 )
        self.win.set_title( "Socktroll" )
        self.win.connect( "destroy", self.close )
        
        # stuff
        self.buf = gtk.TextBuffer( table = None )
        self.buf.connect( 'insert-text', self.text_insert_event )
        
        self.log = gtk.TextView( self.buf )
        self.log.set_editable( False )
        self.log.set_wrap_mode( gtk.WRAP_WORD_CHAR ) # Line wrapping
        self.scroller = gtk.ScrolledWindow()
        self.scroller.set_policy( gtk.POLICY_NEVER, gtk.POLICY_ALWAYS )
        self.scroller.add( self.log )

        self.cmd = gtk.Entry()
        self.cmd.connect( "activate", self.enter_event )

        vbox = gtk.VBox()
        vbox.pack_start( self.scroller, True, True, 3 )
        vbox.pack_end( self.cmd, False, False, 3 )
        
        # add stuff to the window
        self.win.add( vbox )
        self.cmd.show()
        self.log.show()
        vbox.show()
        self.scroller.show()
        self.win.show()

        # Done...
        self.buf.set_text( " ! Hi you must first set choose a nick.\n" )

        # connect to the server (Add warning box)
        self.socktroll = SocktrollProtocol( host, port, ui = self )

    def delete_event( self, widget, event, data = None ):
        return False

    def enter_event( self, widget ):
        text = widget.get_text()
        widget.set_text( "" )
        end = self.buf.get_end_iter()

        if text.startswith( '/' ): # it is a command
            cmd = text.split( ' ' )[0][1:].lower()
            args = text.split( ' ' )[1:]

            if cmd == 'nick':
                mtype = "nick"
                data = { 'nick': args[0] }

            elif cmd == 'names':
                mtype = "names"
                data = {}

            elif cmd == 'me' or cmd == 'action':
                mtype = "action"
                data = { 'message': " ".join( args ) }

            elif cmd == 'quit':
                mtype = "quit"
                data = {}

            else:
                mtype = None
            
        elif text != "": # else just a message
            mtype = "message"
            data = { 'message': text }

        else: # Just blank...
            return False
            
        if mtype != None:
            try:
                self.socktroll.send( mtype, data )
            except SocktrollError, why:
                self.buf.insert( end, " ! Error: %s\n" % why )
        else:
            self.buf.insert( end, " ! Error: Unknown command: '%s'\n" % text )

        return False

    def socktroll_event( self, mtype, data ):
        end = self.buf.get_end_iter()

        if mtype == "message":
            # Use coloured nicks
            # see: http://faq.pygtk.org/index.py?req=show&file=faq14.011.htp
            string = "%s: %s\n" %( data['from'], data['message'] )

        elif mtype == "nick reject":
            if data['why'] == 'not valid':
                string = "! Hmm that nick is invalid\n"
            else:
                string = "! That nick was already in use\n"

        elif mtype == "nick ok":
            string = "! Welcome %s!\n" % data['nick']

        elif mtype == "action":
            string = "* %s\n" % data['message']

        elif mtype == "names":
            string = "! Names: %s\n" % ', '.join( data['nicks'] )

        elif mtype == "join":
            string = "+ %s\n" % data['who']

        elif mtype == "part":
            string = "- %s\n" % data['who']

        elif mtype == "rename":
            string = "! %s is now called %s\n" %( data['old'], data['new'] )

        elif mtype == "error":
            if data['type'] == "nonstd":
                string = "! Error: got undefined command: %s %s" %(
                    data['command'], data['args'] )
            else:
                string = "! Error: you sent a illegal command to the server\n"

        else:
            string = "UNKNOWN(%s) %s\n" %( mtype, data )

        self.buf.insert( end, string )

    def text_insert_event( self, widget, textiter, string, length ):
        #adj = self.scroller.get_vadjustment() # blinky..
        #if not adj.get_value == adj.upper:
        #    adj.set_value( adj.upper )
        self.log.scroll_mark_onscreen( self.buf.get_insert() )
        
    def close( self, widget, data = None ):
        self.socktroll.close()
        gtk.main_quit()

##--
host = "localhost"
port = 6000
chatui = ChatUI( host, port )
gtk.main()



