# -*- coding: utf-8 -*-
import re

from bs4 import BeautifulSoup, element
from copy import copy

try:
    # Python 2
    from urlparse import parse_qs, urlsplit
except ImportError:
    # Python 3
    from urllib.parse import parse_qs, urlsplit

try:
    # Python 2
    unicode
except NameError:
    # Python 3
    unicode = str

ATTR_WHITELIST = {
    'a': ['href'],
    'img': ['src', 'alt'],
}

class CopyDoc:
    """
    Clean up Google Doc html.

    To use, create a doc parser instance:

    .. code:: python

        parser = CopyDoc('
My fake doc
')

    Access rendered, parsed document:

    .. code:: python

        print(str(parser))

    Access parsed, Beautifulsoup object:

    ..code:: python

        soup = parser.soup
    """
    def __init__(self, html_string, tokens=[]):
        """
        Constructor takes an HTML string and sets up object.
        """
        self.soup = BeautifulSoup(html_string, 'html.parser')
        self.tags_blacklist = []
        self.tokens = tokens
        self.parse()

    def parse(self):
        """
        Run all parsing functions.
        """
        for tag in self.soup.findAll('span'):
            self.create_italic(tag)
            self.create_strong(tag)
            self.create_underline(tag)
            self.unwrap_span(tag)

        for tag in self.soup.findAll('a'):
            self.remove_comments(tag)
            self.check_next(tag)

        if self.soup.body:
            for tag in self.soup.body.findAll():
                self.remove_empty(tag)
                self.remove_inline_comment(tag)
                self.parse_attrs(tag)
                for token, target in self.tokens:
                    self.find_token(tag, token, target)

                self.remove_blacklisted_tags(tag)

    def remove_comments(self, tag):
        """
        Remove comments.
        """
        if tag.get('id', '').startswith('cmnt'):
            tag.parent.extract()

    def check_next(self, tag):
        """
        If next tag is link with same href, combine them.
        """
        if (type(tag.next_sibling) == element.Tag and
                tag.next_sibling.name == 'a'):

            next_tag = tag.next_sibling
            if tag.get('href') and next_tag.get('href'):
                href = self._parse_href(tag.get('href'))
                next_href = self._parse_href(next_tag.get('href'))

                if href == next_href:
                    next_text = next_tag.get_text()
                    tag.append(next_text)
                    self.tags_blacklist.append(next_tag)

    def remove_blacklisted_tags(self, tag):
        if tag in self.tags_blacklist:
            tag.decompose()

    def create_italic(self, tag):
        """
        See if span tag has italic style and wrap with em tag.
        """
        style = tag.get('style')
        if style and 'font-style:italic' in style:
            tag.wrap(self.soup.new_tag('em'))

    def create_strong(self, tag):
        """
        See if span tag has bold style and wrap with strong tag.
        """
        style = tag.get('style')
        if (style and
                ('font-weight:bold' in style or 'font-weight:700' in style)):
            tag.wrap(self.soup.new_tag('strong'))

    def create_underline(self, tag):
        """
        See if span tag has underline style and wrap with u tag.
        """
        style = tag.get('style')
        if style and 'text-decoration:underline' in style:
            tag.wrap(self.soup.new_tag('u'))

    def unwrap_span(self, tag):
        """
        Remove span tags while preserving contents.
        """
        tag.unwrap()

    def parse_attrs(self, tag):
        """
        Reject attributes not defined in ATTR_WHITELIST.
        """
        if tag.name in ATTR_WHITELIST.keys():
            attrs = copy(tag.attrs)
            for attr, value in attrs.items():
                if attr in ATTR_WHITELIST[tag.name]:
                    tag.attrs[attr] = self._parse_attr(tag.name, attr, value)
                else:
                    del tag.attrs[attr]
        else:
            tag.attrs = {}

    def remove_empty(self, tag):
        """
        Remove non-self-closing tags with no children *and* no content.
        """
        has_children = len(tag.contents)
        has_text = len(list(tag.stripped_strings))
        if not has_children and not has_text and not tag.is_empty_element:
            tag.extract()

    def find_token(self, tag, token, attr):
        try:
            if not hasattr(self, attr) or not getattr(self, attr):
                text = tag.text
                if text.startswith(token):
                    setattr(self, attr, text.split(':', 1)[-1].strip())
                    tag.extract()
        except TypeError:
            pass

    def remove_inline_comment(self, tag):
        text = tag.text
        if text.startswith('##'):
            tag.extract()

    def clean_linebreaks(self, tag):
        """
        get unicode string without any other content transformation.
        and clean extra spaces
        """
        stripped = tag.decode(formatter=None)
        stripped = re.sub('\s+', ' ', stripped)
        stripped = re.sub('\n', '', stripped)
        return stripped

    def _parse_href(self, href):
        """
        Extract "real" URL from Google redirected url by getting `q`
        querystring parameter.
        """
        params = parse_qs(urlsplit(href).query)
        return params.get('q')

    def _parse_attr(self, tagname, attr, value):
        """
        Parse attribute. Delegate to href parser for hrefs, otherwise return
        value.
        """
        if tagname == 'a' and attr == 'href':
            return self._parse_href(value)
        else:
            return value

    def __unicode__(self):
        if not self.soup.body:
            return ''
        else:
            return ''.join([unicode(self.clean_linebreaks(tag))
                            for tag in self.soup.body.children])

    def __str__(self):
        if not self.soup.body:
            return ''
        else:
            return ''.join([str(self.clean_linebreaks(tag))
                            for tag in self.soup.body.children])


# coding=utf-8
# -------------------------------------------------------------------------------
# Author: Alexandre Manhaes Savio <alexsavio@gmail.com>
# Grupo de Inteligencia Computational <www.ehu.es/ccwintco>
# Universidad del Pais Vasco UPV/EHU
#
# 2015, Alexandre Manhaes Savio
# Use this at your own risk!
# -------------------------------------------------------------------------------
import json

def translate_key_values(adict, translations, default=''):
    """Modify the keys in adict to the ones in translations.
    Be careful, this will modify your input dictionary.
    The keys not present in translations will be left intact.

    Parameters
    ----------
    adict: a dictionary

    translations: iterable of 2-tuples
    Each 2-tuple must have the following format:
    (<adict existing key>, <desired key name for the existing key>)

    Returns
    -------
    Translated adict
    """
    for src_key, dst_key in translations:
        adict[dst_key] = adict.pop(src_key, default)
    return adict

def json_to_dict(json_str):
    """Convert json string into dict"""
    return json.JSONDecoder().decode(json_str)

class JSONMixin(object):
    """Simple, stateless json utilities mixin.

    Requires class to implement two methods:
      to_json(self): convert data to json-compatible datastructure (dict,
        list, strings, numbers)
      @classmethod from_json(cls, json): load data from json-compatible structure.
    """

    @classmethod
    def from_json_str(cls, json_str):
        """Convert json string representation into class instance.

        Args:
          json_str: json representation as string.

        Returns:
          New instance of the class with data loaded from json string.
        """
        dct = json_to_dict(json_str)
        return cls(**dct)

    def to_json_str(self):
        """Convert data to json string representation.

        Returns:
          json representation as string.
        """
        adict = dict(vars(self), sort_keys=True)
        adict['type'] = self.__class__.__name__
        return json.dumps(adict)

    def __repr__(self):
        return self.to_json_str()

    def __str__(self):
        return self.to_json_str()


# coding=utf-8
# -------------------------------------------------------------------------------
# Author: Alexandre Manhaes Savio <alexsavio@gmail.com>
# Grupo de Inteligencia Computational <www.ehu.es/ccwintco>
# Universidad del Pais Vasco UPV/EHU
#
# 2015, Alexandre Manhaes Savio
# Use this at your own risk!
# -------------------------------------------------------------------------------

import os
import re
import logging
from sys import platform as _platform

from docstamp.commands import which, is_exe

LOGGING_LVL = logging.INFO
logging.basicConfig(level=LOGGING_LVL)

def find_file_match(folder_path, regex=''):
    """
    Returns absolute paths of files that match the regex within folder_path and
    all its children folders.

    Note: The regex matching is done using the match function
    of the re module.

    Parameters
    ----------
    folder_path: string

    regex: string

    Returns
    -------
    A list of strings.

    """
    outlist = []
    for root, dirs, files in os.walk(folder_path):
        outlist.extend([os.path.join(root, f) for f in files
                        if re.match(regex, f)])

    return outlist

def get_system_path():
    if _platform == "linux" or _platform == "linux2":
        return os.environ['PATH']
    elif _platform == "darwin":
        return os.environ['PATH']
    elif _platform == "win32":
        # don't know if this works
        return os.environ['PATH']

def get_other_program_folders():
    if _platform == "linux" or _platform == "linux2":
        return ['/opt/bin']
    elif _platform == "darwin":
        return ['/Applications', os.path.join(os.environ['HOME'], 'Applications')]
    elif _platform == "win32":
        # don't know if this works
        return ['C:\Program Files']

def get_temp_dir():
    if _platform == "linux" or _platform == "linux2":
        return '/tmp'
    elif _platform == "darwin":
        return '.'
    elif _platform == "win32":
        # don't know if this works
        return None

def find_in_other_programs_folders(app_name):
    app_name_regex = '^' + app_name + '$'
    other_folders = get_other_program_folders()

    for folder in other_folders:
        abin_file = find_program(folder, app_name_regex)
        if abin_file is not None:
            return abin_file

    return None

def find_program(root_dir, exec_name):
    file_matches = find_file_match(root_dir, exec_name)
    for f in file_matches:
        if is_exe(f):
            return f
    return None

def ask_for_path_of(app_name):
    bin_path = None
    while bin_path is not None:
        bin_path = input('Insert path of {} executable file [Press Ctrl+C to exit]: '.format(app_name))

        if not os.path.exists(bin_path):
            print('Could not find file {}. Try it again.'.format(bin_path))
            bin_path = None
            continue

        if not is_exe(bin_path):
            print('No execution permissions on file {}. Try again.'.format(bin_path))
            bin_path = None
            continue

        return bin_path

def proactive_search_of(app_name):
    if _platform == 'win32':
        bin_name = app_name + '.exe'
    else:
        bin_name = app_name

    bin_path = which(app_name)
    if bin_path is not None and is_exe(bin_path):
        return bin_path

    bin_path = find_in_other_programs_folders(bin_name)
    if bin_path is not None:
        return bin_path

    return ask_for_path_of(bin_name)

def get_inkscape_binpath():
    bin_name = 'inkscape'
    if _platform == "darwin":
        bin_name = 'inkscape-bin'

    if 'INKSCAPE_BINPATH' not in globals():
        global INKSCAPE_BINPATH
        INKSCAPE_BINPATH = proactive_search_of(bin_name)

    return INKSCAPE_BINPATH

