#!/usr/bin/env python
"""
Chat Server Example

This example demonstrates how to create a very simple telnet-style chat
server that supports many connecting clients.
"""

from optparse import OptionParser

from circuits import Component, Debugger
from circuits.net.events import write
from circuits.net.sockets import TCPServer


__version__ = '0.0.1'

USAGE = '%prog [options]'
VERSION = '%prog v' + __version__


def parse_options():
    parser = OptionParser(usage=USAGE, version=VERSION)

    parser.add_option(
        '-b',
        '--bind',
        action='store',
        type='string',
        default='0.0.0.0:8000',
        dest='bind',
        help='Bind to address:[port]',
    )

    parser.add_option(
        '-d',
        '--debug',
        action='store_true',
        default=False,
        dest='debug',
        help='Enable debug mode',
    )

    opts, args = parser.parse_args()

    return opts, args


class ChatServer(Component):
    def init(self, args, opts):
        """
        Initialize our ``ChatServer`` Component.

        This uses the convenience ``init`` method which is called after the
        component is properly constructed and initialized and passed the
        same args and kwargs that were passed during construction.
        """
        self.args = args
        self.opts = opts

        self.clients = {}

        if opts.debug:
            Debugger().register(self)

        if ':' in opts.bind:
            address, port = opts.bind.split(':')
            port = int(port)
        else:
            address, port = opts.bind, 8000

        bind = (address, port)

        TCPServer(bind).register(self)

    def broadcast(self, data, exclude=None):
        exclude = exclude or []
        targets = (sock for sock in self.clients if sock not in exclude)
        for target in targets:
            self.fire(write(target, data))

    def connect(self, sock, host, port):
        """Connect Event -- Triggered for new connecting clients"""
        self.clients[sock] = {
            'host': sock,
            'port': port,
            'state': {
                'nickname': None,
                'registered': False,
            },
        }

        self.fire(write(sock, b'Welcome to the circuits Chat Server!\n'))
        self.fire(write(sock, b'Please enter a desired nickname: '))

    def disconnect(self, sock):
        """Disconnect Event -- Triggered for disconnecting clients"""
        if sock not in self.clients:
            return

        nickname = self.clients[sock]['state']['nickname']

        self.broadcast(
            f'!!! {nickname:s} has left !!!\n'.encode(),
            exclude=[sock],
        )

        del self.clients[sock]

    def read(self, sock, data):
        """Read Event -- Triggered for when client connections have data"""
        data = data.strip().decode('utf-8')

        if not self.clients[sock]['state']['registered']:
            nickname = data
            self.clients[sock]['state']['registered'] = True
            self.clients[sock]['state']['nickname'] = nickname

            self.broadcast(
                f'!!! {nickname:s} has joined !!!\n'.encode(),
                exclude=[sock],
            )
        else:
            nickname = self.clients[sock]['state']['nickname']

            self.broadcast(
                f'<{nickname:s}> {data:s}\n'.encode(),
                exclude=[sock],
            )


def main():
    opts, args = parse_options()

    # Configure and "run" the System.
    ChatServer(args, opts).run()


if __name__ == '__main__':
    main()
