#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim: ai ts=4 sts=4 et sw=4 nu
"""
(c) 2013 - Ronan Delacroix
Text Utils
:author: Ronan Delacroix
"""
import json
import datetime

import os
import re
import smtplib
import unicodedata
import six
import uuid as UUID
import base64
import yaml
import io

try:
    import toml
except:
    toml = None

from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.utils import COMMASPACE, formatdate
from email import encoders

if six.PY3:
    import html
else:
    import cgi as html


def convert_to_snake_case(name):
    s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
    return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()


def normalize_text(text):
    return unicodedata.normalize('NFKD', text).encode('ascii', 'ignore').decode('utf-8')


def slugify(text, delim='-'):
    """Generates an slightly worse ASCII-only slug."""
    punctuation_re = re.compile(r'[\t !"#$%&\'()*\-/<=>?@\[\\\]^_`{|},.:]+')

    result = []
    for word in punctuation_re.split(text.lower()):
        word = normalize_text(word)
        if word:
            result.append(word)
    return delim.join(result)


def slugify_bytes(b):
    return base64.urlsafe_b64encode(b).decode('utf-8').strip('=').replace('-', '0').replace('_', 'A')


def uuid_to_slug(uuid):
    if isinstance(uuid, str):
        b = UUID.UUID(uuid)
    elif isinstance(uuid, UUID.UUID):
        b = uuid.bytes
    elif isinstance(uuid, bytes):
        b = uuid
    else:
        b = bytes(uuid)
    return slugify_bytes(b)


def random_slug():
    return uuid_to_slug(UUID.uuid4().bytes)


def random_short_slug():
    return uuid_to_slug(UUID.uuid4().bytes[0:8])


def javascript_escape(s, quote_double_quotes=True):
    """
    Escape characters for javascript strings.
    """
    ustring_re = re.compile(u"([\u0080-\uffff])")

    def fix(match):
        return r"\u%04x" % ord(match.group(1))

    if type(s) == str:
        s = s.decode('utf-8')
    elif type(s) != six.text_type:
        raise TypeError(s)
    s = s.replace('\\', '\\\\')
    s = s.replace('\r', '\\r')
    s = s.replace('\n', '\\n')
    s = s.replace('\t', '\\t')
    s = s.replace("'", "\\'")
    if quote_double_quotes:
        s = s.replace('"', '&quot;')
    return str(ustring_re.sub(fix, s))


def send_mail(send_from, send_to, subject, text, server, mime='plain', files=None):
    """
    Send an email with attachments.
    :param send_from: from email adress
    :param send_to: to email adress
    :param subject: email subject
    :param text: text of the email in html
    :param server: SMTP server
    :param files: files to attach
    :return: None
    """
    if not files:
        files = []

    assert type(send_to) == list
    assert type(files) == list

    msg = MIMEMultipart()
    msg['From'] = send_from
    msg['To'] = COMMASPACE.join(send_to)
    msg['Date'] = formatdate(localtime=True)
    msg['Subject'] = subject

    msg.attach(MIMEText(text, mime))

    for f in files:
        part = MIMEBase('application', "octet-stream")
        fp = open(f, "rb")
        file_content = fp.read()
        part.set_payload(file_content)
        encoders.encode_base64(part)
        part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(f))
        msg.attach(part)

    smtp = smtplib.SMTP(server)
    smtp.sendmail(send_from, send_to, msg.as_string())
    smtp.close()
    return


def hms_to_seconds(time_string):
    """
    Converts string 'hh:mm:ss.ssssss' as a float
    """
    s = time_string.split(':')
    hours = int(s[0])
    minutes = int(s[1])
    secs = float(s[2])
    return hours * 3600 + minutes * 60 + secs


def seconds_to_hms_verbose(t):
    """
    Converts seconds float to 'H hours 8 minutes, 30 seconds' format
    """
    hours = int((t / 3600))
    mins = int((t / 60) % 60)
    secs = int(t % 60)
    return ' '.join([
        (hours + ' hour' + ('s' if hours > 1 else '')) if hours > 0 else '',
        (mins + ' minute' + ('s' if mins > 1 else '')) if mins > 0 else '',
        (secs + ' second' + ('s' if secs > 1 else '')) if secs > 0 else ''
    ])


