#!/usr/bin/python
import os
import sys
import gi
import mimetypes
import importlib.util
import urllib.request
import tempfile
import matrix_client.errors
from time import sleep
from matrix_client.client import MatrixClient
from matrix_client.api import MatrixHttpApi
from matrix_client.user import User

try:
    gi.require_version('Notify', '0.7')
    from gi.repository import Notify
except:
    pass


def get_parser():
    from argparse import ArgumentParser
    parser = ArgumentParser(description="command line matrix client")
    subparsers = parser.add_subparsers(title='subcommands',dest='subcommand')

    parser.add_argument("-s", "--server", dest="server",
                        help="server to login to")

    parser.add_argument("-u", "--username", dest="username",
                        help="username to login with")


    parser.add_argument("-p", "--password", dest="password",
                        help="the password")

    parser.add_argument("-c", "--config", dest="config",
                        help="custom configuration file")

    parser_send = subparsers.add_parser('send', help='send something to a room')
    group = parser_send.add_mutually_exclusive_group()
    group.add_argument("-r", "--room-id", dest="room_id",
                                help='specify the room id')
    group.add_argument("-a", "--room-alias", dest="room_alias",
                                help='specify the room by room alias')
    group = parser_send.add_mutually_exclusive_group()
    group.add_argument("-t", "--text", dest="text", action="store_true",
                            help="force the program to treat the content as text message")
    group.add_argument("-f", "--file", dest="file", action="store_true",
                            help="force the program to treat the content as a file")
    parser_send.add_argument("content")

    parser_listen = subparsers.add_parser('listen',
                                          help='listen forever for events')

    parser_rooms = subparsers.add_parser('rooms',
                                         help='get all joined rooms')

    parser_unread = subparsers.add_parser('unread',
                                         help='get unread notifications')
    parser_unread.add_argument("-f", "--follow", dest="follow",
                             action="store_true",
                            help="don't close connection and print the number of unread messages whenever updated")

    group = parser_unread.add_mutually_exclusive_group()
    group.add_argument("-r", "--room-id", dest="room_id",
                                help='specify the room id')
    group.add_argument("-a", "--room-alias", dest="room_alias",
                                help='specify the room by room alias')

    parser_tail = subparsers.add_parser('tail',
                                        help='print last messages')
    group = parser_tail.add_mutually_exclusive_group()
    group.add_argument("-r", "--room-id", dest="room_id",
                                help='specify the room id')
    group.add_argument("-a", "--room-alias", dest="room_alias",
                                help='specify the room by room alias')

    parser_tail.add_argument("-f", "--follow", dest="follow",
                             action="store_true",
                            help="wait for messages and print them as they come")
    parser_tail.add_argument("-n", "--messages", dest="messages_number", default=10,
                             type=int, choices=range(1, 100), metavar="[1-100]",
                             help='print the last specified messages')

    return parser

def load_config():
    home = os.getenv('HOME')
    if args.config is None:
        if os.path.exists(home + '/.config/matrixcli/config.py'):
            config_path = home + '/.config/matrixcli/config.py'
        else:
            config_path = '/etc/matrixcli/config.py'
    else:
        if os.path.exists(args.config):
            if args.config.endswith('.py'):
                config_path = args.config
            else:
                print("config file must be python file ends with .py", file=sys.stderr)
                exit(1)
        else:
            print("the specified config file does not exist", file=sys.stderr)
            exit(1)

    try:
        spec = importlib.util.spec_from_file_location("config.py", config_path)
        config = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(config)
        try:
            config.ignore_rooms
        except AttributeError:
            config.ignore_rooms = []

    except FileNotFoundError:
        print("WARNING: config file does not exist", flush=True)
    return config, config_path

