# -*- coding: utf-8 -*-
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is Reportek version 1.0.
#
# The Initial Developer of the Original Code is European Environment
# Agency (EEA).  Portions created by Finsiel are
# Copyright (C) European Environment Agency.  All
# Rights Reserved.
#
# Contributor(s):
# Soren Roug, EEA

""" Generic functions module """

import os
import sys
import re
import tempfile
import traceback
import string,base64,time
import operator
from types import FunctionType
from urllib import FancyURLopener

def formatException(self, error):
     """
     Format and return the specified exception information as a string.

     This implementation builds the complete stack trace, combining
     traceback.format_exception and traceback.format_stack.
     """
     lines = traceback.format_exception(*error)
     if error[2]:
         lines[1:1] = traceback.format_stack(error[2].tb_frame.f_back)
     return ''.join(lines)

import logging
logger = logging.getLogger('Reportek.ReptUtils')
#logger.setLevel(logging.DEBUG)
logging.Formatter.formatException = formatException

bad_chars = ' ,+&;()[]{}\xC4\xC5\xC1\xC0\xC2\xC3' \
    '\xE4\xE5\xE1\xE0\xE2\xE3\xC7\xE7\xC9\xC8\xCA\xCB' \
    '\xC6\xE9\xE8\xEA\xEB\xE6\xCD\xCC\xCE\xCF\xED\xEC' \
    '\xEE\xEF\xD1\xF1\xD6\xD3\xD2\xD4\xD5\xD8\xF6\xF3' \
    '\xF2\xF4\xF5\xF8\x8A\x9A\xDF\xDC\xDA\xD9\xDB\xFC' \
    '\xFA\xF9\xFB\xDD\x9F\xFD\xFF\x8E\x9E'

good_chars= '___________AAAAAA' \
    'aaaaaaCcEEEE' \
    'EeeeeeIIIIii' \
    'iiNnOOOOOOoo' \
    'ooooSssUUUUu' \
    'uuuYYyyZz'

TRANSMAP = string.maketrans(bad_chars, good_chars)

def copy_file(infile, outfile):
    # Read binary data from infile and write it to outfile
    # infile and outfile may be strings, in which case a file with that
    # name is opened, or filehandles, in which case they are accessed
    # directly.

    if isinstance(infile, str):
        try:
            instream = open(infile, 'rb')
        except IOError:
            raise IOError, ("%s (%s)" %(self.id, infile))
        close_in = 1
    else:
        instream = infile
        close_in = 0

    if isinstance(outfile, str):
        try:
            outstream = open(outfile, 'wb')
        except IOError:
            raise IOError, ("%s (%s)" %(self.id, outfile))
        close_out = 1
    else:
        outstream = outfile
        close_out = 0

    try:
        blocksize = 2<<16
        block = instream.read(blocksize)
        outstream.write(block)
        while len(block)==blocksize:
            block = instream.read(blocksize)
            outstream.write(block)
    except IOError:
        raise IOError, ("%s (%s)" %(self.id, filename))
    if close_in: instream.close()
    if close_out: outstream.close()

def cleanup_id(str):
    """ Cleanup an id
        Should be more thorough and e.g. remove trailing dots/spaces
    """
    return string.translate(str, TRANSMAP)

def generate_id(template):
    """ Generate a unique string
    """
    if template != '' and template is not None:
        template = cleanup_id(template)
    else:
        template = ''
    k = int(time.time())
    id = ''
    for x in range(4):
        id = chr(k & 0xff) + id
        k >>= 8
    id= base64.encodestring(id)
    return template + string.translate(string.lower(id),TRANSMAP,"/=\n")

def xmlEncode(p_string):
    """Encode some special chars"""
    l_tmp = str(p_string)
    l_tmp = l_tmp.replace('&', '&amp;')
    l_tmp = l_tmp.replace('<', '&lt;')
    l_tmp = l_tmp.replace('"', '&quot;')
    l_tmp = l_tmp.replace('\'', '&apos;')
    l_tmp = l_tmp.replace('>', '&gt;')
    return l_tmp

#encode to UTF-8 from user encoding
def utGMLEncode(p_str, p_str_enc):
    """ Giving a string and an encoding, returns the string encoded to UTF-8
        If no encoding is provided it will assume as input encoding UTF-8 

        Also special characters that might appear in GML files are escaped
    """
    if p_str_enc == '':
        l_tmp = unicode(str(p_str), errors='replace')
    else:
         l_tmp = unicode(str(p_str),'%s' % p_str_enc, errors='replace')
    l_tmp = l_tmp.encode('utf8', 'replace')

    #xml entities
    l_tmp = l_tmp.replace('&', '&amp;')
    l_tmp = l_tmp.replace('<', '&lt;')
    l_tmp = l_tmp.replace('"', '&quot;')
    l_tmp = l_tmp.replace('\'', '&apos;')
    l_tmp = l_tmp.replace('>', '&gt;')

    #Microsoft Word entities
    l_tmp = l_tmp.replace('—', '-')
    l_tmp = l_tmp.replace('–', '-')
    l_tmp = l_tmp.replace('‘', "'")
    l_tmp = l_tmp.replace('’', "'")
    l_tmp = l_tmp.replace(' ', " ")
    l_tmp = l_tmp.replace('´', "'")
    l_tmp = l_tmp.replace('“', '&quot;')
    l_tmp = l_tmp.replace('”', '&quot;')
    l_tmp = l_tmp.replace('§', "-")
    l_tmp = l_tmp.replace('¤', " ")
    l_tmp = l_tmp.replace('«', "&quot;")
    l_tmp = l_tmp.replace('»', "&quot;")
    l_tmp = l_tmp.replace('…', "...")
    l_tmp = l_tmp.replace('•', "* ")

    return l_tmp