def seconds_to_hms(seconds):
    """
    Converts seconds float to 'hh:mm:ss.ssssss' format.
    """
    hours = int(seconds / 3600.0)
    minutes = int((seconds / 60.0) % 60.0)
    secs = float(seconds % 60.0)
    return "{0:02d}:{1:02d}:{2:02.6f}".format(hours, minutes, secs)


def str_to_bool(v):
    return str(v).lower() in ("yes", "on", "true", "y", "t", "1")


def datetime_handler(obj):
    return obj.isoformat(sep=' ') if isinstance(obj, datetime.datetime) else None


def render_xml(_dict):
    return dict_to_xml_string("xml", _dict)


def render_json(_dict):
    return json.dumps(_dict, sort_keys=False, indent=4, default=datetime_handler)


def render_html(_dict):
    return dict_to_html(_dict)


def render_txt(_dict):
    return dict_to_plaintext(_dict)


def render_yaml(_dict):
    return yaml.dump(_dict, default_flow_style=False)


def render_toml(_dict):
    if toml:
        return toml.dumps(_dict)
    import logging
    logging.warning("TOML Serialisation is unavailable as toml package is not installed.")
    return render_yaml(_dict)


def render_ini(_dict):
    import configparser
    s = io.StringIO()
    p = configparser.ConfigParser()
    p.read_dict(_dict)
    p.write(s)
    s.close()
    return s.getvalue()


mime_rendering_dict = {
    'text/html': render_html,
    'application/html': render_html,
    'application/xml': render_xml,
    'application/json': render_json,
    'application/yaml': render_yaml,
    'text/plain': render_txt,
    'text/yaml': render_yaml,
    'text/toml': render_toml,
    'text/ini': render_ini
}


def render_dict_from_mimetype(d, mimetype):
    return mime_rendering_dict.get(mimetype, render_json)(d)


mime_shortcuts = {
    'html': 'text/html',
    'xml': 'application/xml',
    'json': 'application/json',
    'text': 'text/plain',
    'txt': 'text/plain',
    'yaml': 'text/yaml',
    'yml': 'text/yaml',
    'tml': 'text/toml',
    'toml': 'text/toml',
    'ini': 'text/ini'
}


def render_dict_from_format(d, format):
    return mime_rendering_dict.get(mime_shortcuts.get(format, 'application/json'))(d)


def pretty_render(data, format='text', indent=0):
    """
    Render a dict based on a format
    """
    if format == 'json':
        return render_json(data)
    elif format == 'html':
        return render_html(data)
    elif format == 'xml':
        return render_xml(data)
    else:
        return dict_to_plaintext(data, indent=indent)


# DICT TO XML FUNCTION
def _dict_to_xml_recurse(parent, dictitem):
    import lxml.etree as etree
    if isinstance(dictitem, list):
        dictitem = {'item': dictitem}
    if isinstance(dictitem, dict):
        for (tag, child) in dictitem.items():
            if str(tag) == '_text':
                parent.text = str(child)
            elif type(child) is type([]):
                # iterate through the array and convert
                for listchild in child:
                    elem = etree.Element(tag)
                    parent.append(elem)
                    _dict_to_xml_recurse(elem, listchild)
            elif len(tag) == 36 and tag[8] == '-' and tag[
                13] == '-':  # if uuid is name of the element we try to cook up something nice to display in xml
                uuid = tag
                tag = parent.tag.replace('_list', '').replace('_dict', '')
                elem = etree.Element(tag, uuid=uuid)
                parent.append(elem)
                _dict_to_xml_recurse(elem, child)
            else:
                try:
                    elem = etree.Element(tag)
                except ValueError:
                    elem = etree.Element("element", unrecognized=tag)
                parent.append(elem)
                _dict_to_xml_recurse(elem, child)
    else:
        parent.text = str(dictitem)