def get_lyx_binpath():
    if 'LYX_BINPATH' not in globals():
        global LYX_BINPATH
        LYX_BINPATH = proactive_search_of('lyx')
    return LYX_BINPATH

# TEMPLATES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')

# JINJA_ENV = Environment(loader=PackageLoader('docstamp', 'templates'))
# JINJA_ENV = Environment(loader=FileSystemLoader(TEMPLATES_DIR))

# FILE_EXPORTERS = {'.svg': Inkscape,}
#                   '.tex': PdfLatex,
#                   '.lyx': LyX}


# amara3.uxml.uxpath

'''
from amara3.uxml.uxpath import qquery
results = qquery(b'123', 'a/text()'))
next(results).xml_value
next(results).xml_value

from amara3.uxml import tree
DOC = '<x>1</x><c><x>2</x><d><x>3</x></d></c><x>4</x><y>5</y>'
tb = tree.treebuilder()
root = tb.parse(DOC)
from amara3.uxml.uxpath import context, parse as uxpathparse
ctx = context(root)
parsed_expr = uxpathparse('b/x')
result = parsed_expr.compute(ctx)
print(result)
print(list(result))
'''

import os
import re
import tempfile

from ply import lex, yacc

#from amara3 import iri
#from amara3.uxml import xml
from amara3.uxml import tree
from amara3.uxml.treeutil import *
from amara3.uxml.tree import node as nodetype
from amara3.util import coroutine
from . import lexrules, parserules, ast
from .functions import BUILTIN_FUNCTIONS

__all__ = ['lexer', 'parser', 'parse', 'context']#, 'serialize']

lexer = None
#BUILDuxpath.LEX=1; python -m amara3.uxml.uxpath
if 'BUILDuxpath.LEX' in os.environ:
    # Build with cached lex table. Meant for developers to do and commit the updated file. Will fail without write access on the source directory.
    lexdir = os.path.dirname(lexrules.__file__)
    try:
        lexer = lex.lex(module=lexrules, optimize=1, outputdir=lexdir,
            reflags=re.UNICODE)
    except IOError as e:
        import errno
        if e.errno != errno.EACCES:
            raise

if lexer is None:
    lexer = lex.lex(module=lexrules, reflags=re.UNICODE)

# Generate parsetab.py
parsedir = os.path.dirname(parserules.__file__)
if (not os.access(parsedir, os.W_OK)):
    parsedir = tempfile.gettempdir()

parser = yacc.yacc(module=parserules, outputdir=parsedir)#, debug=True)

def parse(xpath):
    '''
    Parse an xpath.

    >>> from amara3.uxml.uxpath import parse
    >>> xp = parse('a/text()')
    '''
    # Explicitly specify the lexer created above, otherwise parser.parse will use the most-recently created lexer. (Ewww! Wha?!)
    return parser.parse(xpath, lexer=lexer)#, debug=True)

class context(object):
    def __init__(self, item, pos=None, variables=None, functions=None, lookuptables=None, extras=None, parent=None, force_root=True):
        '''

        Note: No explicit context size. Will be dynamically computed if needed
        '''
        self.item = item
        if force_root and isinstance(item, nodetype):
            self.item = ast.root_node.get(item)
        self.pos = pos
        self.variables = variables or {}
        self.functions = functions or BUILTIN_FUNCTIONS
        self.extras = extras or {}
        #Needed for the case where the context node is a text node
        self.parent = parent or node.xml_parent
        self.lookuptables = lookuptables or {}

    def copy(self, item=None, pos=None, variables=None, functions=None, lookuptables=None, extras=None, parent=None):
        item = item if item else self.item
        pos = pos if pos else self.pos
        variables = variables if variables else self.variables
        functions = functions if functions else self.functions
        lookuptables = lookuptables if lookuptables else self.lookuptables
        extras = extras if extras else self.extras
        parent = parent if parent else self.parent
        return context(item, pos=pos, variables=variables, functions=functions, lookuptables=lookuptables, extras=extras, parent=parent, force_root=False)

def qquery(xml_thing, xpath_thing, vars=None, funcs=None):
    '''
    Quick query. Convenience for using the MicroXPath engine.
    Give it some XML and an expression and it will yield the results. No fuss.

    xml_thing - bytes or string, or amara3.xml.tree node
    xpath_thing - string or parsed XPath expression
    vars - optional mapping of variables, name to value
    funcs - optional mapping of functions, name to function object

    >>> from amara3.uxml.uxpath import qquery
    >>> results = qquery(b'123', 'a/text()'))
    >>> next(results).xml_value
    '1'
    >>> next(results).xml_value
    '3'
    '''
    root = None
    if isinstance(xml_thing, nodetype):
        root = xml_thing
    elif isinstance(xml_thing, str):
        tb = tree.treebuilder()
        root = tb.parse(xml_thing)
    elif isinstance(xml_thing, bytes):
        tb = tree.treebuilder()
        #Force UTF-8
        root = tb.parse(xml_thing.decode('utf-8'))
    if not root: return
    if isinstance(xpath_thing, str):
        parsed_expr = parse(xpath_thing)
    ctx = context(root, variables=vars, functions=funcs)
    result = parsed_expr.compute(ctx)
    yield from result


# amara3.uxml.uxpath.parserules
'''
XPath parsing rules.

Heavy debt to: https://github.com/emory-libraries/eulxml/blob/master/eulxml/xpath/parserules.py
'''

from . import ast
from .lexrules import tokens

precedence = (
    ('left', 'OR_OP'),
    ('left', 'AND_OP'),
    ('left', 'EQUAL_OP'),
    ('left', 'REL_OP'),
    ('left', 'PLUS_OP', 'MINUS_OP'),
    ('left', 'MULT_OP', 'DIV_OP', 'MOD_OP'),
    ('right', 'UMINUS_OP'),
    ('left', 'UNION_OP'),
)

#
# basic expressions
#

def p_expr_boolean(p):
    """
    Expr : Expr OR_OP Expr
         | Expr AND_OP Expr
         | Expr EQUAL_OP Expr
         | Expr REL_OP Expr
         | Expr PLUS_OP Expr
         | Expr MINUS_OP Expr
         | Expr MULT_OP Expr
         | Expr DIV_OP Expr
         | Expr MOD_OP Expr
    """
    p[0] = ast.BinaryExpression(p[1], p[2], p[3])

def p_expr_unary(p):
    """
    Expr : MINUS_OP Expr %prec UMINUS_OP
    """
    p[0] = ast.UnaryExpression(p[1], p[2])

#
# sequence expressions
#

def p_expr_sequence_empty(p):
    """
    Expr : OPEN_PAREN CLOSE_PAREN
    """
    p[0] = ast.Sequence([])

def p_expr_sequence(p):
    """
    Expr : OPEN_PAREN ExprList CLOSE_PAREN
    """
    p[0] = ast.Sequence(p[2])

def p_expr_list_single(p):
    """
    ExprList : Expr
    """
    p[0] = [p[1]]

def p_expr_list_recursive(p):
    """
    ExprList : ExprList COMMA Expr
    """
    p[0] = p[1]
    p[0].append(p[3])

#
# path expressions
#

def p_path_union_expr(p):
    """
    Expr : Expr UNION_OP Expr
    """
    p[0] = ast.BinaryExpression(p[1], p[2], p[3])

def p_path_expr_binary(p):
    """
    Expr : Expr PATH_SEP RelativeLocationPath
         | Expr ABBREV_PATH_SEP RelativeLocationPath
    """
    p[0] = ast.BinaryExpression(p[1], p[2], p[3])

def p_expr_paths_etc(p):
    """
    Expr : RelativeLocationPath
         | AbsoluteLocationPath
         | AbbreviatedAbsoluteLocationPath
         | PredicatedExpression
         | FunctionCall
         | VariableReference
    """
    p[0] = p[1]

def p_expr_literal(p):
    """
    Expr : LITERAL
         | Number
    """
    p[0] = ast.LiteralWrapper(p[1])

#
# paths
#

def p_absolute_location_path_rootonly(p):
    """
    AbsoluteLocationPath : PATH_SEP
    """
    p[0] = ast.AbsolutePath(p[1])

def p_absolute_location_path_subpath(p):
    """
    AbsoluteLocationPath : PATH_SEP RelativeLocationPath
    """
    p[0] = ast.AbsolutePath(p[1], p[2])

def p_abbreviated_absolute_location_path(p):
    """
    AbbreviatedAbsoluteLocationPath : ABBREV_PATH_SEP RelativeLocationPath
    """
    p[0] = ast.AbsolutePath(p[1], p[2])

def p_relative_location_path_simple(p):
    """
    RelativeLocationPath : Step
    """
    p[0] = p[1]

def p_relative_location_path_binary(p):
    """
    RelativeLocationPath : RelativeLocationPath PATH_SEP Step
                         | RelativeLocationPath ABBREV_PATH_SEP Step
    """
    p[0] = ast.BinaryExpression(p[1], p[2], p[3])

#
# path steps
#

def p_step_axis_nodetest(p):
    """
    Step : AxisSpecifier NodeTest
    """
    p[0] = ast.Step(p[1], p[2])

def p_step_nodetest(p):
    """
    Step : NodeTest
    """
    #For MicroXML we can assume child axis
    p[0] = ast.Step('child', p[1])

def p_step_abbrev(p):
    """
    Step : ABBREV_STEP_SELF
         | ABBREV_STEP_PARENT
    """
    p[0] = ast.AbbreviatedStep(p[1])

#
# axis specifier
#

def p_axis_specifier_full(p):
    """
    AxisSpecifier : NAME AXIS_SEP
    """
    #XPath 1.0 axes - namespace
    if p[1] not in ('self', 'attribute', 'ancestor', 'ancestor-or-self', 'attribute', 'child', 'descendant', 'descendant-or-self', 'following', 'following-sibling', 'parent', 'preceding', 'preceding-sibling'):
        raise RuntimeError("Invalid axis name '{0}'".format(p[1]))
    p[0] = p[1]

def p_axis_specifier_abbrev(p):
    """
    AxisSpecifier : ABBREV_AXIS_AT
    """
    p[0] = 'attribute'

#
# node test
#

def p_node_test_name_test(p):
    """
    NodeTest : NameTest
    """
    p[0] = p[1]

def p_node_test_type(p):
    """
    NodeTest : NODETEXTTEST
    """
    #assert p[1] in ('node', 'text')
    #raise RuntimeError("Invalid node type '{0}'".format(p[1]))
    p[0] = ast.NodeTypeTest(p[1])

#
# name test
#

def p_name_test_star(p):
    """
    NameTest : STAR_OP
    """
    p[0] = ast.NameTest('*')

def p_name_test_name(p):
    """
    NameTest : NAME
    """
    p[0] = ast.NameTest(p[1])

