#!/usr/bin/env python
# -*- coding: utf-8 -*-

from os import listdir, path
import re
import copy
import datetime
import matplotlib.pyplot as plt


def get_mails(*folders):
    """ Gets the filename of every mail to analyze

    :param folders: Folders to analyze (comma separated arguments)
    :return mails: List of all files in given folders

    :type folders: Tuple of strings
    :rtype: list
    """
    mails = []
    for folder in folders:
        temp = [path.join(folder, file) for file in listdir(folder)
                if path.isfile(path.join(folder, file))]
        mails = mails + temp

    return mails


def get_data(mails, headers):
    """ Gets data specified in headers from the mails

    :param mails: List of mail filenames. Output of get_mails
    :params headers: List of mail headers to get info from
    :return data: Dictionary of the data associated with the headers
                  Each index is named as the corresponding header. The value is
                  a list of lines, each one belonging to one mail.
    :return cnt_skipped: Counter of skipped files due to Unicode decoding errors

    :type mails: List
    :type headers: List
    :rtype data: Dictionary
    :rtype cnt_skipped: int
    """

    # Init  empty dictionary
    data = {}
    for i in headers:
        data[i] = []

    cnt_skipped = 0
    for file in mails:
        with open(file, 'r') as f:

            # If text files are not unicode, there will be an error.
            # However detecting formats is not trivial, right now we
            # are just ignoring files producing errors.
            try:
                lines = f.readlines()
            except UnicodeDecodeError:
                cnt_skipped = cnt_skipped + 1
                continue

            for line in lines:
                for header in headers:
                    if line[0:len(header)] == header:
                        # TODO: +1line? (next one starts \t)
                        data[header].append(line[len(header):-1])

    return (data, cnt_skipped)


def regex_mails(from_to):
    """ Gets the email address (only) from the from/to field, removing
    everything else.

    E.g: Super Lopez Jimenez <superlopez@jimenez.com> --> superlopez@jimenez.com

    :param from_to: Field from/to from email header.
    :return data: List of all files in given folders

    :type from_to: string
    :rtype email_add: string

    Reference:
        http://stackoverflow.com/questions/4026685/regex-to-get-text-between-two-characters
    """

    regex_str = "(?<=<)[^}]*(?=>)"
    temp = re.search(regex_str, from_to)
    if not temp:
        email_add = []
    else:
        email_add = temp.group(0)

    return email_add


def date_format(date):
    """  Changes date format to python datetime object.

    Input format: Thu, [0]4 Sep 2014 14:40:18 +0200 [(CET)]
    Depends of mail

    :param date:  Input date
    :return date_out: List of all files in given folders

    :type date: string
    :rtype date_out: date object

    Reference:
        http://stackoverflow.com/questions/4666973/how-to-extract-a-substring-from-inside-a-string-in-python
    """

    regex_str = ", (.+?):"
    temp = re.search(regex_str, date)
    if temp:
        temp = temp.group(0)
        # Less hardcode?
        month_table = {
            "Jan": 1,
            "Feb": 2,
            "Mar": 3,
            "Apr": 4,
            "May": 5,
            "Jun": 6,
            "Jul": 7,
            "Aug": 8,
            "Sep": 9,
            "Oct": 10,
            "Nov": 11,
            "Dec": 12,
        }
        year = int(temp[-8:-4])
        month = month_table[temp[-12:-9]]
        day = int(temp[-15:-13])
        hour = int(date[len(temp):len(temp)+2])
        minute = int(date[len(temp)+3:len(temp)+5])
        second = int(date[len(temp)+6:len(temp)+8])
        try:
            offset = int(date[len(temp)+9:len(temp)+12])
        except:
            # Some mails do not have UTC offset in numbers, just as string
            # We will ignore this, though it wouldn't be difficult to map
            # strings to number using a dictionary (TODO)
            offset = 0
        hour = (hour + offset) % 24

        date_out = datetime.datetime(year, month, day, hour, minute, second)

    else:
        date_out = []

    return date_out