def asciiEncode(p_value):
    """ Gets a value and returns a string """
    if p_value is not None:
        return unicode(str(p_value), 'latin-1').encode('ascii', 'replace')
    else:
        return ''

def utRead(file):
    """ open file """
    if 'http' in file:
        opener = FancyURLopener()
        f = opener.open(file)
    else:
        f = open(file,'rb+')
    return f

def utf8Encode(p_str):
    """ encodes a string to UTF-8 """
    return p_str.encode('utf8')

def to_utf8(s):
    """ converts Unicode to UTF-8"""
    if isinstance(s, unicode):
        return s.encode('utf-8')
    else:
        return s

def utConvertToList(something):
    """Convert to list"""
    ret = something
    if not something:
        return []
    if type(something) is type(''):
        ret = [something]
    return ret

def utConvertListToLines(values):
    """Takes a list of values and returns a value for a textarea control"""
    if len(values) == 0: return ''
    else: return '\r\n'.join(values)

def utConvertLinesToList(value):
    """Takes a value from a textarea control and returns a list of values"""
    if type(value) == type([]): return value
    elif not value: return []
    else:
        values = []
        for v in value.split('\r\n'):
            if v != '': values.append(v)
    return values

def utIsSubsetOf(first_list, second_list):
    """ returns true if the first list is a subset of the second """
    for l_element in utConvertToList(first_list):
        if l_element not in second_list:
            return 0
    return 1

def utSortByAttr(p_obj_list, p_attr, p_sort_order=0):
    """ Sort a list of objects by one of the attributes """
    l_temp = map(None, map(getattr, p_obj_list, (p_attr,)*len(p_obj_list)), xrange(len(p_obj_list)), p_obj_list)
    l_temp.sort()
    if p_sort_order:
        l_temp.reverse()
    return map(operator.getitem, l_temp, (-1,)*len(l_temp))

def utSortByMethod(p_obj_list, p_attr, p_date, p_sort_order=0):
    """ Sort a list of objects by the result of one of their functions """
    l_temp = map(None, map(lambda x,y:eval('x.' + y + '()'), p_obj_list, (p_attr,)*len(p_obj_list)), xrange(len(p_obj_list)), p_obj_list, (p_date,)*len(p_obj_list))
    l_temp = filter(lambda x: x[0] < x[3], l_temp)
    l_temp.sort()
    if p_sort_order:
        l_temp.reverse()
    return map(operator.getitem, l_temp, (-2,)*len(l_temp))

def utGrabFromUrl(p_url):
    """ Takes a file from a remote server """
    from urllib import URLopener
    try:
        l_opener = URLopener()
        l_file = l_opener.open(p_url)
        ctype = l_file.headers['Content-Type']
        l_opener.close()
        return (l_file.read(), ctype)
    except:
        return (None, 'text/x-unknown-content-type')

def parse_template(template, dict={}):
    """ Make some text from a template file. """
    if dict is not None:
        try:
            text = string.Template(template)
            result = text.substitute(dict)
            return to_utf8(result)
        except (TypeError, ValueError, KeyError), error:
            logger.exception(error)
            raise Exception, "An error occurred while generating this file"

def utGetTemp():
    """ return the system temp dir """
    if sys.platform == "win32":
        return os.getenv('TEMP')
    else:
        return '/tmp'

def getFilename(filename):
    """ return only the filename, removing the path """
    return filename.split('\\')[-1]

def createTempFile(p_file, p_filename=''):
    """ create a file in system temp dir """
    if hasattr(p_file, 'filename'):
        l_data = p_file.read()
        l_filename = cookId(p_file)
    else:
        l_data = p_file
        l_filename = p_filename
    file_temp = open(os.path.join(utGetTemp(), l_filename), "wb")
    file_temp.write(l_data)
    file_temp.close()

def deleteTempFile(filename):
    """ delete a file from the system temp dir """
    os.unlink(os.path.join(utGetTemp(), '%s' % filename))

def cookId(file):
    """ generate a file ID """
    if hasattr(file,'filename'):
        filename = file.filename
        id = filename[max(filename.rfind('/'),
                        filename.rfind('\\'),
                        filename.rfind(':'),
                        )+1:]
        return id
    return file

def extractURLs(s):
    """ find all the URLs from a string """
    return re.findall('(?P<url>http[s]?://[-_&;,?:~=%#+/.0-9a-zA-Z]+)', s)


class TmpFile:

    def __init__(self, data):
        self.fname = tempfile.mktemp()
        open(self.fname,'w+b').write(data)

    def __str__(self): return self.fname
    __repr__ = __str__

    def __del__(self):
        os.unlink(self.fname)