#
# predicated expressions
#

def p_expr_predicates(p):
    """
    PredicatedExpression : Expr PredicateList
    """
    p[0] = ast.PredicatedExpression(p[1], p[2])

#
# predicates
#

def p_predicate_list_single(p):
    """
    PredicateList : Predicate
    """
    p[0] = [p[1]]

def p_predicate_list_recursive(p):
    """
    PredicateList : PredicateList Predicate
    """
    p[0] = p[1]
    p[0].append(p[2])

def p_predicate(p):
    """
    Predicate : OPEN_BRACKET Expr CLOSE_BRACKET
    """
    p[0] = p[2]

#
# variable
#

def p_variable_reference(p):
    """
    VariableReference : DOLLAR NAME
    """
    p[0] = ast.VariableReference(p[2])

#
# number
#

def p_number(p):
    """
    Number : FLOAT
           | INTEGER
    """
    p[0] = p[1]

#
# funcall
#

def p_function_call(p):
    """
    FunctionCall : NAME FormalArguments
    """
    #Hacking around the ambiguity between node type test & function call
    if p[1] in ('node', 'text'):
        p[0] = ast.NodeType(p[1])
    else:
        p[0] = ast.FunctionCall(p[1], p[2])

def p_formal_arguments_empty(p):
    """
    FormalArguments : OPEN_PAREN CLOSE_PAREN
    """
    p[0] = []

def p_formal_arguments_list(p):
    """
    FormalArguments : OPEN_PAREN ArgumentList CLOSE_PAREN
    """
    p[0] = p[2]

def p_argument_list_single(p):
    """
    ArgumentList : Expr
    """
    p[0] = [p[1]]

def p_argument_list_recursive(p):
    """
    ArgumentList : ArgumentList COMMA Expr
    """
    p[0] = p[1]
    p[0].append(p[3])

#
# error handling
#

def p_error(p):
    #FIXME: Other cases where p is None?
    if p is None:
        p = '[END-OF-INPUT]'
    raise RuntimeError("Syntax error at '{0}'".format(p))

#import ply.yacc as yacc
# Build the parser
#parser = yacc.yacc()


# amara3.uxml.uxpath.functions

'''Built-in functions for MicroXPath
'''

__all__ = [
    'BUILTIN_FUNCTIONS',
    ]

#import operator
from functools import wraps
from amara3.uxml.tree import node, element, strval
from .ast import root_node, attribute_node, index_docorder, to_string, to_number, to_boolean

def boolean_arg(ctx, obj):
    '''
    Handles LiteralObjects as well as computable arguments
    '''
    if hasattr(obj, 'compute'):
        obj = next(obj.compute(ctx), False)
    return to_boolean(obj)

def number_arg(ctx, obj):
    '''
    Handles LiteralObjects as well as computable arguments
    '''
    if hasattr(obj, 'compute'):
        obj = next(obj.compute(ctx), False)
    return to_number(obj)

def string_arg(ctx, obj):
    '''
    Handles LiteralObjects as well as computable arguments
    '''
    if hasattr(obj, 'compute'):
        obj = next(obj.compute(ctx), False)
    return to_string(obj)

BUILTIN_FUNCTIONS = {}

def microxpath_function(name):
    def _microxpath_function(f):
        BUILTIN_FUNCTIONS[name] = f
        #global BUILTIN_FUNCTIONS
        @wraps(f)
        def wrapper(*args, **kwargs):
            return f(*args, **kwargs)
        return wrapper
    return _microxpath_function

@microxpath_function('name')
def name(ctx, obj=None):
    '''
    Yields one string a node name or the empty string, operating on the first item in the provided obj, or the current item if obj is omitted
    If this item is a node, yield its node name (generic identifier), otherwise yield ''
    If obj is provided, but empty, yield ''
    '''
    if obj is None:
        item = ctx.item
    elif hasattr(obj, 'compute'):
        item = next(obj.compute(ctx), None)
    else:
        item = obj
    if isinstance(item, node):
        yield item.xml_name
    else:
        yield ''

@microxpath_function('last')
def last(ctx):
    '''
    '''
    #FIXME: Implement
    yield -1

@microxpath_function('count')
def count(ctx, obj):
    '''
    Yields one number, count of items in the argument sequence
    '''
    yield len(list(obj.compute(ctx)))

@microxpath_function('string')
def string_(ctx, seq=None):
    '''
    Yields one string, derived from the argument literal (or the first item in the argument sequence, unless empty in which case yield '') as follows:

    * If a node, yield its string-value
    * If NaN, yield 'NaN'
    * If +0 or -0, yield '0'
    * If positive infinity, yield 'Infinity'
    * If negative infinity, yield '-Infinity'
    * If an integer, no decimal point and no leading zeros
    * If a non-integer number, at least one digit before the decimal point and at least one digit after
    * If boolean, either 'true' or 'false'
    '''
    if seq is None:
        item = ctx.item
    elif hasattr(seq, 'compute'):
        item = next(seq.compute(ctx), '')
    else:
        item = seq
    yield next(to_string(item), '')

def flatten(iterables):
    return (elem for it in iterables for elem in ([it] if isinstance(it, str) else it))

@microxpath_function('concat')
def concat(ctx, *strings):
    '''
    Yields one string, concatenation of argument strings
    '''
    strings = flatten([ (s.compute(ctx) if callable(s) else s) for s in strings ])
    strings = (next(string_arg(ctx, s), '') for s in strings)
    #assert(all(map(lambda x: isinstance(x, str), strings)))
    #FIXME: Check arg types
    yield ''.join(strings)

@microxpath_function('starts-with')
def starts_with(ctx, full, part):
    '''
    Yields one boolean, whether the first string starts with the second
    '''
    full = next(string_arg(ctx, full), '')
    part = next(string_arg(ctx, part), '')
    yield full.startswith(part)

@microxpath_function('contains')
def contains(ctx, full, part):
    '''
    Yields one boolean, whether the first string contains the second
    '''
    full = next(string_arg(ctx, full), '')
    part = next(string_arg(ctx, part), '')
    yield part in full

@microxpath_function('substring-before')
def substring_before(ctx, full, part):
    '''
    Yields one string
    '''
    full = next(string_arg(ctx, full), '')
    part = next(string_arg(ctx, part), '')
    yield full.partition(part)[0]

@microxpath_function('substring-after')
def substring_after(ctx, full, part):
    '''
    Yields one string
    '''
    full = next(string_arg(ctx, full), '')
    part = next(string_arg(ctx, part), '')
    yield full.partition(part)[-1]

@microxpath_function('substring')
def substring(ctx, full, start, length):
    '''
    Yields one string
    '''
    full = next(string_arg(ctx, full), '')
    start = int(next(to_number(start)))
    length = int(next(to_number(length)))
    yield full[start-1:start-1+length]

@microxpath_function('string-length')
def string_length(ctx, s=None):
    '''
    Yields one number
    '''
    if s is None:
        s = ctx.node
    elif callable(s):
        s = next(s.compute(ctx), '')
    yield len(s)

@microxpath_function('normalize-space')
def normalize_space(ctx, s):
    '''
    Yields one string
    '''
    #FIXME: Implement
    raise NotImplementedError
    yield s

@microxpath_function('translate')
def translate(ctx, s, subst):
    '''
    Yields one string
    '''
    #FIXME: Implement
    raise NotImplementedError
    yield s

@microxpath_function('same-lang')
def same_lang(ctx, seq, lang):
    '''
    Yields one boolean
    '''
    #FIXME: Implement
    raise NotImplementedError
    yield s

@microxpath_function('boolean')
def boolean(ctx, obj):
    '''
    Yields one boolean, false if the argument sequence is empty, otherwise

    * false if the first item is a boolean and false
    * false if the first item is a number and positive or negative zero or NaN
    * false if the first item is a string and ''
    * true in all other cases
    '''
    if hasattr(obj, 'compute'):
        obj = next(seq.compute(ctx), '')
    else:
        obj = seq
    yield next(to_boolean(obj), '')

@microxpath_function('not')
def _not(ctx, obj):
    '''
    Yields one boolean
    '''
    yield not next(boolean_arg(ctx, obj), False)

@microxpath_function('true')
def _true(ctx):
    '''
    Yields one boolean, true
    '''
    yield True

@microxpath_function('false')
def _false(ctx):
    '''
    Yields one boolean, false
    '''
    yield False

@microxpath_function('number')
def number(ctx, seq=None):
    '''
    Yields one float, derived from the first item in the argument sequence (unless empty in which case yield NaN) as follows:

    * If string with optional whitespace followed by an optional minus sign followed by a Number followed by whitespace, converte to the IEEE 754 number that is nearest (according to the IEEE 754 round-to-nearest rule) to the mathematical value represented by the string; in case of any other string yield NaN
    * If boolean true yield 1; if boolean false yield 0
    * If a node convert to string as if by a call to string(); yield the same value as if passed that string argument to number()
    '''
    if hasattr(obj, 'compute'):
        obj = next(seq.compute(ctx), '')
    else:
        obj = seq
    yield next(to_number(obj), '')

@microxpath_function('for-each')
def foreach_(ctx, seq, expr):
    '''
    Yields the result of applying an expression to each item in the input sequence.

    * seq: input sequence
    * expr: expression to be converted to string, then dynamically evaluated for each item on the sequence to produce the result
    '''
    from . import context, parse as uxpathparse

    if hasattr(seq, 'compute'):
        seq = seq.compute(ctx)

    expr = next(string_arg(ctx, expr), '')

    pexpr = uxpathparse(expr)
    for item in seq:
        innerctx = ctx.copy(item=item)
        yield from pexpr.compute(innerctx)

@microxpath_function('lookup')
def lookup_(ctx, tableid, key):
    '''
    Yields a sequence of a single value, the result of looking up a value from the tables provided in the context, or an empty sequence if lookup is unsuccessful

    * tableid: id of the lookup table to use
    * expr: expression to be converted to string, then dynamically evaluated for each item on the sequence to produce the result
    '''
    tableid = next(string_arg(ctx, tableid), '')
    key = next(string_arg(ctx, key), '')
    #value = ctx.
    for item in seq:
        innerctx = ctx.copy(item=item)
        yield from pexpr.compute(innerctx)

@microxpath_function('sum')
def _sum(ctx, seq):
    '''
    Yields one number, the sum, for each item in the argument sequence, of the result of converting as if by a call to number()
    '''
    #FIXME: Implement
    raise NotImplementedError
    yield seq

@microxpath_function('floor')
def _floor(ctx, num):
    '''
    Yields one number, the largest (closest to positive infinity) number that is not greater than the argument and that is an integer.
    '''
    #FIXME: Implement
    raise NotImplementedError
    yield num