def config_vs_options():
    """ responsible of the logic of deciding how to choose the right
server, username and password from the specified options and the
configuration file by trying to figure out what the user probably mean
    """
    try :
        # if there is a configuration file
        # the default is to use the first account

        server = config.accounts[0]["server"]
        username = config.accounts[0]["username"]
        password = config.accounts[0]["passeval"]()

        if args.username is not None:
            # if username specified use it
            username = args.username

            indexes = [ index for index, account in enumerate(config.accounts) if account['username']==args.username]
            occurences = len(indexes)
            if occurences == 1:
                server = config.accounts[indexes[0]]['server']
                password = config.accounts[indexes[0]]['passeval']()
            elif occurences == 0:
                # if this username does not appear in the config file require server and password
                if args.password is None or args.server is None:
                    print("we cant find any occurence of username {0} in the config file,\n".format(args.username) +
                          "in this case you have to pass the password and server through the command line options", file=sys.stderr)
                    exit(1)
                else:
                    password = args.password
                    server = args.server
            else:
                # occurences are more than one
                if args.server is None:
                    # if the user didn't specify the server choose the first account
                    server = config.accounts[indexes[0]]["server"]
                    password = config.accounts[indexes[0]]["passeval"]()
                else:
                    for index in indexes:
                        if config.accounts[index]["server"] == args_server:
                            server = args_server
                            password = config.accounts[index]["passeval"]()
                            break

            if args.password is not None:
                # if the user specified a password use it and neglect any configuration password
                password = args.password
        else:
            if args.password is not None or args.server is not None:
                print("you can't specify password or server without username", file=sys.stderr)
                exit(1)

        return server, username, password
    except AttributeError as err:
        # if there is no configuration file require the three command line
        # arguments to be not None

        if all([args.server, args.password, args.username]):
            return args_server, args_username, args_password
        else :
            print("config file with accounts list does not exist, you have to specify the --server, --username and --password \n" +
                  "error raised: {0}".format(err), file=sys.stderr)
            exit(1)

def get_room_id():
    if args.room_id is not None:
        return args.room_id
    elif args.room_alias is not None:
        return api.get_room_id(args.room_alias)
    else : return None

def str_event(event):
    ''' responsible for the str representation of the events.
    arguments:
        event -- dictionary containing the api event response
    returns a tuple of:
        room_name (empty string if its a direct chat)
        sender_name -- sender name
        content -- content representation
    '''
    room = client.rooms[event['room_id']]
    if len(room.get_joined_members()) > 2:
        room_name = "(" + room.display_name + ") "
    else:
        room_name = ""

    while True:
        try:
            sender_name = client.get_user(event["sender"]).get_display_name()
            avatar_url = client.get_user(event["sender"]).get_avatar_url()
            break
        except:
            listen_handler()

    if event['type'] == "m.room.message":
        if event['content']['msgtype'] == "m.text":
            content = event['content']['body']
        else :
            download_url = api.get_download_url(event['content']['url'])
            content = download_url
    else:
        content =  "\n{{ " + event['type'] +  " event }}\n"

    return room_name, sender_name, avatar_url, content

def maybe_notify(title, content,avatar_url):
    """if the system is running headless or any problem happened with system notifications don't complain """
    try:
        if avatar_url:
            bullshit, avatar_file = tempfile.mkstemp()
            urllib.request.urlretrieve(avatar_url, avatar_file)
        else:
            avatar_file = None
        Notify.init("matrix")
        Notify.Notification.new(title, content, avatar_file).show()
        Notify.uninit()
    except:
        pass

def listen_handler(e):
    sleep(2) # for now we just wait and that's enough

def listen_callback(event):
    if event["sender"] != client.user_id:
        room_name, sender_name, avatar_url, content = str_event(event)
        title = (sender_name if room_name == "" else sender_name + " in " + room_name) + ":"
        maybe_notify(title, content, avatar_url)
        print(title, content, flush=True)

def on_message(room, event):
    if event["sender"] != client.user_id:
        room_name, sender_name, avatar_url, content = str_event(event)
        maybe_notify(sender_name, content, avatar_url)
        print(sender_name + ":", content, flush=True)

