#!/usr/bin/env python
# -*- coding: utf-8 -*-
# PYTHON_ARGCOMPLETE_OK
import argparse
import codecs
import keyring
import locale
import re
import os
import sys
import requests
import configobj
import getpass
import threading
from HTMLParser import HTMLParser
from operator import itemgetter


try:
    import argcomplete
    __autocomplete = True
except:
    __autocomplete = False


class bcolors:
    HEADER = '\033[1m\033[95m'
    OKBLUE = '\033[94m'
    OKGREEN = '\033[1m\033[92m'
    FAIL = '\033[1m\033[91m'
    ENDC = '\033[0m'


class Puncher(object):
    APP_NAME = 'ttzentyal puncher'
    APP_NAME_TTZENTYAL = "Zentyal Time Tracker"
    BASE_URL = 'https://tt.zentyal.com'
    ROUTES = {'punch-in': '/timetracking/punch/punch_in',
              'punch-out': '/timetracking/punch/punch_out',
              'punch': '/timetracking/punch',
              'hq': '/timetracking/hq',
              'adage-up': '/timetracking/adage/up',
              'adage-down': '/timetracking/adage/down'}

    def __init__(self):
        self._get_username()
        self._get_password()

    def _get_username(self):
        conf_file_punch = config_file = os.path.join(os.environ['HOME'], '.ttpuncher')
        if not os.path.exists(config_file):
            config_file = os.path.join(os.environ['HOME'], '.ttzentyal')
        try:
            conf_obj = configobj.ConfigObj(config_file, encoding='UTF8')
            self.username = conf_obj["username"]
        except:
            print ("You need to create a %s file and define username key, something like:\n"
                   "username=jconnor" % conf_file_punch)
            sys.exit(1)

    def _get_password(self):
        self.password = keyring.get_password(self.APP_NAME, self.username)
        if not self.password:
            self.password = keyring.get_password(self.APP_NAME_TTZENTYAL, self.username)
        if not self.password:
            self.password = getpass.getpass(prompt=("Password for %s: " % self.username))
            keyring.set_password(self.APP_NAME, self.username, self.password)

    def punch_in(self):
        body = self._get('punch-in')
        if self.ROUTES['punch-out'] not in body:
            raise RuntimeError("Punch in failed")

        return self.working_info(body)

    def punch_out(self):
        body = self._get('punch-out')
        if self.ROUTES['punch-in'] not in body:
            raise RuntimeError("Punch out failed")

        return self.working_info(body)

    def vote_up(self):
        self._post('adage-up')

    def vote_down(self):
        self._post('adage-down')

    def people_working(self):
        """
        Return a dict:
           - working : names of people working
           - holidays: list of lists of people on holidays
                       0 - name  1 - start  2 - end
        """
        body = self._get('hq')
        name_re = re.compile(r"<td>([^0-9]*)</td>")
        holiday_re = re.compile(r'<a href="/flextime/vacations\?user.*>([^0-9]+)</a>')
        date_re = re.compile(r'<td>([0-9]{2,4}-[0-9]{2}-[0-9]{2})</td>')
        working = []
        holidays = []
        on_holiday = False
        for line in body.split("\n"):
            if on_holiday:
                date = date_re.search(line)
                if date:
                    holidays[-1].append(date.group(1))
                    if len(holidays[-1]) == 3:
                        on_holiday = False
            else:
                name = name_re.search(line)
                if name:
                    working.append(name.group(1))
                else:
                    name = holiday_re.search(line)
                    if name and len(name.group(1).split(' ')) > 1:
                        holidays.append([name.group(1)])
                        on_holiday = True

        # Return the names sorted by first name
        # And people on holidays by the first to finish them
        return {'working': sorted(working),
                'holidays': sorted(holidays, key=itemgetter(2))}

    def working_info(self, body=None):
        """
        Return a tuple of:
          - Today worked hours
          - Week worked hours
          - Punched_in (boolean)
          - adage message
          - adage creator
          - adage author
          - adage votes
          - adage karma
        """
        if body is None:
            body = self._get('punch')

        day_re = re.compile(r"d.a[^0-9]*([0-9]+:[0-9]+)")
        week_re = re.compile(r"semana[^0-9]*([0-9]+:[0-9]+)")
        day = day_re.search(body)
        week = week_re.search(body)

        day = day.group(1) if day else '?'
        week = week.group(1) if week else '?'

        is_punched_in = self.ROUTES['punch-out'] in body

        adage_message = re.search(r"<pre class=\"adage_message\">(.*)</pre>", body, re.DOTALL)
        adage_creator = re.search(r"<p class=\"adage_creator\">(.*)</p>", body)
        adage_author = re.search(r"<p class=\"adage_author\">(.*)</p>", body)
        adage_votes = re.search(r"Votos: (.*)", body)
        adage_karma = re.search(r"Karma: (.*)", body)

        adage_message = adage_message.group(1) if adage_message else '?'
        adage_creator = adage_creator.group(1) if adage_creator else '?'
        adage_author = adage_author.group(1) if adage_author else '?'
        adage_votes = adage_votes.group(1) if adage_votes else '?'
        adage_karma = adage_karma.group(1) if adage_karma else '?'

        html_parser = HTMLParser()
        adage_message = html_parser.unescape(adage_message)
        adage_creator = html_parser.unescape(adage_creator)

        return (day, week, is_punched_in, adage_message,
                adage_creator, adage_author, adage_votes, adage_karma)

    def _get(self, route):
        return requests.get(self.BASE_URL + self.ROUTES[route],
                            auth=(self.username, self.password)).text

    def _post(self, route):
        return requests.post(self.BASE_URL + self.ROUTES[route],
                             auth=(self.username, self.password)).text