@microxpath_function('ceiling')
def _ceiling(ctx, num):
    '''
    Yields one number, the smallest (closest to negative infinity) number that is not less than the argument and that is an integer.
    '''
    #FIXME: Implement
    raise NotImplementedError
    yield num

@microxpath_function('round')
def _round(ctx, num):
    '''
    Yields one number, that which is closest to the argument and that is an integer. If there are two such numbers, then the one that is closest to positive infinity is returned. If the argument is NaN, then NaN is returned. If the argument is positive infinity, then positive infinity is returned. If the argument is negative infinity, then negative infinity is returned. If the argument is positive zero, then positive zero is returned. If the argument is negative zero, then negative zero is returned. If the argument is less than zero, but greater than or equal to -0.5, then negative zero is returned.
    '''
    #FIXME: Implement
    raise NotImplementedError
    yield num


"""
Function helpers to do stuff on svg files.
"""
import os
import logging

from docstamp.commands import call_command, which, check_command
import svgutils.transform as sg

log = logging.getLogger(__name__)

def replace_chars_for_svg_code(svg_content):
    """ Replace known special characters to SVG code.

    Parameters
    ----------
    svg_content: str

    Returns
    -------
    corrected_svg: str
        Corrected SVG content
    """
    result = svg_content
    svg_char = [
        ('&', '&'),
        ('>', '>'),
        ('<', '<'),
        ('"', '"'),
    ]

    for c, entity in svg_char:
        result = result.replace(c, entity)

    return result

def _check_svg_file(svg_file):
    """ Try to read a SVG file if `svg_file` is a string.
    Raise an exception in case of error or return the svg object.

    If `svg_file` is a svgutils svg object, will just return it.

    Parameters
    ----------
    svg_file: str or svgutils.transform.SVGFigure object
        If a `str`: path to a '.svg' file,
        otherwise a svgutils svg object is expected.

    Returns
    -------
    svgutils svg object

    Raises
    ------
    Exception if any error happens.
    """
    if isinstance(svg_file, str):
        try:
            svg = sg.fromfile(svg_file)
        except Exception as exc:
            raise Exception('Error reading svg file {}.'.format(svg_file)) from exc
        else:
            return svg

    if isinstance(svg_file, sg.SVGFigure):
        return svg_file

    raise ValueError('Expected `svg_file` to be `str` or `svgutils.SVG`, got {}.'.format(type(svg_file)))

def merge_svg_files(svg_file1, svg_file2, x_coord, y_coord, scale=1):
    """ Merge `svg_file2` in `svg_file1` in the given positions `x_coord`, `y_coord` and `scale`.

    Parameters
    ----------
    svg_file1: str or svgutils svg document object
        Path to a '.svg' file.

    svg_file2: str or svgutils svg document object
        Path to a '.svg' file.

    x_coord: float
        Horizontal axis position of the `svg_file2` content.

    y_coord: float
        Vertical axis position of the `svg_file2` content.

    scale: float
        Scale to apply to `svg_file2` content.

    Returns
    -------
    `svg1` svgutils object with the content of 'svg_file2'
    """
    svg1 = _check_svg_file(svg_file1)
    svg2 = _check_svg_file(svg_file2)

    svg2_root = svg2.getroot()
    svg1.append([svg2_root])

    svg2_root.moveto(x_coord, y_coord, scale=scale)

    return svg1

def rsvg_export(input_file, output_file, dpi=90, rsvg_binpath=None):
    """ Calls the `rsvg-convert` command, to convert a svg to a PDF (with unicode).

    Parameters
    ----------

    rsvg_binpath: str
        Path to `rsvg-convert` command

    input_file: str
        Path to the input file

    output_file: str
        Path to the output file

    Returns
    -------
    return_value
        Command call return value

    """
    if not os.path.exists(input_file):
        log.error('File {} not found.'.format(input_file))
        raise IOError((0, 'File not found.', input_file))

    if rsvg_binpath is None:
        rsvg_binpath = which('rsvg-convert')
        check_command(rsvg_binpath)

    args_strings = []
    args_strings += ["-f pdf"]
    args_strings += ["-o {}".format(output_file)]
    args_strings += ["--dpi-x {}".format(dpi)]
    args_strings += ["--dpi-y {}".format(dpi)]
    args_strings += [input_file]

    return call_command(rsvg_binpath, args_strings)


"""
Function helpers to manage PDF files.
"""
from PyPDF2 import PdfFileMerger, PdfFileReader

from docstamp.commands import call_command

def merge_pdfs(pdf_filepaths, out_filepath):
    """ Merge all the PDF files in `pdf_filepaths` in a new PDF file `out_filepath`.

    Parameters
    ----------
    pdf_filepaths: list of str
        Paths to PDF files.

    out_filepath: str
        Path to the result PDF file.

    Returns
    -------
    path: str
        The output file path.
    """
    merger = PdfFileMerger()
    for pdf in pdf_filepaths:
        merger.append(PdfFileReader(open(pdf, 'rb')))

    merger.write(out_filepath)

    return out_filepath

def pdf_to_cmyk(input_file: str, output_file: str):
    cmd_args = '-dSAFER -dBATCH -dNOPAUSE -dNOCACHE -sDEVICE=pdfwrite ' \
        '-sColorConversionStrategy=CMYK ' \
        '-dProcessColorModel=/DeviceCMYK ' \
        '-sOutputFile="{}" "{}"'.format(output_file, input_file)
    call_command('gs', cmd_args)


# coding=utf-8
# -------------------------------------------------------------------------------
# Author: Alexandre Manhaes Savio <alexsavio@gmail.com>
# Asociación Python San Sebastián (ACPySS)
#
# 2017, Alexandre Manhaes Savio
# Use this at your own risk!
# -------------------------------------------------------------------------------

import os
import base64
from lxml import etree

from .file_utils import get_extension

FONT_TYPES = {'ttf': 'truetype',
              'otf': 'opentype'}

def get_base64_encoding(bin_filepath):
    """Return the base64 encoding of the given binary file"""
    return base64.b64encode(open(bin_filepath, 'r').read())

def remove_ext(filepath):
    """Return the basename of filepath without extension."""
    return os.path.basename(filepath).split('.')[0]

class FontFace(object):
    """CSS font-face object"""

    def __init__(self, filepath, fonttype=None, name=None):
        self.filepath = filepath
        self.ftype = fonttype
        self.given_name = name

    @classmethod
    def from_file(cls, filepath):
        return cls(filepath)

    @property
    def name(self):
        if self.given_name is None:
            return remove_ext(self.filepath)
        else:
            return self.given_name

    @property
    def base64(self):
        return get_base64_encoding(self.filepath)

    @property
    def fonttype(self):
        if self.ftype is None:
            return FONT_TYPES[get_extension(self.filepath)]
        else:
            return self.ftype

    @property
    def ext(self):
        return get_extension(self.filepath)

    @property
    def css_text(self):
        css_text = u"@font-face{\n"
        css_text += u"font-family: " + self.name + ";\n"
        css_text += u"src: url(data:font/" + self.ext + ";"
        css_text += u"base64," + self.base64 + ") "
        css_text += u"format('" + self.fonttype + "');\n}\n"
        return css_text

class FontFaceGroup(object):
    """Group of FontFaces"""

    def __init__(self):
        self.fontfaces = []

    @property
    def css_text(self):
        css_text = u'<style type="text/css">'
        for ff in self.fontfaces:
            css_text += ff.css_text
        css_text += u'</style>'
        return css_text

    @property
    def xml_elem(self):
        return etree.fromstring(self.css_text)

    def append(self, font_face):
        self.fontfaces.append(font_face)

def _embed_font_to_svg(filepath, font_files):
    """ Return the ElementTree of the SVG content in `filepath`
    with the font content embedded.
    """
    with open(filepath, 'r') as svgf:
        tree = etree.parse(svgf)

    if not font_files:
        return tree

    fontfaces = FontFaceGroup()
    for font_file in font_files:
        fontfaces.append(FontFace(font_file))

    for element in tree.iter():
        if element.tag.split("}")[1] == 'svg':
            break

    element.insert(0, fontfaces.xml_elem)

    return tree

def embed_font_to_svg(filepath, outfile, font_files):
    """ Write ttf and otf font content from `font_files`
    in the svg file in `filepath` and write the result in
    `outfile`.

    Parameters
    ----------
    filepath: str
        The SVG file whose content must be modified.

    outfile: str
        The file path where the result will be written.

    font_files: iterable of str
        List of paths to .ttf or .otf files.
    """
    tree = _embed_font_to_svg(filepath, font_files)
    tree.write(outfile, encoding='utf-8', pretty_print=True)


# BSD 3-Clause License
#
# Copyright (c) 2017, Science and Technology Facilities Council
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# Author R. Ford STFC Daresbury Lab.
#
'''This module provides the internally supported Melody search
methods. A search method is expected to conform to the SearchMethod
base class'''

class SearchMethod(object):
    '''A utility baseclass for different search/optimisation methods'''

    def __init__(self, inputs, function, state=None):
        self._inputs = inputs
        self._check_inputs()
        self._function = function
        self._check_function()
        self._state = state

    def _check_inputs(self):
        ''' make some basic checks on the inputs to make sure they are valid'''
        try:
            _ = self._inputs[0]
        except TypeError:
            raise RuntimeError(
                "inputs should be iterable but found type='{0}', value="
                "'{1}'".format(type(self._inputs), str(self._inputs)))
        from melody.inputs import Input
        for check_input in self._inputs:
            if not isinstance(check_input, Input):
                raise RuntimeError(
                    "input should be a subclass of the Input class but "
                    "found type='{0}', value='{1}'".format(type(check_input),
                                                           str(check_input)))

    def _check_function(self):
        ''' make some basic checks on the function to make sure it is valid'''
        # note, callable is valid for Python 2 and Python 3.2 onwards but
        # not inbetween
        if not callable(self._function):
            raise RuntimeError(
                "provided function '{0}' is not callable".
                format(str(self._function)))
        from inspect import getargspec
        arg_info = getargspec(self._function)
        if len(arg_info.args) != 1:
            print str(arg_info)
            raise RuntimeError(
                "provided function should have one argument but found "
                "{0}".format(len(arg_info.args)))
        # I don't think I can statically determine how many arguments
        # a function will return so will have to check after calling
        # the function

    def run(self):
        '''Ensure that any subclass implements this method'''
        raise NotImplementedError("Run method should be implemented")

    @property
    def function(self):
        '''Return the function associated with this instance of search
        method'''
        return self._function

    @property
    def inputs(self):
        '''Return the input search parameters for this instance of search
        method'''
        return self._inputs

    @property
    def state(self):
        '''Return the state for this instance of search method'''
        return self._state

    @state.setter
    def state(self, state):
        '''Set the state for this instance of search method'''
        self._state = state

