"""
twitterbot

  A twitter IRC bot. Twitterbot connected to an IRC server and idles in
  a channel, polling a twitter account and broadcasting all updates to
  friends.

USAGE

  twitterbot [config_file]

CONFIG_FILE

  The config file is an ini-style file that must contain the following:

[irc]
server: <irc_server>
port: <irc_port>
nick: <irc_nickname>
channel: <irc_channels_to_join>
prefixes: <prefix_type>

[twitter]
oauth_token_file: <oauth_token_filename>


  If no config file is given "twitterbot.ini" will be used by default.

  The channel argument can accept multiple channels separated by commas.

  The default token file is ~/.twitterbot_oauth.

  The default prefix type is 'cats'. You can also use 'none'.

"""

from __future__ import print_function

BOT_VERSION = "TwitterBot 1.9.1 (http://mike.verdone.ca/twitter)"

CONSUMER_KEY = "XryIxN3J2ACaJs50EizfLQ"
CONSUMER_SECRET = "j7IuDCNjftVY8DBauRdqXs4jDl5Fgk1IJRag8iE"

IRC_BOLD = chr(0x02)
IRC_ITALIC = chr(0x16)
IRC_UNDERLINE = chr(0x1f)
IRC_REGULAR = chr(0x0f)

import sys
import time
from datetime import datetime, timedelta
from email.utils import parsedate
try:
    from configparser import ConfigParser
except ImportError:
    from ConfigParser import ConfigParser
from heapq import heappop, heappush
import traceback
import os
import os.path

from .api import Twitter, TwitterError
from .oauth import OAuth, read_token_file
from .oauth_dance import oauth_dance
from .util import htmlentitydecode

PREFIXES = dict(
    cats=dict(
        new_tweet="=^_^= ",
        error="=O_o= ",
        inform="=o_o= "
        ),
    none=dict(
        new_tweet=""
        ),
    )
ACTIVE_PREFIXES=dict()

def get_prefix(prefix_typ=None):
    return ACTIVE_PREFIXES.get(prefix_typ, ACTIVE_PREFIXES.get('new_tweet', ''))


try:
    import irclib
except ImportError:
    raise ImportError(
        "This module requires python irclib available from "
        + "https://github.com/sixohsix/python-irclib/zipball/python-irclib3-0.4.8")

OAUTH_FILE = os.environ.get('HOME', os.environ.get('USERPROFILE', '')) + os.sep + '.twitterbot_oauth'

def debug(msg):
    # uncomment this for debug text stuff
    # print(msg, file=sys.stdout)
    pass

class SchedTask(object):
    def __init__(self, task, delta):
        self.task = task
        self.delta = delta
        self.next = time.time()

    def __repr__(self):
        return "<SchedTask %s next:%i delta:%i>" %(
            self.task.__name__, self.__next__, self.delta)

    def __lt__(self, other):
        return self.next < other.next

    def __call__(self):
        return self.task()

class Scheduler(object):
    def __init__(self, tasks):
        self.task_heap = []
        for task in tasks:
            heappush(self.task_heap, task)

    def next_task(self):
        now = time.time()
        task = heappop(self.task_heap)
        wait = task.next - now
        task.next = now + task.delta
        heappush(self.task_heap, task)
        if (wait > 0):
            time.sleep(wait)
        task()
        #debug("tasks: " + str(self.task_heap))

    def run_forever(self):
        while True:
            self.next_task()


