#!/usr/bin/env python
# coding=utf-8
from datetime import datetime
import sys
import asyncio
import time
import signal
import traceback

import hangups
from hangups.ui.utils import get_conv_name
import config
import handlers

import re
import multiprocessing
from hubot_handler import HubotHandler

__version__ = '1.1'
LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'


class ConversationEvent(object):
    def __init__(self, bot, conv_event):
        self.conv_event = conv_event
        self.conv_id = conv_event.conversation_id
        self.conv = bot._conv_list.get(self.conv_id)
        self.user_id = conv_event.user_id
        self.user = self.conv.get_user(self.user_id)
        self.timestamp = conv_event.timestamp
        self.text = conv_event.text.strip() if isinstance(conv_event, hangups.ChatMessageEvent) else ''

    def print_debug(self):
        print('Conversation ID: {}'.format(self.conv_id))
        print('Conversation name: {}'.format(get_conv_name(self.conv, truncate=True)))
        print('User ID: {}'.format(self.user_id))
        print('User name: {}'.format(self.user.full_name))
        print('Timestamp: {}'.format(self.timestamp.astimezone(tz=None).strftime('%Y-%m-%d %H:%M:%S')))
        print('Text: {}'.format(self.text))
        print()


class HangupsBot(object):
    """Hangouts bot listening on all conversations"""

    def __init__(self, cookies_path, config_path, max_retries=5):
        self._client = None
        self._cookies_path = cookies_path
        self._max_retries = max_retries
        self.conv_settings = {}

        # These are populated by on_connect when it's called.
        self._conv_list = None  # hangups.ConversationList
        self._user_list = None  # hangups.UserList
        self._message_handler = None  # MessageHandler

        # Load config file
        self.config = config.Config(config_path)
        self.devmode = self.get_config_suboption('', 'development_mode')

        # Handle signals on Unix
        # (add_signal_handler is not implemented on Windows)
        try:
            loop = asyncio.get_event_loop()
            for signum in (signal.SIGINT, signal.SIGTERM):
                loop.add_signal_handler(signum, lambda: self.stop())
        except NotImplementedError:
            pass

    @property
    def dev(self):
        return self.devmode

    @dev.setter
    def dev(self, value):
        if value:
            self.devmode = value
        else:
            self.devmode = False
        if self.devmode:
            def dev_send_segments(conversation, segments):
                if len(segments) == 0:
                    return
                for segment in segments:
                    print(segment.text if not segment.type_ == hangups.SegmentType.LINE_BREAK else "\n")

            def dev_send(conversation, text):
                dev_send_segments(conversation, [hangups.ChatMessageSegment(text)])

            self.send_message_segments = dev_send_segments
            self.send_message = dev_send
        else:
            self.send_message_segments = HangupsBot("cookies.txt", "config.json").send_message_segments
            self.send_message = HangupsBot("cookies.txt", "config.json").send_message


    def restart(self):
        self.stop()
        self.run()

    def login(self, cookies_path):
        """Login to Google account"""
        # Authenticate Google user and save auth cookies
        # (or load already saved cookies)
        try:
            cookies = hangups.auth.get_auth_stdin(cookies_path)
            return cookies
        except hangups.GoogleAuthError as e:
            print('Login failed ({})'.format(e))
            return False

    def run(self):
        """Connect to Hangouts and run bot"""
        cookies = self.login(self._cookies_path)
        if cookies:
            print("You're logged in!")

            # Create Hangups client
            self._client = hangups.Client(cookies)
            self._client.on_connect.add_observer(self._on_connect)
            self._client.on_disconnect.add_observer(self._on_disconnect)

            self.output_pipe, self.input_pipe = multiprocessing.Pipe(duplex = False)

            hubot = HubotHandler(self)
            self.p = multiprocessing.Process(target=hubot.listen, args=(self.input_pipe,))
            self.p.start()

            # Start asyncio event loop and connect to Hangouts 
            # If we are forcefully disconnected, try connecting again
            loop = asyncio.get_event_loop()

            loop.add_reader(self.output_pipe.fileno(), self._on_new_response)

            for retry in range(self._max_retries):
                try:
                    loop.run_until_complete(self._client.connect())
                    sys.exit(0)
                except Exception as e:
                    print('Client unexpectedly disconnected:\n{}'.format(e))
                    log = open('log.txt', 'a+')
                    log.writelines(str(datetime.now()) + ":\n " + traceback.format_exc() + "\n\n")
                    log.close()
                    print(traceback.format_exc())
                    print('Waiting {} seconds...'.format(5 + retry * 5))
                    time.sleep(5 + retry * 5)
                    print('Trying to connect again (try {} of {})...'.format(retry + 1, self._max_retries))
            print('Maximum number of retries reached! Exiting...')
        sys.exit(1)

    def stop(self):
        """Disconnect from Hangouts"""

        self.input_pipe.close()
        self.output_pipe.close()
        self.p.terminate()

        asyncio.async(
            self._client.disconnect()
        ).add_done_callback(lambda future: future.result())


    def handle_chat_message(self, conv_event):
        """Handle chat messages"""
        event = ConversationEvent(self, conv_event)

        #start message
        asyncio.async(self._message_handler.handle(event))

    def send_message(self, conversation, text):
        """"Send simple chat message"""
        # Ignore if the user hasn't typed a message.
        if len(text) == 0:
            return
        self.send_message_segments(conversation, hangups.ChatMessageSegment.from_str(text))

    def send_message_segments(self, conversation, segments):
        """Send chat message segments"""
        # XXX: Exception handling here is still a bit broken. Uncaught
        # exceptions in _on_message_sent will only be logged.
        asyncio.async(
            conversation.send_message(segments)
        ).add_done_callback(self._on_message_sent)

    def list_conversations(self):
        """List all active conversations"""
        convs = sorted(self._conv_list.get_all(),
                       reverse=True, key=lambda c: c.last_modified)
        return convs

    def get_config_suboption(self, conv_id, option):
        """Get config suboption for conversation (or global option if not defined)"""
        try:
            suboption = self.config['conversations'][conv_id][option]

        except KeyError:
            try:
                suboption = self.config[option]
            except KeyError:
                suboption = None

        return suboption

    def _on_message_sent(self, future):
        """Handle showing an error if a message fails to send"""
        try:
            future.result()
        except hangups.NetworkError:
            print('Failed to send message!')

    def _on_new_response(self):

        while self.output_pipe.poll():

            jsonData = self.output_pipe.recv()
            conversation = self._conv_list.get(jsonData['conversationId'])
            segments = []
            broken = jsonData['message'].split('\n')

            if len(broken) > 1:
                for i in range(0,len(broken)):
                    segments.append(hangups.ChatMessageSegment(broken[i]))
                    segments.append(hangups.ChatMessageSegment('\n', hangups.SegmentType.LINE_BREAK))

                self.send_message_segments(conversation, segments)

            else:
                self.send_message(conversation, jsonData['message'])

    def _on_connect(self, initial_data):
        """Handle connecting for the first time"""
        print('Connected!')

        self._message_handler = handlers.MessageHandler(self)

        self._user_list = hangups.UserList(self._client,
                                           initial_data.self_entity,
                                           initial_data.entities,
                                           initial_data.conversation_participants)

        self._conv_list = hangups.ConversationList(self._client,
                                                   initial_data.conversation_states,
                                                   self._user_list,
                                                   initial_data.sync_timestamp)
        self._conv_list.on_event.add_observer(self._on_event)

    def _on_event(self, conv_event):
        """Handle conversation events"""
        if isinstance(conv_event, hangups.ChatMessageEvent):
            self.handle_chat_message(conv_event)

    def _on_disconnect(self):
        """Handle disconnecting"""

        print('Connection lost!')