class BruteForce(SearchMethod):
    '''A search method that tests all input options'''

    def __init__(self, inputs, function, state=None):
        SearchMethod.__init__(self, inputs, function, state=state)

    def run(self):
        ''' perform the search over inputs'''
        self._recurse(self.inputs, [])

    def _recurse(self, inputs, output):
        '''internal recursion routine called by the run method that generates
        all input combinations'''
        if inputs:
            my_input = inputs[0]
            name = my_input.name
            if my_input.state:
                my_options = my_input.options(self.state)
            else:
                my_options = my_input.options
            for option in my_options:
                my_output = list(output)
                my_output.append({name: option})
                self._recurse(inputs[1:], my_output)
        else:
            try:
                valid, result = self._function(output)
            except ValueError:
                raise RuntimeError("function must return 2 values")
            print output, valid, result


# BSD 3-Clause License
#
# Copyright (c) 2017, Science and Technology Facilities Council
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# Author R. Ford STFC Daresbury Lab.
#
'''This file contains a set of input classes that users can use to
define the space they want to search. These classes can also be
specialised for particular scenarios.'''

class Input(object):
    '''This is the base class for all types of input class.'''

    def __init__(self, name, options, state=None):
        self._state = state
        self._name = name
        self._options = options

    @property
    def state(self):
        '''Returns the state variable. This is 'None' by default.'''
        return self._state

    @property
    def name(self):
        '''Returns the name of the input class. All classes are expected to
        define a name'''
        return self._name

    @property
    def options(self):
        '''Returns the particular set of options that are valid for the
        instance of the input'''
        return self._options

class Fixed(Input):
    '''This class simply returns a single fixed value. It therefore does
    not add to the search space. Rather it is a convenience class.'''

    def __init__(self, name, value):
        options = [value]
        Input.__init__(self, name, options)

class Switch(Input):
    '''This class allows the user to specify two options where only one is
    valid at a time. It could be considered to be a special case of
    Choice, but it is so common that a separate class makes sense.'''

    def __init__(self, name, off=None, on=None):
        if not off and not on:
            raise RuntimeError(
                "A value must be provided for at least one of 'off' or 'on'")
        options = []
        if off:
            options.append(off)
        else:
            options.append("")
        if on:
            options.append(on)
        else:
            options.append("")
        Input.__init__(self, name, options)

class Choice(Input):
    '''This class allows the user to specify an unlimited number of
    options, where only one option is valid at a time.

    '''

    def __init__(self, name, inputs, pre=None):
        options = []
        for value in inputs:
            if pre:
                options.append(pre + value)
            else:
                options.append(value)
        Input.__init__(self, name, options)

class IntRange(Input):
    '''This class implements the integer version of the Range class,
    allowing a set of integer inputs to be defined by a low, high and
    step.'''

    def __init__(self, name, low, high, step):
        options = [i for i in range(low, high, step)]
        Input.__init__(self, name, options)

class FloatRange(Input):
    '''This class implements a range of floating point values,
    which are set by low, high and step values.'''

    def __init__(self, name, low, high, step):
        from numpy import arange
        options = [f for f in arange(low, high, step)]
        Input.__init__(self, name, options)

class Subsets(Input):
    '''This class allows all combinations of a particular set of values to
    be chosen, including, no values, individual values, pairs of values,
    triplets of values etc.'''

    def __init__(self, name, inputs):
        self._inputs = inputs
        self._options = []
        for k in range(len(inputs) + 1):
            self._recurse(inputs, [], depth=0, max_depth=k)
        Input.__init__(self, name, self._options)

    def _recurse(self, inputs, output, depth, max_depth):
        '''We work out all combinations using this internal recursion method'''
        if depth < max_depth:
            for index, option in enumerate(inputs):
                my_output = list(output)
                my_output.append(option)
                self._recurse(inputs[index + 1:], my_output, depth + 1,
                              max_depth)
        else:
            self._options.append(output)

def create_input(option, template_name, template_location="template"):

    '''create an input file using jinja2 by filling a template
    with the values from the option variable passed in.'''

    # restructure option list into jinja2 input format
    jinja2_input = {}
    for item in option:
        try:
            jinja2_input.update(item)
        except ValueError:
            raise RuntimeError(
                ("inputs.py, create_input : format of item '{0}' is not "
                 "supported. Expecting a dictionary.".format(str(item))))

    # load the template and fill it with the option variable contents
    import jinja2
    try:
        template_loader = jinja2.FileSystemLoader(searchpath=template_location)
        template_env = jinja2.Environment(loader=template_loader)
        template = template_env.get_template(template_name)
        output_text = template.render(jinja2_input)
    except jinja2.TemplateNotFound:
        raise RuntimeError("template '{0}' not found".format(template_name))
    # return the particular input file as a string
    return output_text


# amara3.uxml.uxpath.ast

'''Abstract Syntax Tree for parsed MicroXPath.

Heavy debt to: https://github.com/emory-libraries/eulxml/blob/master/eulxml/xpath/ast.py
'''

#Q=a; python -c "from amara3.uxml import tree; tb = tree.treebuilder(); root = tb.parse('<x>1</x><c i=\"1.2\"><x>2</x><d><x>3</x></d></c><x>4</x><y>5</y>'); from amara3.uxml.uxpath import context, parse as uxpathparse; ctx = context(root); parsed_expr = uxpathparse('$Q'); result = parsed_expr.compute(ctx); print(list(result))"

__all__ = [
    'serialize',
    'UnaryExpression',
    'BinaryExpression',
    'PredicatedExpression',
    'AbsolutePath',
    'Step',
    'NameTest',
    'NodeType',
    'AbbreviatedStep',
    'VariableReference',
    'FunctionCall',
    ]

import operator
import functools
from collections import Iterable
from amara3.uxml.tree import node, element, strval
from amara3.uxml.treeutil import descendants

class root_node(node):
    _cache = {}

    def __init__(self, docelem):
        self.xml_name = ''
        self.xml_value = ''
        self.xml_children = [docelem]
        node.__init__(self)

    def __repr__(self):
        return u'{uxpath.rootnode}'

    @staticmethod
    #@functools.lru_cache()
    def get(elem):
        if elem in root_node._cache: return root_node._cache[elem]
        if isinstance(elem, root_node): return elem
        assert isinstance(elem, element), 'Cannot get root node from object {} of type {}'.format(elem, type(elem))
        curr_elem = elem
        parent = curr_elem.xml_parent
        while parent:
            curr_elem = parent
            parent = curr_elem.xml_parent
            if parent in root_node._cache: return root_node._cache[parent]
        return root_node._cache.setdefault(elem, root_node(curr_elem))

class attribute_node(node):
    def __init__(self, name, value, parent):
        self.xml_name = name
        self.xml_value = value
        node.__init__(self, parent)

    def __repr__(self):
        return '{{uxpath.attribute {0}="{1}"}}'.format(self.xml_name, self.xml_value)

    def xml_encode(self):
        return '{{{0}="{1}"}}'.format(self.xml_name, self.xml_value)

    def xml_write(self):
        raise NotImplementedError

def index_docorder(node):
    #Always start at the root
    while node.xml_parent:
        node = node.xml_parent
    index = 0
    node._docorder = index
    for node in descendants(node):
        index += 1
        node._docorder = index

#Casts
def to_string(obj):
    '''
    Cast an arbitrary object or sequence to a string type
    '''
    if isinstance(obj, LiteralWrapper):
        val = obj.obj
    elif isinstance(obj, Iterable) and not isinstance(obj, str):
        val = next(obj, None)
    else:
        val = obj
    if val is None:
        yield ''
    elif isinstance(val, str):
        yield val
    elif isinstance(val, node):
        yield strval(val)
    elif isinstance(val, int) or isinstance(val, float):
        yield str(val)
    elif isinstance(item, bool):
        yield 'true' if item else 'false'
    else:
        raise RuntimeError('Unknown type for string conversion: {}'.format(val))

def to_number(obj):
    '''
    Cast an arbitrary object or sequence to a number type
    '''
    if isinstance(obj, LiteralWrapper):
        val = obj.obj
    elif isinstance(obj, Iterable) and not isinstance(obj, str):
        val = next(obj, None)
    else:
        val = obj
    if val is None:
        #FIXME: Should be NaN, not 0
        yield 0
    elif isinstance(val, str):
        yield float(val)
    elif isinstance(val, node):
        yield float(strval(val))
    elif isinstance(val, int) or isinstance(val, float):
        yield val
    else:
        raise RuntimeError('Unknown type for number conversion: {}'.format(val))

def to_boolean(obj):
    '''
    Cast an arbitrary sequence to a boolean type
    '''
    #if hasattr(obj, '__iter__'):
    if isinstance(obj, LiteralWrapper):
        val = obj.obj
    elif isinstance(obj, Iterable) and not isinstance(obj, str):
        val = next(obj, None)
    else:
        val = obj
    if val is None:
        yield False
    elif isinstance(val, bool):
        yield val
    elif isinstance(val, str):
        yield bool(str)
    elif isinstance(val, node):
        yield True
    elif isinstance(val, float) or isinstance(val, int):
        yield bool(val)
    else:
        raise RuntimeError('Unknown type for boolean conversion: {}'.format(val))

class LiteralWrapper(object):
    '''
    Literal string or number
    '''
    def __init__(self, obj):
        self.obj = obj

    def __repr__(self):
        return '{{{}}}'.format(self.__class__.__name__, self.obj)

    def _serialize(self):
        yield(str(self.obj))

    def __call__(self, ctx):
        '''
        Alias for user convenience
        '''
        yield from self.compute(ctx)

    def compute(self, ctx):
        #self.op is always '-'
        yield self.obj

class UnaryExpression(object):
    '''A unary XPath expression. Really only used with unary minus (self.op == '-')'''

    def __init__(self, op, right):
        assert op == '-'
        self.op = op
        '''the operator used in the expression'''
        self.right = right
        '''the expression the operator is applied to'''

    def __repr__(self):
        return '{{{} {} {}}}'.format(self.__class__.__name__, self.op, serialize(self))

    def _serialize(self):
        yield(self.op)
        for tok in _serialize(self.right):
            yield(tok)

    def __call__(self, ctx):
        '''
        Alias for user convenience
        '''
        yield from self.compute(ctx)

    def compute(self, ctx):
        #self.op is always '-'
        yield -(to_number(next(self.right.compute(ctx), None)))