class TwitterBot(object):
    def __init__(self, configFilename):
        self.configFilename = configFilename
        self.config = load_config(self.configFilename)

        global ACTIVE_PREFIXES
        ACTIVE_PREFIXES = PREFIXES[self.config.get('irc', 'prefixes')]

        oauth_file = self.config.get('twitter', 'oauth_token_file')
        if not os.path.exists(oauth_file):
            oauth_dance("IRC Bot", CONSUMER_KEY, CONSUMER_SECRET, oauth_file)
        oauth_token, oauth_secret = read_token_file(oauth_file)

        self.twitter = Twitter(
            auth=OAuth(
                oauth_token, oauth_secret, CONSUMER_KEY, CONSUMER_SECRET),
            domain='api.twitter.com')

        self.irc = irclib.IRC()
        self.irc.add_global_handler('privmsg', self.handle_privmsg)
        self.irc.add_global_handler('ctcp', self.handle_ctcp)
        self.irc.add_global_handler('umode', self.handle_umode)
        self.ircServer = self.irc.server()

        self.sched = Scheduler(
            (SchedTask(self.process_events, 1),
             SchedTask(self.check_statuses, 120)))
        self.lastUpdate = (datetime.utcnow() - timedelta(minutes=10)).utctimetuple()

    def check_statuses(self):
        debug("In check_statuses")
        try:
            updates = reversed(self.twitter.statuses.home_timeline())
        except Exception as e:
            print("Exception while querying twitter:", file=sys.stderr)
            traceback.print_exc(file=sys.stderr)
            return

        nextLastUpdate = self.lastUpdate
        for update in updates:
            crt = parsedate(update['created_at'])
            if (crt > nextLastUpdate):
                text = (htmlentitydecode(
                    update['text'].replace('\n', ' '))
                    .encode('utf8', 'replace'))

                # Skip updates beginning with @
                # TODO This would be better if we only ignored messages
                #   to people who are not on our following list.
                if not text.startswith(b"@"):
                    msg = "%s %s%s%s %s" %(
                        get_prefix(),
                        IRC_BOLD, update['user']['screen_name'],
                        IRC_BOLD, text.decode('utf8'))
                    self.privmsg_channels(msg)

                nextLastUpdate = crt

        self.lastUpdate = nextLastUpdate

    def process_events(self):
        self.irc.process_once()

    def handle_privmsg(self, conn, evt):
        debug('got privmsg')
        args = evt.arguments()[0].split(' ')
        try:
            if (not args):
                return
            if (args[0] == 'follow' and args[1:]):
                self.follow(conn, evt, args[1])
            elif (args[0] == 'unfollow' and args[1:]):
                self.unfollow(conn, evt, args[1])
            else:
                conn.privmsg(
                    evt.source().split('!')[0],
                    "%sHi! I'm Twitterbot! you can (follow "
                    "<twitter_name>) to make me follow a user or "
                    "(unfollow <twitter_name>) to make me stop." %
                    get_prefix())
        except Exception:
            traceback.print_exc(file=sys.stderr)

    def handle_ctcp(self, conn, evt):
        args = evt.arguments()
        source = evt.source().split('!')[0]
        if (args):
            if args[0] == 'VERSION':
                conn.ctcp_reply(source, "VERSION " + BOT_VERSION)
            elif args[0] == 'PING':
                conn.ctcp_reply(source, "PING")
            elif args[0] == 'CLIENTINFO':
                conn.ctcp_reply(source, "CLIENTINFO PING VERSION CLIENTINFO")

    def handle_umode(self, conn, evt):
        """
        QuakeNet ignores all your commands until after the MOTD. This
        handler defers joining until after it sees a magic line. It
        also tries to join right after connect, but this will just
        make it join again which should be safe.
        """
        args = evt.arguments()
        if (args and args[0] == '+i'):
            channels = self.config.get('irc', 'channel').split(',')
            for channel in channels:
                self.ircServer.join(channel)

    def privmsg_channels(self, msg):
        return_response=True
        channels=self.config.get('irc','channel').split(',')
        return self.ircServer.privmsg_many(channels, msg.encode('utf8'))

    def follow(self, conn, evt, name):
        userNick = evt.source().split('!')[0]
        friends = [x['name'] for x in self.twitter.statuses.friends()]
        debug("Current friends: %s" %(friends))
        if (name in friends):
            conn.privmsg(
                userNick,
                "%sI'm already following %s." %(get_prefix('error'), name))
        else:
            try:
                self.twitter.friendships.create(screen_name=name)
            except TwitterError:
                conn.privmsg(
                    userNick,
                    "%sI can't follow that user. Are you sure the name is correct?" %(
                        get_prefix('error')
                        ))
                return
            conn.privmsg(
                userNick,
                "%sOkay! I'm now following %s." %(get_prefix('followed'), name))
            self.privmsg_channels(
                "%s%s has asked me to start following %s" %(
                    get_prefix('inform'), userNick, name))

    def unfollow(self, conn, evt, name):
        userNick = evt.source().split('!')[0]
        friends = [x['name'] for x in self.twitter.statuses.friends()]
        debug("Current friends: %s" %(friends))
        if (name not in friends):
            conn.privmsg(
                userNick,
                "%sI'm not following %s." %(get_prefix('error'), name))
        else:
            self.twitter.friendships.destroy(screen_name=name)
            conn.privmsg(
                userNick,
                "%sOkay! I've stopped following %s." %(
                    get_prefix('stop_follow'), name))
            self.privmsg_channels(
                "%s%s has asked me to stop following %s" %(
                    get_prefix('inform'), userNick, name))

    def _irc_connect(self):
        self.ircServer.connect(
            self.config.get('irc', 'server'),
            self.config.getint('irc', 'port'),
            self.config.get('irc', 'nick'))
        channels=self.config.get('irc', 'channel').split(',')
        for channel in channels:
            self.ircServer.join(channel)

    def run(self):
        self._irc_connect()

        while True:
            try:
                self.sched.run_forever()
            except KeyboardInterrupt:
                break
            except TwitterError:
                # twitter.com is probably down because it
                # sucks. ignore the fault and keep going
                pass
            except irclib.ServerNotConnectedError:
                # Try and reconnect to IRC.
                self._irc_connect()


def load_config(filename):
    # Note: Python ConfigParser module has the worst interface in the
    # world. Mega gross.
    cp = ConfigParser()
    cp.add_section('irc')
    cp.set('irc', 'port', '6667')
    cp.set('irc', 'nick', 'twitterbot')
    cp.set('irc', 'prefixes', 'cats')
    cp.add_section('twitter')
    cp.set('twitter', 'oauth_token_file', OAUTH_FILE)

    cp.read((filename,))

    # attempt to read these properties-- they are required
    cp.get('twitter', 'oauth_token_file'),
    cp.get('irc', 'server')
    cp.getint('irc', 'port')
    cp.get('irc', 'nick')
    cp.get('irc', 'channel')

    return cp

# So there was a joke here about the twitter business model
# but I got rid of it. Not because I want this codebase to
# be "professional" in any way, but because someone forked
# this and deleted the comment because they couldn't take
# a joke. Hi guy!
#
# Fact: The number one use of Google Code is to look for that
# comment in the Linux kernel that goes "FUCK me gently with
# a chainsaw." Pretty sure Linus himself wrote it.

def main():
    configFilename = "twitterbot.ini"
    if (sys.argv[1:]):
        configFilename = sys.argv[1]

    try:
        if not os.path.exists(configFilename):
            raise Exception()
        load_config(configFilename)
    except Exception as e:
        print("Error while loading ini file %s" %(
            configFilename), file=sys.stderr)
        print(e, file=sys.stderr)
        print(__doc__, file=sys.stderr)
        sys.exit(1)

    bot = TwitterBot(configFilename)
    return bot.run()