def dict_to_xml(xml_dict):
    """
    Converts a dictionary to an XML ElementTree Element
    """
    import lxml.etree as etree
    root_tag = list(xml_dict.keys())[0]
    root = etree.Element(root_tag)
    _dict_to_xml_recurse(root, xml_dict[root_tag])
    return root


def dict_to_xml_string(root_name, _dict):
    import lxml.etree as etree
    _dict = {root_name: _dict}
    xml_root = dict_to_xml(_dict)
    return etree.tostring(xml_root, pretty_print=True, encoding="UTF-8", xml_declaration=True)


# DICT TO TEXT FUNCTION
def dict_to_plaintext(_dict, indent=0, result=''):
    if isinstance(_dict, list):
        i = 0
        if not _dict:
            result += '\t' * indent + "<empty>\n"
        for value in _dict:
            i += 1
            if isinstance(value, dict):
                result += '\t' * indent + "[" + str(i) + "]={DICT}\n" + dict_to_plaintext(value, indent + 1)
            elif isinstance(value, list):
                result += '\t' * indent + "[" + str(i) + "]=<LIST>\n" + dict_to_plaintext(value, indent + 1) + "\n"
            else:
                result += '\t' * indent + "[" + str(i) + "]=\"" + str(value) + "\"\n"
        return result
    elif isinstance(_dict, dict):
        for key, value in _dict.items():
            if isinstance(value, dict):
                result += '\t' * indent + "{" + str(key) + "}\n" + dict_to_plaintext(value, indent + 1)
            elif isinstance(value, list):
                result += '\t' * indent + "<" + str(key) + '>\n' + dict_to_plaintext(value, indent + 1)
            else:
                if "\n" in str(value):
                    value = ' '.join([line.strip() for line in str(value).replace("\"", "'").split("\n")])
                result += '\t' * indent + str(key) + '=' + "\"" + str(value) + "\"\n"
        return result
    else:
        return "\"" + str(_dict) + "\""


# DICT TO HTML FUNCTION
def _dict_to_html_recurse(_dict, indent=0, result=''):
    if isinstance(_dict, list):
        i = 0
        result += '    ' * indent + "<ul>\n"
        for value in _dict:
            i += 1
            if isinstance(value, dict):
                result += '    ' * (indent + 1) + "<li class='row" + str(i % 2) + "'>\n" + _dict_to_html_recurse(value,
                                                                                                                 indent + 2) + '    ' * (
                indent + 1) + "</li>\n"
            elif isinstance(value, list):
                result += '    ' * (indent + 1) + "<li class='row" + str(i % 2) + "'>\n" + _dict_to_html_recurse(value,
                                                                                                                 indent + 2) + '    ' * (
                indent + 1) + "</li>\n"
            else:
                result += '    ' * (indent + 1) + "<li class='row" + str(i % 2) + "'><pre>" + html.escape(
                    str(value)) + "</pre></li>\n"
        result += '    ' * indent + "</ul>\n"
        return result
    elif isinstance(_dict, dict):
        result += '    ' * indent + "<table>\n"
        i = 0
        for key, value in _dict.items():
            i += 1
            if isinstance(value, dict) or isinstance(value, list):
                result += '    ' * (indent + 1) + "<tr class='row" + str(i % 2) + "'>\n"
                result += '    ' * (indent + 2) + "<td>" + str(key) + "</td>\n"
                result += '    ' * (indent + 2) + "<td>\n" + _dict_to_html_recurse(value, indent + 3)
                result += '    ' * (indent + 2) + "</td>\n"
                result += '    ' * (indent + 1) + "</tr>\n"
            else:
                value = html.escape(str(value))
                result += '    ' * (indent + 1) + "<tr class='row" + str(i % 2) + "'><td>" + str(
                    key) + "</td><td><pre>" + str(value) + "</pre></td></tr>\n"
        result += '    ' * indent + "</table>\n"
        return result
    else:
        return "<pre>" + html.escape(str(_dict)) + "</pre>"


