#!/usr/bin/env python
# -*- coding:utf-8 -*-
#
# Author: Pablo Saavedra
# Maintainer: Pablo Saavedra
# Contact: saavedra.pablo at gmail.com

import argparse
import getconf
import ldap
import logging
# import pprint
import sys
import time

from matrix_client.api import MatrixHttpApi, MatrixRequestError
from matrix_client.client import MatrixClient

try:
    reload(sys)
    sys.setdefaultencoding('utf-8')  # Forcing UTF-8 in the enviroment:
    # http://stackoverflow.com/questions/3828723/why-we-need-sys-setdefaultencodingutf-8-in-a-py-scrip
except Exception:
    pass


## GLOBAL VARS #################################################################

conffile = ".matrixbot.cfg"

settings = {}
settings["DEFAULT"] = {
    "loglevel": 10,
    "logfile": "/dev/stdout",
    "period": 30,
}
settings["matrix"] = {
    "uri": "http://localhost:8000",
    "username": "username",
    "password": "password",
    "domain": "matrix.org",
    "rooms": []
}
settings["ldap"] = {
    "server": "ldap://ldap.local",
    "base": "ou=People,dc=example,dc=com",
    "groups": []
}

sync_token = None

DEV_NULL = '/dev/null'


# Functions ####################################################################
def setup(conffile, settings):
    config = getconf.ConfigGetter('matrixbot',
                                  config_files=['/etc/matrixbot/settings.ini',
                                                '.matrixbot.ini',
                                                conffile],
                                  defaults=settings)
    for s in settings.keys():
        for k in settings[s].keys():
            if s == "DEFAULT":
                if k == "loglevel":
                    settings[s][k] = config.getint(k)
                elif k == "period":
                    settings[s][k] = config.getint(k)
                else:
                    settings[s][k] = config.get(k)
            else:
                if s == "matrix" and k == "rooms":
                    settings[s][k] = config.getlist("%s.%s" % (s, k))
                elif s == "ldap" and k == "groups":
                    settings[s][k] = config.getlist("%s.%s" % (s, k))
                    # Load the LDAP filters
                    for g in settings[s][k]:
                        settings[s][g] = config.get("%s.%s" % (s, g))
                else:
                    settings[s][k] = config.get("%s.%s" % (s, k))


def debug_conffile(settings, logger):
    for s in settings.keys():
        for k in settings[s].keys():
            key = "%s.%s" % (s, k)
            value = settings[s][k]
            logger.debug("Configuration setting - %s: %s" % (key, value))


def get_ldap_group_members(ldap_settings, groups=None, logger=None):
    ldap_server = ldap_settings["server"]
    ldap_base = ldap_settings["base"]
    ldap_groups = ldap_settings["groups"]
    get_uid = lambda x: x[1]["uid"][0]
    if groups:
        ldap_groups = filter(lambda x: x in groups, ldap_groups)
    res = {}
    try:
        conn = ldap.initialize(ldap_server)
        for g in ldap_groups:
            g_ldap_filter = ldap_settings[g]
            logger.debug("Searching members for %s: %s" % (g, g_ldap_filter))
            items = conn.search_s(ldap_base, ldap.SCOPE_SUBTREE,
                                  attrlist=['uid'],
                                  filterstr=g_ldap_filter)
            members = map(get_uid, items)
            res[g] = members
    except Exception, e:
        if logger:
            logger.error("Error getting groups from LDAP: %s" % e)
    return res


def is_command(body, command="command_name"):
    global logger
    res = False
    if body.lower().strip().startswith("%s:" % username.lower()):
        command_list = body.split()[1:]
        if len(command_list) == 0:
            if command == "help":
                res = True
        else:
            if command_list[0] == command:
                res = True
    logger.debug("is_%s: %s" % (command, res))
    return res


def do_invite_user(user_id, room_id):
    global logger
    global settings
    domain = settings["matrix"]["domain"]
    if not user_id.startswith("@"):
        user_id = "@" + user_id
        logger.debug("Adding missing '@' to the username: %s" % user_id)
    user_id = "%s:%s" % (user_id, domain)
    try:
        matrix.invite_user(room_id, user_id)
        logger.info("do_invite (%s, %s)" % (room_id, user_id))
        send_message(room_id, "Invitation to room %s sent to %s" % (room_id, user_id))
    except MatrixRequestError, e:
        logger.warning(e)
        send_message(room_id, "Oops!!!: %s" % (e))