BE_KEYWORDS = set(['or', 'and', 'div', 'mod'])
class BinaryExpression(object):
    '''Binary XPath expression, e.g. a/b; a and b; a | b.'''

    def __init__(self, left, op, right):
        self.left = left
        '''the left side of the binary expression'''
        self.op = op
        '''the operator of the binary expression'''
        self.right = right
        '''the right side of the binary expression'''

    def __repr__(self):
        return '{{{} {} {} {}}}'.format(self.__class__.__name__, serialize(self.left), self.op, serialize(self.right))

    def _serialize(self):
        for tok in _serialize(self.left):
            yield(tok)

        if self.op in BE_KEYWORDS:
            yield(' ')
            yield(self.op)
            yield(' ')
        else:
            yield(self.op)

        for tok in _serialize(self.right):
            yield(tok)

    def __call__(self, ctx):
        '''
        Alias for user convenience
        '''
        yield from self.compute(ctx)

    def compute(self, ctx):
        #print('BINARYEXPRESSION', (self.left, self.op, self.right))
        if self.op == '/':
            #left & right are steps
            selected = self.left.compute(ctx)
            for item in selected:
                new_ctx = ctx.copy(item=item)
                yield from self.right.compute(new_ctx)
        elif self.op == '//':
            #left & right are steps
            #Rewrite the axis to expand the abbreviation
            #Really only needed the first time.
            self.right.axis = 'descendant-or-self'
            selected = self.left.compute(ctx)
            for item in selected:
                new_ctx = ctx.copy(item=item)
                yield from self.right.compute(new_ctx)
        elif self.op == '|':
            #Union expressions require an indexing by doc order
            if not hasattr(ctx.item, '_docorder'):
                index_docorder(ctx.item)
            #XXX Might be more efficient to maintain a list in doc order as left & right are added
            selected = list(self.left.compute(ctx))
            selected.extend(list(self.right.compute(ctx)))
            selected.sort(key=operator.attrgetter('_docorder'))
            yield from selected

        #FIXME: A lot of work to do on comparisons
        elif self.op == '=':
            lhs = self.left.compute(ctx)
            rhs = self.right.compute(ctx)
            #print(ctx.item, list(self.left.compute(ctx)), list(self.right.compute(ctx)))
            for i in lhs:
                for j in rhs:
                    i = i.xml_value if isinstance(i, node) else i
                    j = i.xml_value if isinstance(j, node) else j
                    if i == j: yield True
                    return
            yield False
        elif self.op == '!=':
            lhs = self.left.compute(ctx)
            rhs = self.right.compute(ctx)
            yield next(lhs) != next(rhs)
        elif self.op == '>':
            lhs = self.left.compute(ctx)
            rhs = self.right.compute(ctx)
            yield next(lhs) > next(rhs)
        elif self.op == '<':
            lhs = self.left.compute(ctx)
            rhs = self.right.compute(ctx)
            yield next(lhs) < next(rhs)
        elif self.op == '>=':
            lhs = self.left.compute(ctx)
            rhs = self.right.compute(ctx)
            yield next(lhs) >= next(rhs)
        elif self.op == '<=':
            lhs = self.left.compute(ctx)
            rhs = self.right.compute(ctx)
            yield next(lhs) <= next(rhs)
        else:
            raise NotImplementedErr('Oops! Operator "{}" not yet implemented'.format(self.op))
        return

class AbsolutePath(object):
    '''
    Absolute XPath path. /a/b/c; //a/ancestor:b/@c
    '''
    def __init__(self, op='/', relative=None):
        #Operator used to root the expression
        self.op = op
        #Relative path after the absolute root operator
        self.relative = relative

    def __repr__(self):
        if self.relative:
            return '{{{} {} {}}}'.format(self.__class__.__name__, self.op, serialize(self.relative))
        else:
            return '{{{} {}}}'.format(self.__class__.__name__, self.op)

    def _serialize(self):
        yield(self.op)
        for tok in _serialize(self.relative):
            yield(tok)

    def __call__(self, ctx):
        '''
        Alias for user convenience
        '''
        yield from self.compute(ctx)

    def compute(self, ctx):
        rnode = root_node.get(ctx.item)
        if self.relative:
            new_ctx = ctx.copy(item=rnode)
            yield from self.relative.compute(new_ctx)
        else:
            yield rnode

class Step(object):
    '''
    Single step in a relative path. a; @b; text(); parent::foo:bar[5]
    '''
    def __init__(self, axis, node_test):
        self.axis = axis or 'child'
        #NameTest or NodeType object used to select from nodes in the axis
        self.node_test = node_test

    def __repr__(self):
        return '{{{} {}}}'.format(self.__class__.__name__, serialize(self))

    def _serialize(self):
        if self.axis == 'attribute':
            yield('@')
        elif self.axis:
            yield self.axis
            yield('::')

        for tok in self.node_test._serialize():
            yield(tok)

    def compute(self, ctx):
        #print('STEP', (self.axis, self.node_test, ctx.item))
        if self.axis == 'self':
            yield from self.node_test.compute(ctx)
        elif self.axis == 'child':
            for child in ctx.item.xml_children:
                new_ctx = ctx.copy(item=child)
                yield from self.node_test.compute(new_ctx)
        elif self.axis == 'attribute':
            if isinstance(ctx.item, element):
                for k, v in ctx.item.xml_attributes.items():
                    if self.node_test.name in ('*', k):
                        yield attribute_node(k, v, ctx.item)
        elif self.axis == 'ancestor':
            parent = ctx.item.xml_parent
            while parent:
                new_ctx = ctx.copy(item=parent)
                yield from self.node_test.compute(new_ctx)
                parent = parent.xml_parent
            yield root_node.get(ctx.item)
        elif self.axis == 'ancestor-or-self':
            yield from self.node_test.compute(ctx)
            parent = ctx.item.xml_parent
            while parent:
                new_ctx = ctx.copy(item=parent)
                yield from self.node_test.compute(new_ctx)
            yield root_node.get(parent)
        elif self.axis == 'descendant':
            to_process = list(ctx.item.xml_children)
            while to_process:
                child = to_process[0]
                to_process = list(child.xml_children) + to_process[1:]
                new_ctx = ctx.copy(item=child)
                yield from self.node_test.compute(new_ctx)
        elif self.axis == 'descendant-or-self':
            yield from self.node_test.compute(ctx)
            to_process = list(ctx.item.xml_children)
            while to_process:
                child = to_process[0]
                to_process = list(child.xml_children) + to_process[1:]
                new_ctx = ctx.copy(item=child)
                yield from self.node_test.compute(new_ctx)
        elif self.axis == 'following':
            if not ctx.item.xml_parent: return
            start = ctx.item.xml_parent.xml_children.index(ctx.item) + 1
            to_process = list(ctx.item.xml_parent.xml_children)[start:]
            while to_process:
                ff = to_process[0]
                to_process = list(ff.xml_children) + to_process[1:]
                new_ctx = ctx.copy(item=ff)
                yield from self.node_test.compute(new_ctx)
        elif self.axis == 'following-sibling':
            if not ctx.item.xml_parent: return
            start = ctx.item.xml_parent.xml_children.index(ctx.item) + 1
            for ff in list(ctx.item.xml_parent.xml_children)[start:]:
                new_ctx = ctx.copy(item=ff)
                yield from self.node_test.compute(new_ctx)
        elif self.axis == 'parent':
            if ctx.item.xml_parent:
                new_ctx = ctx.copy(item=ctx.item.xml_parent)
                yield from self.node_test.compute(new_ctx)
            else:
                yield root_node.get(ctx.item)
        elif self.axis == 'preceding':
            if not ctx.item.xml_parent: return
            start = ctx.item.xml_parent.xml_children.index(ctx.item)
            if start == -1: return
            to_process = list(ctx.item.xml_parent.xml_children)[start:].reverse()
            while to_process:
                prev = to_process[0]
                to_process = to_process[1:] + list(prev.xml_children).reverse()
                new_ctx = ctx.copy(item=prev)
                yield from self.node_test.compute(new_ctx)
        elif self.axis == 'preceding-sibling':
            if not ctx.item.xml_parent: return
            start = ctx.item.xml_parent.xml_children.index(ctx.item)
            if start == -1: return
            to_process = list(ctx.item.xml_parent.xml_children)[:start]
            to_process.reverse()
            for prev in to_process:
                new_ctx = ctx.copy(item=prev)
                yield from self.node_test.compute(new_ctx)
        return

    def __call__(self, ctx):
        '''
        Alias for user convenience
        '''
        yield from self.compute(ctx)

class PredicatedExpression(object):
    '''
    Expression modified by one or more predicates. (1, 2, 3, 4, 5)[. > 3]; parent::foo:bar[5]
    '''
    def __init__(self, lhs, predicates):
        self.lhs = lhs
        #list of predicates filtering the LHS's results
        self.predicates = predicates

    def __repr__(self):
        return '{{{} {}}}'.format(self.__class__.__name__, serialize(self))

    def _serialize(self):
        for tok in _serialize(self.lhs):
            yield(tok)

        for predicate in self.predicates:
            yield('[')
            for tok in _serialize(predicate):
                yield(tok)
            yield(']')

    def __call__(self, ctx):
        '''
        Alias for user convenience
        '''
        yield from self.compute(ctx)

    def compute(self, ctx):
        #print('PREDICATEDEXPRESSION', (self.lhs, self.predicates))
        for pos, item in enumerate(self.lhs.compute(ctx)):
            #XPath is 1-indexed
            new_ctx = ctx.copy(item=item, pos=pos+1)
            for pred in self.predicates:
                if hasattr(pred, 'compute'):
                    predval = next(pred.compute(new_ctx), False)
                else:
                    predval = pred
                #bools are ints in Python
                if (isinstance(predval, float) or isinstance(predval, int)) and not isinstance(predval, bool):
                    if pos + 1 != int(predval):
                        break
                else:
                    if not next(to_boolean(predval)):
                    #if not next(to_boolean(predval, scalar=True)):
                        break
            else:
                #All predicates true
                yield(item)

class NameTest(object):
    '''
    Element name node test for a Step.
    '''
    def __init__(self, name):
        #XML name or '*'
        self.name = name

    def __repr__(self):
        return '{{{} {}}}'.format(self.__class__.__name__, serialize(self))

    def _serialize(self):
        yield(self.name)

    def __str__(self):
        return ''.join(self._serialize())

    def __call__(self, ctx):
        '''
        Alias for user convenience
        '''
        yield from self.compute(ctx)

    def compute(self, ctx):
        #print('NAMETEST', (self.name, ctx.item))
        if self.name == '*':
            yield ctx.item
        else:
            #yield from (n for n in nodeseq if n.xml_name == self.name)
            if isinstance(ctx.item, element) and ctx.item.xml_name == self.name:
                yield ctx.item

class NodeTypeTest(object):
    '''
    Node type node test for a Step.
    '''
    def __init__(self, name):
        #node type name, 'node' or 'text'
        self.name = name

    def __repr__(self):
        return '{{{} {}}}'.format(self.__class__.__name__, serialize(self))

    def _serialize(self):
        yield(self.name)
        yield('(')
        yield(')')

    def __str__(self):
        return ''.join(self._serialize())

    def __call__(self, ctx):
        '''
        Alias for user convenience
        '''
        yield from self.compute(ctx)

    def compute(self, ctx):
        if self.name == 'node' or isinstance(ctx.item, str):
            yield ctx.item