def dict_to_html(_dict, title="Result"):
    return """
<html>
    <head>
        <style>
            body { font-family: monospace; }
            table { display : inline-block; border-spacing: 0px; border-collapse: collapse; }
            td { border : 1px solid grey; padding:3px 10px; }
            li { border : 1px solid grey; padding:0px 10px 0px 10px; margin: 0px 0px 0px 5px; list-style-type : circle; }
            ul { display : inline-block; padding:0px 0px 0px 10px; margin:0px;}
            pre { margin:0 ; }
            .row0 { background-color:#EAEAFF; }
            .row1 { background-color:#FFFFFF; }
        </style>
        <title>""" + title + """</title>
    </head>
    <body>
""" + _dict_to_html_recurse(_dict, 2) + "    </body>\n</html>"


def test_page(title="Result"):
    result = "<table>"
    docu = {}
    i = 0
    for func_name, doc in docu.items():
        result += "<tr class='row" + str(i) + "'><td>" + doc['friendly_name'] + "</td>"
        if 'parameters' in doc:
            result += "<td><form action='" + func_name + "' method='" + doc[
                'method_type'] + "' enctype='multipart/form-data'>"
            result += "<table width='100%'>"
            if 'required' in doc['parameters']:
                result += "<tr><th colspan='2'>Required</th></tr>"
                for param in doc['parameters']['required']:
                    if param == 'asset_file':
                        result += "<tr><td>" + str(param) + "</td><td><input type='file' name='" + str(
                            param) + "' value=''/></td><tr/>"
                    else:
                        result += "<tr><td>" + str(param) + "</td><td><input type='text' name='" + str(
                            param) + "' value=''/></td><tr/>"

            if 'optionnal' in doc['parameters']:
                result += "<tr><th colspan='2'>Optionnal</th></tr>"
                for param, value in doc['parameters']['optionnal'].items():
                    if value == None:
                        value = ''
                    result += "<tr><td>" + str(param) + "</td><td><input type='text' name='" + str(
                        param) + "' value='" + str(value) + "'/></td><tr/>"
            result += "<tr><th colspan='2'><input type='submit'/></th></tr>"
            result += "</table>"
            result += "</form></td>"
        else:
            result += "<td><a href='" + func_name + "'>" + func_name + "</a></td>"
        result += "</tr>"
        i += 1
        i = i % 2

    result += "</table>"
    return """
<html>
    <head>
        <style>
            body {font-family: monospace;}
            table {display : inline-block; border-spacing: 0px; border-collapse: collapse;}
            td {border: 1px solid grey; padding: 3px 10px;}
            li {border: 1px solid grey; padding: 0px 10px 0px 10px; margin: 0px 0px 0px 5px; list-style-type: circle;}
            ul {display: inline-block; padding: 0px 0px 0px 10px; margin:0px;}
            pre {margin: 0;}
            .row0 {background-color:#EAEAFF;}
            .row1 {background-color:#FFFFFF;}
        </style>
        <title>""" + title + """</title>
    </head>
    <body>
""" + result + """
    </body>
</html>"""


def uni(text):
    """
    Tries to force to convert to unicode a text.
    REALLY DIRTY HACK TO TRY TO DETECT ENCODINGS...
    :param text: text to convert
    :return: unicode text
    """
    if type(text) == six.text_type:
        for encoding in ['latin_1', 'ascii', 'utf-8']:
            try:
                strtext = text.encode(encoding)
            except:
                pass
            else:
                break
        text = strtext

    unitext = text
    for encoding in ['utf-8', 'ascii', 'latin_1']:
        try:
            unitext = text.decode(encoding)
        except:
            pass
        else:
            break
    return unitext


def xml_get_tag(xml, tag, parent_tag=None, multi_line=False):
    """
    Returns the tag data for the first instance of the named tag, or for all instances if multi is true.
    If a parent tag is specified, then that will be required before the tag.
    """
    expr_str = '[<:]' + tag + '.*?>(?P<matched_text>.+?)<'
    if parent_tag:
        expr_str = '[<:]' + parent_tag + '.*?>.*?' + expr_str
    expr = re.compile(expr_str, re.DOTALL | re.IGNORECASE)
    if multi_line:
        return expr.findall(xml)
    else:
        if expr.search(xml):
            return expr.search(xml).group('matched_text').strip()
        else:
            return None