def data_munge(data_raw):
    """ Changes data format
          Emails list is replaced with a dictionary with only one entry for
          each email (and a counter), deleting also aliases
          Build a python datetime object from the Date header

    :param data_raw: Output of get_data
    :return data: Dicitonary with munged data

    :type data_raw: dictionary
    :rtype data: dictionary
    """

    # We can make it faster using a shallow copy if we are not interested in
    # keeping data_raw.
    data = copy.deepcopy(data_raw)

    # Changing email from/to fields
    for header in ['From: ', 'To: ']:
        if header in data_raw.keys():
            for ind in range(len(data[header])):
                # TODO: Handling multiple receivers
                data[header][ind] = regex_mails(data[header][ind])
            temp = {}
            for addr in data[header]:
                if addr:  # not empty
                    if addr in temp:
                        temp[addr] = temp[addr] + 1
                    else:
                        temp[addr] = 1
            data[header] = temp

    if 'Date: ' in data_raw.keys():
        for ind in range(len(data['Date: '])):
            data['Date: '][ind] = date_format(data['Date: '][ind])

    return data


def hour_mails(data):
    """ Calculate percentage of mails sent on each hour of the day

    :param data: Output of data_munge
    :return cnt: List of counters. First element is at 1am,  second at 2am...

    :type data: dictionary
    rtype cnt: list
    """
    cnt = [0]*24
    cnt_total = 0
    if 'Date: ' in data.keys():
        for date in data['Date: ']:
            if date:  # not empty
                cnt[date.hour] = cnt[date.hour] + 1
                cnt_total = cnt_total + 1

    cnt = [100*x/cnt_total for x in cnt]
    return cnt


def emails_involved(data):
    """ Prints to a file emails involved and a counter for each one

    :param data: Output of data_munge
    :type data: dictionary

    Reference:
        http://stackoverflow.com/questions/613183/sort-a-python-dictionary-by-value
    """
    filename_out = 'resultados.txt'
    with open(filename_out, 'w') as f:
        if 'To: ' in data.keys():
            f.write('******************************************************\n')
            f.write('                      To                              \n')
            f.write('               E-mail + Counter\n')
            f.write('------------------------------------------------------\n')
            for mail, cnt in \
                    sorted(data['To: '].items(),
                           key=lambda x: x[1], reverse=True):
                f.write(mail + '\t' + str(cnt) + '\n')
            f.write('******************************************************\n')
        if 'From: ' in data.keys():
            f.write('******************************************************\n')
            f.write('                     From                             \n')
            f.write('               E-mail + Counter\n')
            f.write('------------------------------------------------------\n')
            for mail, cnt in \
                    sorted(data['From: '].items(),
                           key=lambda x: x[1], reverse=True):
                f.write(mail + '\t' + str(cnt) + '\n')
            f.write('******************************************************\n')

if __name__ == '__main__':
    debug = False

    mails = get_mails('./Inbox', './Sent')

    headers = ['Date: ', 'From: ', 'To: ', 'Subject: ']
    (data_raw, cnt_skipped) = get_data(mails, headers)
    data = data_munge(data_raw)

    hour_cnt = hour_mails(data)

    # Output
    emails_involved(data)

    fig1 = plt.figure()
    axis1 = fig1.add_subplot(111)
    axis1.set_title('Percentage of mails sent each hour of the day')
    axis1.set_xlabel('Hour')
    axis1.set_ylabel('%')
    x_labels = list(range(1, 25))
    axis1.bar(x_labels, hour_cnt, align='center')
    axis1.set_xticks(x_labels)
    axis1.set_xlim(1, 24)
    plt.show()

    # TODO: analyse content, limit data to time frame...

    if debug is True:
        print('{} skipped files due to unicode decoding error'
              .format(cnt_skipped))

# Mails saved using offlineimap and a google account.
# Folders needed: inbox and Sent.
# Notes:
#   In python variables defined in main are global, but I prefer to declare
#   arguments
# References:
#