class AbbreviatedStep(object):
    '''
    Abbreviated XPath step. '.' or '..'
    '''
    def __init__(self, abbr):
        #abbreviated step
        self.abbr = abbr

    def __repr__(self):
        return '{{{} {}}}'.format(self.__class__.__name__, serialize(self))

    def _serialize(self):
        yield(self.abbr)

    def compute(self, ctx):
        #print('ABBREVIATEDSTEP', (self.abbr))
        if self.abbr == '.':
            yield ctx.item
        elif self.abbr == '..':
            #parent axis
            #assert isinstance(ctx.item, node)
            if ctx.item.xml_parent:
                yield ctx.item.xml_parent
            else:
                yield root_node.get(ctx.item)

    def __call__(self, ctx):
        '''
        Alias for user convenience
        '''
        yield from self.compute(ctx)

class VariableReference(object):
    '''
    XPath variable reference, e.g. '$foo'
    '''
    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return '{{{} {}}}'.format(self.__class__.__name__, serialize(self))

    def _serialize(self):
        yield('$')
        yield(self.name)

    def __call__(self, ctx):
        '''
        Alias for user convenience
        '''
        yield from self.compute(ctx)

    def compute(self, ctx):
        yield ctx.variables[self.name]

class FunctionCall(object):
    '''
    XPath function call, e.g. 'foo()'; 'foo(1, 'a', $var)'
    '''
    def __init__(self, name, args):
        self.name = name
        #list of argument expressions
        self.args = args

    def __repr__(self):
        return '{{{} {}}}'.format(self.__class__.__name__, serialize(self))

    def _serialize(self):
        yield(self.name)
        yield('(')
        if self.args:
            for tok in _serialize(self.args[0]):
                yield(tok)

            for arg in self.args[1:]:
                yield(',')
                for tok in _serialize(arg):
                    yield(tok)
        yield(')')

    def __call__(self, ctx):
        '''
        Alias for user convenience
        '''
        yield from self.compute(ctx)

    def compute(self, ctx):
        if not self.name in ctx.functions:
            #FIXME: g11n
            raise RuntimeError('Unknown function: {}'.format(self.name))
        func = ctx.functions[self.name]
        yield from func(ctx, *self.args)

class Sequence(object):
    '''
    MicroXPath sequence, e.g. '()'; '(1, 'a', $var)'
    '''
    def __init__(self, items):
        #list of argument expressions
        self.items = items

    def __repr__(self):
        return '{{{} {}}}'.format(self.__class__.__name__, serialize(self))

    def _serialize(self):
        yield('(')
        if self.items:
            for tok in _serialize(self.items[0]):
                yield(tok)

            for item in self.items[1:]:
                yield(',')
                for tok in _serialize(item):
                    yield(tok)
        yield(')')

    def __call__(self, ctx):
        '''
        Alias for user convenience
        '''
        yield from self.compute(ctx)

    def compute(self, ctx):
        for item in self.items:
            if hasattr(item, 'compute'):# and iscallable(item.compute):
                yield from item.compute(ctx)
            else:
                yield item

def serialize(xp_ast):
    '''Serialize an XPath AST as a valid XPath expression.'''
    return ''.join(_serialize(xp_ast))

def _serialize(xp_ast):
    '''Generate token strings which, when joined together, form a valid
    XPath serialization of the AST.'''

    if hasattr(xp_ast, '_serialize'):
        for tok in xp_ast._serialize():
            yield(tok)
    elif isinstance(xp_ast, str):
        yield(repr(xp_ast))


"""
Function helpers to treat XML content.
"""

from xml.sax.saxutils import escape, unescape

from .file_utils import replace_file_content