def choose_room():
    """ prompts the user to choose a room from his joined rooms and returns the room_id """
    room_id = get_room_id()
    if room_id is not None:
        try:
            room = client.rooms[room_id]
            return room
        except KeyError:
            print("this room does not exist or you are not joined\n", file=sys.stderr)
    enum_rooms = print_rooms()
    choice = int(input("enter room number : "))
    print("", flush=True)
    room = list(enum_rooms)[choice][1][1]
    return room

def print_rooms():
    enum_rooms = list(enumerate(client.rooms.items()))
    for i, (k, v) in enum_rooms:
        print(i, v.display_name , k, sep=" : ", flush=True)
    print("", flush=True)
    return enum_rooms

def listen():
    print("started listening ...", flush=True)
    client.add_listener(listen_callback)
    client.listen_forever(exception_handler=listen_handler)

def send():
    room = choose_room()
    if os.path.isfile(args.content) and not args.text:
        filename = os.path.basename(args.content)
        mimetype = mimetypes.guess_type(args.content)[0]

        with open( args.content , 'rb') as fobj:
            content = fobj.read()
        mxc_url = client.upload(content, mimetype)

        mime_func = { "image": room.send_image, "audio": room.send_audio,
                      "video": room.send_video, "text": room.send_file}
        # os.path.dirname takes the part before the slash in the mime type
        mime_func[os.path.dirname(mimetype)](mxc_url, filename)
    elif not args.file:
        room.send_text(args.content)
    else:
        print("file does not exist", file=sys.stderr)
        exit(1)

def tail():
    room = choose_room()
    if args.messages_number > 10 :
        room.backfill_previous_messages(limit=args.messages_number-10)
    for event in room.events[-1*args.messages_number:]:
        room_name, sender_name, avatar_url, content = str_event(event)
        print(sender_name + ":", content, flush=True)
    if args.follow :
        room.add_listener(on_message)
        client.start_listener_thread(exception_handler=listen_handler)

        while True:
            msg = input()
            room.send_text(msg)

def unread_callback(dump1, dump2=False):
    sleep(1) # a brief delay needed when we send messages
    unread(oneshot=True)


def unread(oneshot=False):
    response = api.sync()
    room_id = get_room_id()

    if room_id is None:
        sum = 0
        for room_id, v in client.rooms.items():
            if room_id not in config.ignore_rooms:
                try:
                    sum += response['rooms']['join'][room_id]['unread_notifications']['notification_count']
                except KeyError:
                    sum += 0
        print(sum, flush=True)
        if args.follow and not oneshot:
            client.add_listener(unread_callback)
            client.start_listener_thread(exception_handler=listen_handler)
            while True:
                sleep(2)
                unread_callback(None)
    else:
        try:
            print(response['rooms']['join'][room_id]['unread_notifications']['notification_count'], flush=True)
            if args.follow and not oneshot:
                # add a room listener that's calls unread(oneshot=True)
                room = client.rooms[room_id]
                room.add_listener(unread_callback)
                client.listen_forever()
        except KeyError:
            print("problems fetching unread notifications, maybe wrong room id?", file=sys.stderr)
            exit(1)

#--------------------------------------------

args = get_parser().parse_args()
config, config_path = load_config()
server, username, password = config_vs_options()

print("logging in ....", flush=True)

while True:
    # loop until no errors
    try:
        client = MatrixClient(server)
        token = client.login(username,password)
        api = MatrixHttpApi(server, token)
        break
    except matrix_client.errors.MatrixHttpLibError:
        print('error connecting. retrying after 1 second...', file=sys.stderr)
        sleep(1)

print("logged in ....", flush=True)

subcommand = { "listen": listen,
               "rooms": print_rooms,
               "send": send,
               "tail": tail,
               "unread": unread,
               None: print_rooms}

subcommand[args.subcommand]()