def print_people_working(people_working):
    print bcolors.HEADER + "People working"
    print "==============" + bcolors.ENDC + bcolors.OKBLUE
    for person in people_working['working']:
        print person
    print bcolors.ENDC

    if people_working['holidays']:
        # find the length of the longest person and add 2
        max_length = max(len(p[0]) for p in people_working['holidays']) + 2
        print bcolors.HEADER + "People on holidays"
        print "==================" + bcolors.ENDC + bcolors.OKBLUE
        for person, start, end in people_working['holidays']:
            print u"%s (%s -> %s)" % (person.ljust(max_length), start, end)
        print bcolors.ENDC


def print_working_info(working_info):
    print bcolors.OKBLUE + "Today hours: " + bcolors.OKGREEN + working_info[0] + bcolors.ENDC
    print bcolors.OKBLUE + "Week hours: " + working_info[1] + bcolors.ENDC
    print bcolors.ENDC
    print bcolors.OKBLUE + "Adage: " + bcolors.OKGREEN + working_info[3] + bcolors.ENDC
    print (bcolors.OKBLUE + "created by: " + bcolors.OKGREEN + working_info[4] + bcolors.ENDC + " " +
           bcolors.OKBLUE + "author: " + bcolors.OKGREEN + working_info[5] + bcolors.ENDC)
    print (bcolors.OKBLUE + "votes: " + bcolors.OKGREEN + working_info[6] + bcolors.ENDC + " " +
           bcolors.OKBLUE + "karma: " + bcolors.OKGREEN + working_info[7] + bcolors.ENDC)
    print bcolors.ENDC


def usage():
    print "Use %s in|out|status|vote [+|-]" % sys.argv[0]


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Puncher application. " + "Use %s in|out|status|vote [+|-]" % sys.argv[0])
    subparsers = parser.add_subparsers(title="punch actions", dest="punch_action")

    # Create the sub-parsers for every sub-command
    in_parser = subparsers.add_parser('in', help="Punch in")
    out_parser = subparsers.add_parser('out', help="Punch out")
    status_parser = subparsers.add_parser('status', help="Status")
    st_parser = subparsers.add_parser('st', help="Status")

    vote_parser = subparsers.add_parser('vote', help="Adage vote")
    vote_parser.add_argument('vote_action', type=str,
                             choices=('+', '-'),
                             help="+ to vote a good adage, - to vote a bad adage")

    if __autocomplete:
        argcomplete.autocomplete(parser)

    args = parser.parse_args()

    p = Puncher()

    if sys.stdout.encoding is None:
        # It happens on pipelining the output to another program like grep
        sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout)

    if args.punch_action == 'in':
        try:
            working_info = p.punch_in()
            print bcolors.OKGREEN + "Punch in done!" + bcolors.ENDC
            print ''

            print_working_info(working_info)
            print_people_working(p.people_working())
        except:
            print bcolors.FAIL + "I couldn't punch in :-/" + bcolors.ENDC

    elif args.punch_action == 'out':
        try:
            working_info = p.punch_out()
            print bcolors.OKGREEN + "Punch out done!" + bcolors.ENDC
            print ''

            print_working_info(working_info)
        except:
            print bcolors.FAIL + "I couldn't punch out :-/" + bcolors.ENDC

    elif args.punch_action == 'vote':
        if args.vote_action == '+':
            p.vote_up()
        elif args.vote_action == '-':
            p.vote_down()
        print bcolors.OKGREEN + "Vote sent!" + bcolors.ENDC

    elif args.punch_action in ('status', 'st'):
        people_working = None
        is_punched_in = False
        working_info = None

        def fetch_people_working():
            global people_working
            people_working = p.people_working()

        def fetch_working_info():
            global is_punched_in
            global working_info
            working_info = p.working_info()
            is_punched_in = working_info[2]

        threads = [threading.Thread(target=fetch_people_working),
                   threading.Thread(target=fetch_working_info)]
        for t in threads:
            t.start()
        for t in threads:
            t.join()

        print ''
        if is_punched_in:
            print bcolors.OKGREEN + "Punched [IN]" + bcolors.ENDC
        else:
            print bcolors.FAIL + "Punched [OUT]" + bcolors.ENDC
        print ''
        print_working_info(working_info)

        # print what other are doing
        print_people_working(people_working)

    else:
        # This path is never reached :)
        usage()