xml_escape_table = {
    "&": "&",
    '"': """,
    "'": "'",
    ">": ">",
    "<": "<",
}

xml_unescape_table = {v: k for k, v in xml_escape_table.items()}

def xml_escape(text):
    """ Replace not valid characters for XML such as &, < and > to
      their valid replacement strings

    Parameters
    ----------
    text: str
        The text to be escaped.

    Returns
    -------
    escaped_text: str
    """
    return escape(text, xml_escape_table)

def xml_unescape(text):
    """ Do the inverse of `xml_escape`.

    Parameters
    ----------
    text: str
        The text to be escaped.

    Returns
    -------
    escaped_text: str
    """
    return unescape(text, xml_unescape_table)

def change_xml_encoding(filepath, src_enc, dst_enc='utf-8'):
    """ Modify the encoding entry in the XML file.

    Parameters
    ----------
    filepath: str
        Path to the file to be modified.

    src_enc: str
        Encoding that is written in the file

    dst_enc: str
        Encoding to be set in the file.
    """
    enc_attr = "encoding='{}'"
    replace_file_content(filepath, enc_attr.format(src_enc), enc_attr.format(dst_enc), 1)


"""
Utility functions to create QRCodes using `qrcode`.
"""
import qrcode
import qrcode.image.svg

from docstamp.file_utils import replace_file_content

def save_into_qrcode(text, out_filepath, color='', box_size=10, pixel_size=1850):
    """ Save `text` in a qrcode svg image file.

    Parameters
    ----------
    text: str
        The string to be codified in the QR image.

    out_filepath: str
        Path to the output file

    color: str
        A RGB color expressed in 6 hexadecimal values.

    box_size: scalar
        Size of the QR code boxes.
    """
    try:
        qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_L,
                           box_size=box_size, border=0, )
        qr.add_data(text)
        qr.make(fit=True)
    except Exception as exc:
        raise Exception('Error trying to generate QR code '
                        ' from `vcard_string`: {}'.format(text)) from exc
    else:
        img = qr.make_image(image_factory=qrcode.image.svg.SvgPathImage)

    _ = _qrcode_to_file(img, out_filepath)

    if color:
        replace_file_content(out_filepath, 'fill:#000000', 'fill:#{}'.format(color))

def _qrcode_to_file(qrcode, out_filepath):
    """ Save a `qrcode` object into `out_filepath`.
    Parameters
    ----------
    qrcode: qrcode object

    out_filepath: str
        Path to the output file.
    """
    try:
        qrcode.save(out_filepath)
    except Exception as exc:
        raise IOError('Error trying to save QR code file {}.'.format(out_filepath)) from exc
    else:
        return qrcode


#amara3.uxml.parser

'''
Hand-crafted parser for MicroXML [1], inspired to some extent by James Clark's Javascript work [2].

[1] https://dvcs.w3.org/hg/microxml/raw-file/tip/spec/microxml.html
[2] https://github.com/jclark/microxml-js/blob/master/microxml.js
'''

import re
from enum import Enum #https://docs.python.org/3.4/library/enum.html

from amara3.util import coroutine

class state(Enum):
    pre_element = 1
    in_element = 2
    pre_tag_gi = 3
    pre_complete_tag_gi = 4
    tag_gi = 5
    complete_tag = 6
    complete_doc = 7
    attribute = 8

class event(Enum):
    start_element = 1
    end_element = 2
    characters = 3

BOM = '\uFEFF'

CHARACTER = re.compile('[\u0009\u000a\u0020-\u007e\u00a0-\ud7ff\ue000-\ufdcf\ufdf0-\ufffd' \
            '\U00010000-\U0001fffd\U00020000-\U0002fffd\U00030000-\U0003fffd\U00040000-\U0004fffd\U00050000-\U0005fffd\U00060000-\U0006fffd' \
            '\U00070000-\U0007fffd\U00080000-\U0008fffd\U00090000-\U0009fffd\U000a0000-\U000afffd\U000b0000-\U000bfffd\U000c0000-\U000cfffd' \
            '\U000d0000-\U000dfffd\U000e0000-\U000efffd\U000f0000-\U000ffffd\U00100000-\U0010fffd]')

#MicroXML production [7] Basically CHARACTER - [\u0026\u003C\u003E]
DATACHAR = re.compile('[\u0009\u000a\u0020-\u0025\u0027-\u003B\u003D\u003F-\u007e\u00a0-\ud7ff\ue000-\ufdcf\ufdf0-\ufffd' \
            '\U00010000-\U0001fffd\U00020000-\U0002fffd\U00030000-\U0003fffd\U00040000-\U0004fffd\U00050000-\U0005fffd\U00060000-\U0006fffd' \
            '\U00070000-\U0007fffd\U00080000-\U0008fffd\U00090000-\U0009fffd\U000a0000-\U000afffd\U000b0000-\U000bfffd\U000c0000-\U000cfffd' \
            '\U000d0000-\U000dfffd\U000e0000-\U000efffd\U000f0000-\U000ffffd\U00100000-\U0010fffd]')

NAMESTARTCHAR = re.compile('[A-Za-z_\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D' \
            '\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF' \
            '\uF900-\uFDCF\uFDF0-\uFFFD\U00010000-\U000EFFFF]')

NAMECHAR =  re.compile('[0-9A-Za-z_\u00B7-\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D' \
            '\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF' \
            '\uF900-\uFDCF\uFDF0-\uFFFD\U00010000-\U000EFFFF]')

#For single quoted attrs
ATTRIBVALCHAR_SGL = re.compile('[\u0020-\u0025\u0028-\u003B\u003D\u003F-\u007e\u00a0-\ud7ff\ue000-\ufdcf\ufdf0-\ufffd' \
            '\U00010000-\U0001fffd\U00020000-\U0002fffd\U00030000-\U0003fffd\U00040000-\U0004fffd\U00050000-\U0005fffd\U00060000-\U0006fffd' \
            '\U00070000-\U0007fffd\U00080000-\U0008fffd\U00090000-\U0009fffd\U000a0000-\U000afffd\U000b0000-\U000bfffd\U000c0000-\U000cfffd' \
            '\U000d0000-\U000dfffd\U000e0000-\U000efffd\U000f0000-\U000ffffd\U00100000-\U0010fffd]')

#For double quoted attrs
ATTRIBVALCHAR_DBL = re.compile('[\u0020-\u0021\\\u0023-\u0025\u0027-\u003B\u003D\u003F-\u007e\u00a0-\ud7ff\ue000-\ufdcf\ufdf0-\ufffd' \
            '\U00010000-\U0001fffd\U00020000-\U0002fffd\U00030000-\U0003fffd\U00040000-\U0004fffd\U00050000-\U0005fffd\U00060000-\U0006fffd' \
            '\U00070000-\U0007fffd\U00080000-\U0008fffd\U00090000-\U0009fffd\U000a0000-\U000afffd\U000b0000-\U000bfffd\U000c0000-\U000cfffd' \
            '\U000d0000-\U000dfffd\U000e0000-\U000efffd\U000f0000-\U000ffffd\U00100000-\U0010fffd]')

# Tokens

WS = '[\u0009\u000A\u0020]'

HEXCHARENTOK = re.compile('[a-fA-F0-9]')
NAMEDCHARENTOK = re.compile('[a-zA-Z0-9]')
#In order of length
CHARNAMES =  (('lt', "<"), ('gt', ">"), ('amp', "&"), ('quot', '"'), ('apos', "'"))
#CHARNAMES = { 'lt': "<", 'gt': ">", 'amp': "&", 'quot': '"', 'apos': "'"}

#Make this one a utility function since we'll hope to make the transition into reading cdata rarely enough to endure the function-call overhead
def handle_cdata(pos, window, charpat, stopchars):
    '''
    Return (result, new_position) tuple.
    Result is cdata string if possible and None if more input is needed
    Or of course bad syntax can raise a RuntimeError
    '''
    cdata = ''
    cursor = start = pos

    try:
        while True:
            while charpat.match(window[cursor]):
                cursor += 1
            addchars = window[start:cursor]
            cdata += addchars
            #if window[pos] != openattr:
            #    raise RuntimeError('Mismatch in attribute quotes')
            if window[cursor] in stopchars:
                return cdata, cursor
            #Check for charref
            elif window[cursor] == '&':
                start = cursor = cursor + 1
                if window[cursor] == '#' and window[cursor + 1] == 'x':
                    #Numerical charref
                    start = cursor = cursor + 2
                    while True:
                        if HEXCHARENTOK.match(window[cursor]):
                            cursor += 1
                        elif window[cursor] == ';':
                            c = chr(int(window[start:cursor], 16))
                            if not CHARACTER.match(c):
                                raise RuntimeError('Character reference gives an illegal character: {0}'.format('&' + window[start:cursor] + ';'))
                            cdata += c
                            break
                        else:
                            raise RuntimeError('Illegal in character entity: {0}'.format(window[cursor]))
                else:
                    #Named charref
                    while True:
                        if NAMEDCHARENTOK.match(window[cursor]):
                            cursor += 1
                        elif window[cursor] == ';':
                            for cn, c in CHARNAMES:
                                if window[start:cursor] == cn:
                                    cdata += c
                                    #cursor += 1 #Skip ;
                                    break
                            else:
                                raise RuntimeError('Unknown named character reference: {0}'.format(repr(window[start:cursor])))
                            break
                        else:
                            raise RuntimeError('Illegal in character reference: {0} (around {1})'.format(window[cursor]), error_context(window, start, cursor))
            #print(start, cursor, cdata, window[cursor])
            cursor += 1
            start = cursor
    except IndexError:
        return None, cursor

def error_context(window, start, end, size=10):
    return window[max(0, start-size):min(end+size, len(window))]

@coroutine
def parser(handler, strict=True):
    next(handler) #Prime the coroutine
    #abspos = 0
    line_count = 1
    col_count = 1
    window = ''
    pos = 0
    wlen = 0
    backtrack = 0
    curr_state = state.pre_element
    done = False
    element_stack = []
    attribs = {}
    try:
        try:
            while not done:
                #import pdb; pdb.set_trace()
                frag, done = yield
                #print(frag, done)
                if not frag: continue #Ignore empty additions
                window += frag
                wlen += len(frag)
                #FIXME: throw away unneeded, prior bits of window here
                need_input = False

                while not need_input:
                    if curr_state == state.pre_element:
                        #Eat up any whitespace
                        try:
                            while window[pos] in ' \r\n\t':
                                pos += 1
                        except IndexError:
                            if not done: need_input = True #Do not advance until we have enough input
                            continue
                        #if not done and pos == wlen:
                        #    need_input = True
                        #    continue
                        if window[pos] == '<':
                            pos += 1
                            curr_state = state.pre_tag_gi
                        #if not done and pos == wlen:
                        #    need_input = True
                        #    continue
                    if curr_state in (state.pre_tag_gi, state.pre_complete_tag_gi):
                        pending_event = event.start_element if curr_state == state.pre_tag_gi else event.end_element
                        #Eat up any whitespace
                        try:
                            while window[pos] in ' \r\n\t':
                                pos += 1
                        except IndexError:
                            if not done: need_input = True #Do not advance until we have enough input
                            continue
                        if curr_state == state.pre_tag_gi and window[pos] == '/':
                            pos += 1
                            curr_state = state.pre_complete_tag_gi
                            pending_event = event.end_element
                            continue
                        #if not done and pos == wlen:
                        #    need_input = True
                        #    continue
                        advpos = pos
                        try:
                            if NAMESTARTCHAR.match(window[advpos]):
                                advpos += 1
                                while NAMECHAR.match(window[advpos]):
                                    advpos += 1
                        except IndexError:
                            if not done: need_input = True #Do not advance until we have enough input
                            continue
                        else:
                            gi = window[pos:advpos]
                            pos = advpos
                            curr_state = state.complete_tag
                    if curr_state == state.complete_tag:
                        #Eat up any whitespace
                        try:
                            while window[pos] in ' \r\n\t':
                                pos += 1
                        except IndexError:
                            if not done: need_input = True #Do not advance until we have enough input
                            continue
                        #Check for attributes
                        if pending_event == event.start_element and NAMESTARTCHAR.match(window[pos]):
                            curr_state = state.attribute
                            #Note: pos not advanced so we can re-read startchar
                            continue

                        if window[pos] == '>':
                            pos += 1
                            curr_state = state.in_element
                            attribs_out = attribs.copy()
                            attribs = {} # Reset attribs
                            if pending_event == event.start_element:
                                handler.send((pending_event, gi, attribs_out, element_stack.copy()))
                                element_stack.append(gi)
                            else:
                                opened = element_stack.pop()
                                if opened != gi:
                                    raise RuntimeError('Expected close element {0}, found {1}'.format(opened, gi))
                                handler.send((pending_event, gi, element_stack.copy()))
                                if not element_stack: #and if strict
                                    curr_state = state.complete_doc
                            if pos == wlen:
                                if done:
                                    #Error: unfinished business if this is opening tag
                                    break
                                else:
                                    need_input = True
                                    continue
                        else:
                            raise RuntimeError('Expected \'>\', found {0}'.format(window[pos]))
                    if curr_state == state.attribute:
                        backtrackpos = pos
                        advpos = pos+1 #Skip 1st char, which we know is NAMESTARTCHAR
                        try:
                            while NAMECHAR.match(window[advpos]):
                                advpos += 1
                        except IndexError:
                            if not done: need_input = True #Do not advance until we have enough input
                            pos = backtrackpos
                            continue
                        else:
                            aname = window[pos:advpos]
                            pos = advpos

                        #Eat up any whitespace
                        try:
                            while window[pos] in ' \r\n\t':
                                pos += 1
                        except IndexError:
                            if not done: need_input = True #Do not advance until we have enough input
                            pos = backtrackpos
                            continue

                        if window[pos] == '=':
                            pos += 1
                        else:
                            raise RuntimeError('Expected \'=\', found {0} (around {1})'.format(window[pos], error_context(window, pos, pos)))
                        if not done and pos == wlen:
                            need_input = True
                            pos = backtrackpos
                            continue

                        if window[pos] in '"\'':
                            openattr = window[pos]
                            attrpat = ATTRIBVALCHAR_SGL if openattr == "'" else ATTRIBVALCHAR_DBL
                            #backtrackpos = pos
                            #pos + 1 to skip the opening quote
                            aval, newpos = handle_cdata(pos+1, window, attrpat, openattr)
                            if aval == None:
                                if not done: need_input = True
                                #Don't advance to newpos, so effectively backtrack
                                continue
                                #if window[pos] != openattr:
                                #    raise RuntimeError('Mismatch in attribute quotes')
                            pos = newpos + 1 #Skip the closing quote
                            attribs[aname] = aval
                            curr_state = state.complete_tag
                    if curr_state == state.in_element:
                        chars, newpos = handle_cdata(pos, window, DATACHAR, '<')
                        if chars == None:
                            if not done: need_input = True
                            #Don't advance to newpos, so effectively backtrack
                            continue
                        pos = newpos
                        if chars: handler.send((event.characters, chars))
                        if window[pos] == '<':
                            pos += 1
                        #advpos = pos
                        #if not done and pos == wlen:
                        #    need_input = True
                        #    continue
                        curr_state = state.pre_tag_gi
                    if curr_state == state.complete_doc:
                        if pos == wlen:
                            break #All done!
                        #Eat up any whitespace
                        try:
                            while window[pos] in ' \r\n\t':
                                pos += 1
                        except IndexError:
                            if not done: need_input = True #Do not advance until we have enough input
                            continue
                        #if not done and pos == wlen:
                        #    need_input = True
                        #    continue
                        if pos == wlen:
                            break
                        else:
                            raise RuntimeError('Junk after document element')
                    #print('END1')
                #print('END2')
            sentinel = yield #Avoid StopIteration in parent from running off enf of coroutine?
        except GeneratorExit:
            #close() called
            pass #Any cleanup
    except StopIteration:
        pass #Any cleanup
    return

'''
echo "from amara3.uxml.parser import parsefrags" > /tmp/spam.py
echo "for e in parsefrags(('<spa', 'm>eggs</spam>')): print(e)" >> /tmp/spam.py
#Replace 173 with the position of the line: if curr_state == state.pre_element
python -m pdb -c "b /Users/uche/.local/venv/py3/lib/python3.3/site-packages/amara3/uxml/parser.py:173" /tmp/spam.py
'''

@coroutine
def handler(accumulator):
    while True:
        event = yield
        accumulator.append(event)
    return

def parse(text):
    acc = []
    h = handler(acc)
    p = parser(h)
    p.send((text, True))
    p.close()
    h.close()
    for event in acc:
        yield event

def parsefrags(textfrags):
    acc = []
    h = handler(acc)
    p = parser(h)
    #fragcount = len(textfrags)
    for i, frag in enumerate(textfrags):
        p.send((frag, False))
    p.send(('', True)) #Wrap it up
    p.close()
    h.close()
    for event in acc:
        yield event


# BSD 3-Clause License
#
# Copyright (c) 2017, Science and Technology Facilities Council
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# Author R. Ford STFC Daresbury Lab.
#
'''The launch function in this file is called by Melody for each
particular configuration.'''

def launch(option):
    '''Set the gromacs input data using the supplied input options, run
    gromacs and extract and return the required outputs.'''

    from melody.inputs import create_input
    _ = create_input(option, template_name="input.mdp")

    # save the input file in the appropriate place and launch gromacs using
    # longbow ...

    # determine if the run was successful
    success = True

    results = None
    if success:
        # extract the required outputs
        results = {"rate": {"value": 35, "units": "ns/day"}, }

    return success, results