def do_invite(room_id, body):
    global logger
    global settings
    ldap_settings = settings["ldap"]
    arg_list = body.split()[2:]
    for arg in arg_list:
        if arg.startswith("+"):
            group_name = arg[1:]
            logger.info("Sending invitation for group (%s)" % (group_name))
            send_message(room_id,
                         "Sending invitation for group (%s)" % (group_name))
            groups_members = get_ldap_group_members(ldap_settings,
                                                    logger=logger)
            if group_name in groups_members.keys():
                for group_member in groups_members[group_name]:
                    do_invite_user(group_member, room_id)
        else:
            do_invite_user(arg, room_id)


def do_help(room_id, body):
    global settings
    global logger
    vars_ = settings["matrix"].copy()
    vars_["groups"] = ', '.join(settings["ldap"]["groups"])
    try:
        logger.debug("do_help")
        msg_help = '''Examples:
%(username)s: help
%(username)s: invite @user
%(username)s: invite +group

Available groups: %(groups)s
''' % vars_
        send_message(room_id, msg_help)
    except MatrixRequestError, e:
        logger.warning(e)


def sync(matrix, timeout_ms=30000):
    global settings
    global sync_token
    global logger
    username = settings["matrix"]["username"]

    response = matrix.sync(sync_token, timeout_ms)
    sync_token = response["next_batch"]
    logger.info("+++ sync_token: %s" % (sync_token))

    for room_id, sync_room in response['rooms']['join'].items():
        logger.info(">>> %s: %s" % (room_id, sync_room))
        # pp = pprint.PrettyPrinter(indent=4)
        # pp.pprint(sync_room)
        for event in sync_room["timeline"]["events"]:
            if event["type"] == 'm.room.message' and \
                    "content" in event and \
                    "msgtype" in event["content"] and \
                    event["content"]["msgtype"] == 'm.text':
                body = event["content"]["body"]
                if body.lower().strip().startswith("%s:" % username.lower()):
                    if is_command(body, "invite"):
                        do_invite(room_id, body)
                    elif is_command(body, "help"):
                        do_help(room_id, body)
                    else:
                        do_help(room_id, body)


def join_rooms(client, room_ids,  silent=True):
    for room_id in room_ids:
        try:
            room = client.join_room(room_id)
            if not silent:
                room.send_text("Mornings!")
        except MatrixRequestError, e:
            logger.error("Error joining in the room %s: %s" %
                         (room_id, e))


def send_message(room_id, message, max_attempts=3, wait=60):
    global logger
    attempts = max_attempts
    while attempts:
        try:
            response = matrix.send_message(room_id, message)
            return response
        except MatrixRequestError, e:
            logger.error("Error sending message (%s/%s) to room %s: %s (%s)" %
                         (attempts, max_attempts, room_id, message, e))
            max_attempts -= 1


## command line options parser #################################################
parser = argparse.ArgumentParser()
parser.add_argument("-c", "--conffile", dest="conffile", default=conffile,
                    help="Conffile (default: %s)" % conffile)
args = parser.parse_args()
conffile = args.conffile

# setting up ###################################################################
setup(conffile, settings)

## logging #####################################################################
hdlr = logging.FileHandler(settings["DEFAULT"]["logfile"])
hdlr.setFormatter(logging.Formatter('%(levelname)s %(asctime)s %(message)s'))
logger = logging.getLogger('matrixbot')
logger.addHandler(hdlr)
logger.setLevel(settings["DEFAULT"]["loglevel"])
logger.debug("Default encoding: %s" % sys.getdefaultencoding())
debug_conffile(settings, logger)


## main ########################################################################
if __name__ == '__main__':
    period = settings["DEFAULT"]["period"]
    uri = settings["matrix"]["uri"]
    username = settings["matrix"]["username"]
    password = settings["matrix"]["password"]
    room_ids = settings["matrix"]["rooms"]

    client = MatrixClient(uri)
    token = client.login_with_password(username=username,
                                       password=password)
    matrix = MatrixHttpApi(uri, token=token)

    join_rooms(client, room_ids, silent=False)

    while True:
        sync(matrix)
        time.sleep(period)
sys.exit(0)
