''' Fast clustering functions that are compiled in time via numba '''

from numba import njit

@njit()
def _new_event(event_number_1, event_number_2):
    'Detect a new event by checking if the event number of the actual hit is the actual event number'
    return event_number_1 != event_number_2

@njit()
def _pixel_masked(hit, array):
    ''' Checks whether a hit (column/row) is masked or not. Array is 2D array with boolean elements corresponding to pixles indicating whether a pixel is disabled or not.
    '''
    if array.shape[0] > hit["column"] and array.shape[1] > hit["row"]:
        return array[hit["column"], hit["row"]]
    else:
        return False

# @njit()
# def _pixel_masked(hit, array):
#     ''' Checks whether a hit (column/row) is masked or not. Array is an iterable of column/row tuples of disabled pixels.
#     '''
#     for array_value in array:
#         if hit["column"] == array_value["column"] and hit["row"] == array_value["row"]:
#             return True
#     return False

@njit()
def _finish_cluster(hits, clusters, cluster_size, cluster_hit_indices, cluster_index, cluster_id, charge_correction, noisy_pixels, disabled_pixels):
    ''' Set hit and cluster information of the cluster (e.g. number of hits in the cluster (cluster_size), total cluster charge (charge), ...).
    '''
    cluster_charge = 0
    max_cluster_charge = -1
    # necessary for charge weighted hit position
    total_weighted_column = 0
    total_weighted_row = 0

    for i in range(cluster_size):
        hit_index = cluster_hit_indices[i]
        if hits[hit_index]['charge'] > max_cluster_charge:
            seed_hit_index = hit_index
            max_cluster_charge = hits[hit_index]['charge']
        hits[hit_index]['is_seed'] = 0
        hits[hit_index]['cluster_size'] = cluster_size
        # include charge correction in sum
        total_weighted_column += hits[hit_index]['column'] * (hits[hit_index]['charge'] + charge_correction)
        total_weighted_row += hits[hit_index]['row'] * (hits[hit_index]['charge'] + charge_correction)
        cluster_charge += hits[hit_index]['charge']
        hits[hit_index]['cluster_ID'] = cluster_id

    hits[seed_hit_index]['is_seed'] = 1

    clusters[cluster_index]["ID"] = cluster_id
    clusters[cluster_index]["n_hits"] = cluster_size
    clusters[cluster_index]["charge"] = cluster_charge
    clusters[cluster_index]['seed_column'] = hits[seed_hit_index]['column']
    clusters[cluster_index]['seed_row'] = hits[seed_hit_index]['row']
    # correct total charge value and calculate mean column and row
    clusters[cluster_index]['mean_column'] = float(total_weighted_column) / (cluster_charge + cluster_size * charge_correction)
    clusters[cluster_index]['mean_row'] = float(total_weighted_row) / (cluster_charge + cluster_size * charge_correction)

    # Call end of cluster function hook
    _end_of_cluster_function(
        hits=hits,
        clusters=clusters,
        cluster_size=cluster_size,
        cluster_hit_indices=cluster_hit_indices,
        cluster_index=cluster_index,
        cluster_id=cluster_id,
        charge_correction=charge_correction,
        noisy_pixels=noisy_pixels,
        disabled_pixels=disabled_pixels,
        seed_hit_index=seed_hit_index)

@njit()
def _finish_event(hits, clusters, start_event_hit_index, stop_event_hit_index, start_event_cluster_index, stop_event_cluster_index):
    ''' Set hit and cluster information of the event (e.g. number of cluster in the event (n_cluster), ...).
    '''
    for hit_index in range(start_event_hit_index, stop_event_hit_index):
        hits[hit_index]['n_cluster'] = stop_event_cluster_index - start_event_cluster_index

    for cluster_index in range(start_event_cluster_index, stop_event_cluster_index):
        clusters[cluster_index]['event_number'] = hits[start_event_hit_index]['event_number']

    # Call end of event function hook
    _end_of_event_function(
        hits=hits,
        clusters=clusters,
        start_event_hit_index=start_event_hit_index,
        stop_event_hit_index=stop_event_hit_index,
        start_event_cluster_index=start_event_cluster_index,
        stop_event_cluster_index=stop_event_cluster_index)

@njit()
def _hit_ok(hit, min_hit_charge, max_hit_charge):
    ''' Check if given hit is withing the limits.
    '''
    # Omit hits with charge < min_hit_charge
    if hit['charge'] < min_hit_charge:
        return False

    # Omit hits with charge > max_hit_charge
    if max_hit_charge != 0 and hit['charge'] > max_hit_charge:
        return False

    return True

@njit()
def _set_hit_invalid(hit, cluster_id=-1):
    ''' Set values for invalid hit.
    '''
    hit['cluster_ID'] = cluster_id
    hit['is_seed'] = 0
    hit['cluster_size'] = 0

@njit()
def _set_1d_array(array, value, size=-1):
    ''' Set array elemets to value for given number of elements (if size is negative number set all elements to value).
    '''
    if size >= 0:
        for i in range(size):
            array[i] = value
    else:
        for i in range(array.shape[0]):
            array[i] = value

@njit()
def _is_in_max_difference(value_1, value_2, max_difference):
    ''' Helper function to determine the difference of two values that can be np.uints. Works in python and numba mode.
    Circumvents numba bug #1653
    '''
    if value_1 <= value_2:
        return value_2 - value_1 <= max_difference
    return value_1 - value_2 <= max_difference

# @njit()
# def _end_of_cluster_function(hits, clusters, cluster_size, cluster_hit_indices, cluster_index, cluster_id, charge_correction, noisy_pixels, disabled_pixels, seed_hit_index):
#     ''' Empty function that can be overwritten with a new function that is called at the end of each cluster
#     '''
#     pass
#
#
# @njit()
# def _end_of_event_function(hits, clusters, start_event_hit_index, stop_event_hit_index, start_event_cluster_index, stop_event_cluster_index):
#     ''' Empty function that can be overwritten with a new function that is called at the end of event
#     '''
#     pass

@njit()
def _cluster_hits(hits, clusters, assigned_hit_array, cluster_hit_indices, column_cluster_distance, row_cluster_distance, frame_cluster_distance, min_hit_charge, max_hit_charge, ignore_same_hits, noisy_pixels, disabled_pixels):
    ''' Main precompiled function that loopes over the hits and clusters them
    '''
    total_hits = hits.shape[0]
    if total_hits == 0:
        return 0  # total clusters
    max_cluster_hits = cluster_hit_indices.shape[0]

    if total_hits != clusters.shape[0]:
        raise ValueError("hits and clusters must be the same size")

    if total_hits != assigned_hit_array.shape[0]:
        raise ValueError("hits and assigned_hit_array must be the same size")

    # Correction for charge weighting
    # Some chips have non-zero charge for a charge value of zero, charge needs to be corrected to calculate cluster center correctly
    if min_hit_charge == 0:
        charge_correction = 1
    else:
        charge_correction = 0

    # Temporary variables that are reset for each cluster or event
    start_event_hit_index = 0
    start_event_cluster_index = 0
    cluster_size = 0
    event_number = hits[0]['event_number']
    event_cluster_index = 0

    # Outer loop over all hits in the array (referred to as actual hit)
    for i in range(total_hits):

        # Check for new event and reset event variables
        if _new_event(hits[i]['event_number'], event_number):
            _finish_event(
                hits=hits,
                clusters=clusters,
                start_event_hit_index=start_event_hit_index,
                stop_event_hit_index=i,
                start_event_cluster_index=start_event_cluster_index,
                stop_event_cluster_index=start_event_cluster_index + event_cluster_index)

            start_event_hit_index = i
            start_event_cluster_index = start_event_cluster_index + event_cluster_index
            event_number = hits[i]['event_number']
            event_cluster_index = 0

        if assigned_hit_array[i] > 0:  # Hit was already assigned to a cluster in the inner loop, thus skip actual hit
            continue

        if not _hit_ok(
                hit=hits[i],
                min_hit_charge=min_hit_charge,
                max_hit_charge=max_hit_charge) or (disabled_pixels.shape[0] != 0 and _pixel_masked(hits[i], disabled_pixels)):
            _set_hit_invalid(hit=hits[i], cluster_id=-1)
            assigned_hit_array[i] = 1
            continue

        # Set/reset cluster variables for new cluster
        # Reset temp array with hit indices of actual cluster for the next cluster
        _set_1d_array(cluster_hit_indices, -1, cluster_size)
        cluster_hit_indices[0] = i
        assigned_hit_array[i] = 1
        cluster_size = 1  # actual cluster has one hit so far

        for j in cluster_hit_indices:  # Loop over all hits of the actual cluster; cluster_hit_indices is updated within the loop if new hit are found
            if j < 0:  # There are no more cluster hits found
                break

            for k in range(cluster_hit_indices[0] + 1, total_hits):
                # Stop event hits loop if new event is reached
                if _new_event(hits[k]['event_number'], event_number):
                    break

                # Hit is already assigned to a cluster, thus skip actual hit
                if assigned_hit_array[k] > 0:
                    continue

                if not _hit_ok(
                        hit=hits[k],
                        min_hit_charge=min_hit_charge,
                        max_hit_charge=max_hit_charge) or (disabled_pixels.shape[0] != 0 and _pixel_masked(hits[k], disabled_pixels)):
                    _set_hit_invalid(hit=hits[k], cluster_id=-1)
                    assigned_hit_array[k] = 1
                    continue

                # Check if event hit belongs to actual hit and thus to the actual cluster
                if _is_in_max_difference(hits[j]['column'], hits[k]['column'], column_cluster_distance) and _is_in_max_difference(hits[j]['row'], hits[k]['row'], row_cluster_distance) and _is_in_max_difference(hits[j]['frame'], hits[k]['frame'], frame_cluster_distance):
                    if not ignore_same_hits or hits[j]['column'] != hits[k]['column'] or hits[j]['row'] != hits[k]['row']:
                        cluster_size += 1
                        if cluster_size > max_cluster_hits:
                            raise IndexError('cluster_hit_indices is too small to contain all cluster hits')
                        cluster_hit_indices[cluster_size - 1] = k
                        assigned_hit_array[k] = 1

                    else:
                        _set_hit_invalid(hit=hits[k], cluster_id=-2)
                        assigned_hit_array[k] = 1

        # check for valid cluster and add it to the array
        if cluster_size == 1 and noisy_pixels.shape[0] != 0 and _pixel_masked(hits[cluster_hit_indices[0]], noisy_pixels):
            _set_hit_invalid(hit=hits[cluster_hit_indices[0]], cluster_id=-1)
        else:
            _finish_cluster(
                hits=hits,
                clusters=clusters,
                cluster_size=cluster_size,
                cluster_hit_indices=cluster_hit_indices,
                cluster_index=start_event_cluster_index + event_cluster_index,
                cluster_id=event_cluster_index,
                charge_correction=charge_correction,
                noisy_pixels=noisy_pixels,
                disabled_pixels=disabled_pixels)
            event_cluster_index += 1

    # Last event is assumed to be finished at the end of the hit array, thus add info
    _finish_event(
        hits=hits,
        clusters=clusters,
        start_event_hit_index=start_event_hit_index,
        stop_event_hit_index=total_hits,
        start_event_cluster_index=start_event_cluster_index,
        stop_event_cluster_index=start_event_cluster_index + event_cluster_index)

    total_clusters = start_event_cluster_index + event_cluster_index
    return total_clusters


# around a signature store type resolution for monomorphic or polymorphic call
import inspect
from pyrser.type_system.type_name import *
from pyrser.type_system.signature import *
from pyrser.type_system.fun import *
from pyrser.type_system.symbol import *

class EvalCtx:
    """
    Store environment for mono/poly call.
    """

    def __str__(self) -> str:
        import pyrser.type_system.to_fmt
        return str(self.to_fmt())

    def __init__(self, sig: Signature):
        self._sig = sig
        self.resolution = dict()
        self._translate_to = None
        self._variadic_types = None
        self.resolve()

    def get_parent(self):
        return self._sig.get_parent()

    def internal_name(self):
        return self._sig.internal_name()

    def show_name(self):
        return self._sig.show_name()

    @property
    def name(self):
        if self._sig is not None:
            return self._sig.name
        return None

    @property
    def tret(self):
        if self._sig is not None and hasattr(self._sig, 'tret'):
            return self._sig.tret
        return None

    @property
    def compute_tret(self):
        tret = []
        for t in self.tret.components:
            if t in self.resolution and self.resolution[t] is not None:
                tret.append(self.resolution[t]().show_name())
            else:
                tret.append(t)
        return " ".join(tret)

    @property
    def tparams(self):
        if self._sig is not None and hasattr(self._sig, 'tparams'):
            return self._sig.tparams
        return None

    @property
    def variadic(self):
        if self._sig is not None and hasattr(self._sig, 'variadic'):
            return self._sig.variadic
        return None

    @property
    def is_polymorphic(self) -> bool:
        if self._sig is not None and hasattr(self._sig, 'is_polymorphic'):
            return self._sig.is_polymorphic
        return False

    def from_sig(sig: Signature) -> object:
        if isinstance(sig, EvalCtx):
            return sig
        else:
            return EvalCtx(sig)

    def set_parent(self, parent) -> object:
        """
        Forward to intern Signature
        """
        return self._sig.set_parent(parent)

    def __len__(self) -> int:
        return len(self._sig)

    def values(self) -> list:
        """
        Make EvalCtx iterable (to update a scope with it)
        """
        return [self._sig]

    def get_compute_sig(self) -> Signature:
        """
        Compute a signature Using resolution!!!

        TODO: discuss of relevance of a final generation for a signature
        """
        tret = []
        tparams = []
        for t in self.tret.components:
            if t in self.resolution and self.resolution[t] is not None:
                tret.append(self.resolution[t]().show_name())
            else:
                tret.append(t)
        if hasattr(self, 'tparams'):
            for p in self.tparams:
                tp = []
                for t in p.components:
                    if t in self.resolution and self.resolution[t] is not None:
                        tp.append(self.resolution[t]().show_name())
                    else:
                        tp.append(t)
                tparams.append(" ".join(tp))
            if self.variadic:
                if self._variadic_types is None:
                    raise ValueError("Can't compute the sig "
                                     + "with unresolved variadic argument"
                                     )
                for p in self._variadic_types:
                    tp = []
                    for t in p.components:
                        if (t in self.resolution
                            and self.resolution[t] is not None
                        ):
                            tp.append(self.resolution[t]().show_name())
                        else:
                            tp.append(t)
                    tparams.append(" ".join(tp))
        ret = Fun(self.name, " ".join(tret), tparams)
        # transform as-is into our internal Signature (Val, Var, whatever)
        ret.__class__ = self._sig.__class__
        return ret

    def set_parent(self, parent) -> object:
        """
        When we add a parent (from Symbol), don't forget to resolve.
        """
        ret = self
        if parent is not None:
            ret = self._sig.set_parent(parent)
            self.resolve()
        elif not hasattr(self, 'parent'):
            # only if parent didn't exist yet
            self.parent = None
        return ret

    def use_translator(self, translator):
        """
        Attach a translator to an EvalCtx to convert output of the function
        into another type
        """
        self._translate_to = translator
        self.resolve()

    def use_variadic_types(self, list_type: [TypeName]):
        """
        Attach a list of types for extra variadic argument of a call
        """
        self._variadic_types = list_type
        self.resolve()

    def resolve(self):
        """
        Process the signature and find definition for type.
        """
        # collect types for resolution
        t2resolv = []
        if hasattr(self._sig, 'tret'):
            t2resolv.append(self._sig.tret)
        if hasattr(self._sig, 'tparams') and self._sig.tparams is not None:
            for p in self._sig.tparams:
                t2resolv.append(p)
        if self._translate_to is not None:
            t2resolv.append(self._translate_to.target)
        if self._variadic_types is not None:
            for t in self._variadic_types:
                t2resolv.append(t)
        for t in t2resolv:
            for c in t.components:
                if c not in self.resolution or self.resolution[c] is None:
                    # try to find what is c
                    parent = self.get_parent()
                    if parent is not None:
                        sc = parent.get_by_symbol_name(c)
                        if len(sc) == 1:
                            sc = list(sc.values())[0]
                            # unwrap EvalCtx around Type
                            if isinstance(sc, EvalCtx):
                                sc = sc._sig
                            rtyp = weakref.ref(sc)
                            self.resolution[c] = rtyp
                            continue
                    # unresolved
                    self.resolution[c] = None

    def get_resolved_names(self, type_name: TypeName) -> list:
        """
        Use self.resolution to subsitute type_name.
        Allow to instanciate polymorphic type ?1, ?toto
        """
        if not isinstance(type_name, TypeName):
            raise Exception("Take a TypeName as parameter not a %s"
                            % type(type_name))
        rnames = []
        for name in type_name.components:
            if name not in self.resolution:
                raise Exception("Unknown type %s in a EvalCtx" % name)
            rname = self.resolution[name]
            if rname is not None:
                rname = rname().show_name()
            else:
                rname = name
            rnames.append(rname)
        return rnames

    def set_resolved_name(self, ref: dict, type_name2solve: TypeName,
                          type_name_ref: TypeName):
        """
        Warning!!! Need to rethink it when global poly type
        """
        if self.resolution[type_name2solve.value] is None:
            self.resolution[type_name2solve.value] = ref[type_name_ref.value]


# tuple handling
from pyrser import fmt
from pyrser.type_system.signature import *

class Tuple(list):
    """
    A tuple is just a list of signature.
    """
    def __init__(self, sig: [Signature]=None):
        self._lsig = []
        if sig is not None:
            if isinstance(sig, Signature):
                self.add(sig)
            elif len(sig) > 0:
                for s in sig:
                    self.add(s)

    def add(self, sig: Signature):
        self._lsig.append(sig)

    def to_fmt(self) -> fmt.indentable:
        """
        Return an Fmt representation for pretty-printing
        """
        lsb = []
        if len(self._lsig) > 0:
            for s in self._lsig:
                lsb.append(s.to_fmt())
        block = fmt.block("(", ")", fmt.sep(', ', lsb))
        qual = "tuple"
        txt = fmt.sep("", [qual, block])
        return txt

    def __repr__(self) -> str:
        """
        Internal representation
        """
        return repr(self._lsig)

    def __str__(self) -> str:
        """
        Usefull representation
        """
        return str(self.to_fmt())


# val for type checking (literal or ENUM style)
from pyrser.type_system.signature import *
from pyrser.type_system.type_name import *

class Val(Signature):
    """
    Describe a value signature for the language
    """
    nvalues = 0
    valuniq = dict()

    def __init__(self, value, tret: str):
        if not isinstance(value, str):
            value = str(value)
        self.value = value
        if not isinstance(tret, TypeName):
            tret = TypeName(tret)
        self.tret = tret
        k = self.value + "$" + tret
        idx = 0
        if k not in Val.valuniq:
            Val.nvalues += 1
            Val.valuniq[k] = Val.nvalues
            idx = Val.nvalues
        else:
            idx = Val.valuniq[k]
        super().__init__('$' + str(idx))

    def internal_name(self):
        """
        Return the unique internal name
        """
        unq = super().internal_name()
        if self.tret is not None:
            unq += "_" + self.tret
        return unq

    def __str__(self) -> str:
        import pyrser.type_system.to_fmt
        return str(self.to_fmt())


__version__ = '0.1.4'

from glob import glob
import os
import re

from boto.s3.connection import S3Connection
from boto.exception import S3ResponseError
from boto.s3.key import Key

class S3Saver(object):
    """
    Saves files to Amazon S3 (or default local storage).
    """

    def __init__(self, storage_type=None, bucket_name=None,
                 access_key_id=None, access_key_secret=None,
                 acl='public-read', field_name=None,
                 storage_type_field=None, bucket_name_field=None,
                 filesize_field=None, base_path=None,
                 permission=0o666, static_root_parent=None):
        if storage_type and (storage_type != 's3'):
            raise ValueError('Storage type "%s" is invalid, the only supported storage type (apart from default local storage) is s3.' % storage_type)

        self.storage_type = storage_type
        self.bucket_name = bucket_name
        self.access_key_id = access_key_id
        self.access_key_secret = access_key_secret
        self.acl = acl
        self.field_name = field_name
        self.storage_type_field = storage_type_field
        self.bucket_name_field = bucket_name_field
        self.filesize_field = filesize_field
        self.base_path = base_path
        self.permission = permission
        self.static_root_parent = static_root_parent

    def _get_path(self, filename):
        if not self.base_path:
            raise ValueError('S3Saver requires base_path to be set.')

        if callable(self.base_path):
            return os.path.join(self.base_path(), filename)
        return os.path.join(self.base_path, filename)

    def _get_s3_path(self, filename):
        if not self.static_root_parent:
            raise ValueError('S3Saver requires static_root_parent to be set.')

        return re.sub('^\/', '', self._get_path(filename).replace(self.static_root_parent, ''))

    def _delete_local(self, filename):
        """Deletes the specified file from the local filesystem."""

        if os.path.exists(filename):
            os.remove(filename)

    def _delete_s3(self, filename, bucket_name):
        """Deletes the specified file from the given S3 bucket."""

        conn = S3Connection(self.access_key_id, self.access_key_secret)
        bucket = conn.get_bucket(bucket_name)

        if type(filename).__name__ == 'Key':
            filename = '/' + filename.name

        path = self._get_s3_path(filename)
        k = Key(bucket)
        k.key = path

        try:
            bucket.delete_key(k)
        except S3ResponseError:
            pass

    def delete(self, filename, storage_type=None, bucket_name=None):
        """Deletes the specified file, either locally or from S3, depending on the file's storage type."""

        if not (storage_type and bucket_name):
            self._delete_local(filename)
        else:
            if storage_type != 's3':
                raise ValueError('Storage type "%s" is invalid, the only supported storage type (apart from default local storage) is s3.' % storage_type)

            self._delete_s3(filename, bucket_name)

    def _save_local(self, temp_file, filename, obj):
        """Saves the specified file to the local file system."""

        path = self._get_path(filename)
        if not os.path.exists(os.path.dirname(path)):
            os.makedirs(os.path.dirname(path), self.permission | 0o111)

        fd = open(path, 'wb')

        # Thanks to:
        # http://stackoverflow.com/a/3253276/2066849
        temp_file.seek(0)
        t = temp_file.read(1048576)
        while t:
            fd.write(t)
            t = temp_file.read(1048576)

        fd.close()

        if self.filesize_field:
            setattr(obj, self.filesize_field, os.path.getsize(path))

        return filename

    def _save_s3(self, temp_file, filename, obj):
        """Saves the specified file to the configured S3 bucket."""

        conn = S3Connection(self.access_key_id, self.access_key_secret)
        bucket = conn.get_bucket(self.bucket_name)

        path = self._get_s3_path(filename)
        k = bucket.new_key(path)
        k.set_contents_from_string(temp_file.getvalue())
        k.set_acl(self.acl)

        if self.filesize_field:
            setattr(obj, self.filesize_field, k.size)

        return filename

    def save(self, temp_file, filename, obj):
        """Saves the specified file to either S3 or the local filesystem, depending on the currently enabled storage type."""

        if not (self.storage_type and self.bucket_name):
            ret = self._save_local(temp_file, filename, obj)
        else:
            if self.storage_type != 's3':
                raise ValueError('Storage type "%s" is invalid, the only supported storage type (apart from default local storage) is s3.' % self.storage_type)

            ret = self._save_s3(temp_file, filename, obj)

        if self.field_name:
            setattr(obj, self.field_name, ret)

        if self.storage_type == 's3':
            if self.storage_type_field:
                setattr(obj, self.storage_type_field, self.storage_type)
            if self.bucket_name_field:
                setattr(obj, self.bucket_name_field, self.bucket_name)
        else:
            if self.storage_type_field:
                setattr(obj, self.storage_type_field, '')
            if self.bucket_name_field:
                setattr(obj, self.bucket_name_field, '')

        return ret

    def _find_by_path_local(self, path):
        """Finds files by globbing on the local filesystem."""

        return glob('%s*' % path)

    def _find_by_path_s3(self, path, bucket_name):
        """Finds files by licking an S3 bucket's contents by prefix."""

        conn = S3Connection(self.access_key_id, self.access_key_secret)
        bucket = conn.get_bucket(bucket_name)

        s3_path = self._get_s3_path(path)

        return bucket.list(prefix=s3_path)

    def find_by_path(self, path, storage_type=None, bucket_name=None):
        """Finds files at the specified path / prefix, either on S3 or on the local filesystem."""

        if not (storage_type and bucket_name):
            return self._find_by_path_local(path)
        else:
            if storage_type != 's3':
                raise ValueError('Storage type "%s" is invalid, the only supported storage type (apart from default local storage) is s3.' % storage_type)

            return self._find_by_path_s3(path, bucket_name)


#: Some meta prog stuff
import functools
import inspect
import collections
import copy

def enum(*sequential, **named):
    """
    Build an enum statement
    """
    #: build enums from parameter
    enums = dict(zip(sequential, range(len(sequential))), **named)
    enums['map'] = copy.copy(enums)
    #: build reverse mapping
    enums['rmap'] = {}
    for key, value in enums.items():
        if type(value) is int:
            enums['rmap'][value] = key
    return type('Enum', (), enums)

#From PEP 362: http://www.python.org/dev/peps/pep-0362/
def checktypes(func):
    """Decorator to verify arguments and return types."""
    sig = inspect.signature(func)

    types = {}
    for param in sig.parameters.values():
        # Iterate through function's parameters and build the list of
        # arguments types
        param_type = param.annotation
        if param_type is param.empty or not inspect.isclass(param_type):
            # Missing annotation or not a type, skip it
            continue

        types[param.name] = param_type

        # If the argument has a type specified, let's check that its
        # default value (if present) conforms with the type.
        if (param.default is not param.empty and
                not isinstance(param.default, param_type)):
            raise ValueError(
                "{func}: wrong type of a default value for {arg!r}".format(
                    func=func.__qualname__, arg=param.name)
            )

    def check_type(sig, arg_name, arg_type, arg_value):
        # Internal function that encapsulates arguments type checking
        if not isinstance(arg_value, arg_type):
            raise ValueError("{func}: wrong type of {arg!r} argument, "
                             "{exp!r} expected, got {got!r}".
                             format(func=func.__qualname__, arg=arg_name,
                                    exp=arg_type.__name__,
                                    got=type(arg_value).__name__))

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # Let's bind the arguments
        ba = sig.bind(*args, **kwargs)
        for arg_name, arg in ba.arguments.items():
            # And iterate through the bound arguments
            try:
                type_ = types[arg_name]
            except KeyError:
                continue
            else:
                # OK, we have a type for the argument, lets get the
                # corresponding parameter description from the signature object
                param = sig.parameters[arg_name]
                if param.kind == param.VAR_POSITIONAL:
                    # If this parameter is a variable-argument parameter,
                    # then we need to check each of its values
                    for value in arg:
                        check_type(sig, arg_name, type_, value)
                elif param.kind == param.VAR_KEYWORD:
                    # If this parameter is a variable-keyword-argument
                    # parameter:
                    for subname, value in arg.items():
                        check_type(sig, arg_name + ':' + subname, type_, value)
                else:
                    # And, finally, if this parameter a regular one:
                    check_type(sig, arg_name, type_, arg)

        result = func(*ba.args, **ba.kwargs)

        # The last bit - let's check that the result is correct
        return_type = sig.return_annotation
        if (return_type is not sig.empty and
                isinstance(return_type, type) and
                not isinstance(result, return_type)):

            raise ValueError(
                '{func}: wrong return type, {exp} expected, got {got}'.format(
                    func=func.__qualname__, exp=return_type.__name__,
                    got=type(result).__name__)
            )
        return result

    return wrapper

# TODO: could be better in a tool module?
#: Addtototo
def set_one(chainmap, thing_name, callobject):
    """ Add a mapping with key thing_name for callobject in chainmap with
        namespace handling.
    """
    namespaces = reversed(thing_name.split("."))
    lstname = []
    for name in namespaces:
        lstname.insert(0, name)
        strname = '.'.join(lstname)
        chainmap[strname] = callobject

def _get_base_class(cls):
    base = cls
    while base.__bases__[0] is not object:
        base = base.__bases__[0]
    return base

def add_method(cls):
    """Attach a method to a class."""
    def wrapper(f):
        #if hasattr(cls, f.__name__):
        #    raise AttributeError("{} already has a '{}' attribute".format(
        #        cls.__name__, f.__name__))
        setattr(cls, f.__name__, f)
        return f
    return wrapper

def hook(cls, hookname=None, erase=False):
    """Attach a method to a parsing class and register it as a parser hook.

       The method is registered with its name unless hookname is provided.
    """
    if not hasattr(cls, '_hooks'):
        raise TypeError(
            "%s didn't seems to be a BasicParser subsclasse" % cls.__name__)
    class_hook_list = cls._hooks
    class_rule_list = cls._rules

    def wrapper(f):
        nonlocal hookname
        add_method(cls)(f)
        if hookname is None:
            hookname = f.__name__
        if not erase and (hookname in class_hook_list or hookname in class_rule_list):
            raise TypeError("%s is already define has rule or hook" % hookname)
        if '.' not in hookname:
            hookname = '.'.join([cls.__module__, cls.__name__, hookname])
        set_one(class_hook_list, hookname, f)
        return f
    return wrapper

def rule(cls, rulename=None, erase=False):
    """Attach a method to a parsing class and register it as a parser rule.

       The method is registered with its name unless rulename is provided.
    """
    if not hasattr(cls, '_rules'):
        raise TypeError(
            "%s didn't seems to be a BasicParser subsclasse" % cls.__name__)
    class_hook_list = cls._hooks
    class_rule_list = cls._rules

    def wrapper(f):
        nonlocal rulename
        add_method(cls)(f)
        if rulename is None:
            rulename = f.__name__
        if not erase and (rulename in class_hook_list or rulename in class_rule_list):
            raise TypeError("%s is already define has rule or hook" % rulename)
        if '.' not in rulename:
            rulename = cls.__module__ + '.' + cls.__name__ + '.' + rulename
        set_one(class_rule_list, rulename, f)
        return f
    return wrapper

# module variable for DirectiveWrapper registering
_directives = collections.ChainMap()

def directive(directname=None):
    """Attach a class to a parsing class and register it as a parser directive.

        The class is registered with its name unless directname is provided.
    """
    global _directives
    class_dir_list = _directives

    def wrapper(f):
        nonlocal directname
        if directname is None:
            directname = f.__name__
        f.ns_name = directname
        set_one(class_dir_list, directname, f)
        return f
    return wrapper

# module variable for FunctorDecorator registration
_decorators = collections.ChainMap()

def decorator(directname=None):
    """
        Attach a class to a parsing decorator and register it to the global
        decorator list.
        The class is registered with its name unless directname is provided
    """
    global _decorators
    class_deco_list = _decorators

    def wrapper(f):
        nonlocal directname
        if directname is None:
            directname = f.__name__
        f.ns_name = directname
        set_one(class_deco_list, directname, f)

    return wrapper


import collections
import os

from pyrser import meta
from pyrser import error
from pyrser.parsing.stream import Stream
from pyrser.parsing.stream import Tag
from pyrser.parsing.node import Node

# TODO: ensure unicity of names
#: Module variable to store meta class instance by classname
_MetaBasicParser = {}

class MetaBasicParser(type):
    """Metaclass for all parser."""
    def __new__(metacls, name, bases, namespace):
        global _MetaBasicParser
        # create the metaclass instance
        cls = type.__new__(metacls, name, bases, namespace)
        # search metaclass instance of all base
        if len(bases) > 1:
            raise TypeError("%s must inherit from an unique parent,"
                            " use Grammar for aggregation" % name)
        # Manage inheritance of Parser
        if len(bases) == 1:
            strbase = bases[0].__name__
            if strbase not in _MetaBasicParser:
                raise TypeError("metaclass of %s not found"
                                % bases[0].__name__)
            # we inherit from an already constructed parser, so get metaclass
            clsbase = _MetaBasicParser[strbase]
            # inherit rules from parser
            if hasattr(clsbase, '_rules'):
                cls._rules = clsbase._rules.new_child()
            # inherit hooks from parser
            if hasattr(clsbase, '_hooks'):
                cls._hooks = clsbase._hooks.new_child()
        # add localy defined rules
        if '_rules' in namespace:
            cls._rules.update(namespace['_rules'])
        # add localy defined hooks
        if '_hooks' in namespace:
            cls._hooks.update(namespace['_hooks'])
        # store in global registry
        _MetaBasicParser[name] = cls
        return cls

class BasicParser(metaclass=MetaBasicParser):
    """Empty basic parser, contains no rule nor hook.

    Unless you know what you are doing, use Parser instead of this class.

    """

    _rules = collections.ChainMap()
    _hooks = collections.ChainMap()

    def __init__(
            self,
            content: str='',
            stream_name: str=None,
            raise_diagnostic=True
    ):
        self._ignores = [BasicParser.ignore_blanks]
        self._streams = [Stream(content, stream_name)]
        self.rule_nodes = None
        self.push_rule_nodes()
        self._lastIgnoreIndex = 0
        self._lastIgnore = False
        self._lastRule = ""
        self.raise_diagnostic = raise_diagnostic
        self.diagnostic = error.Diagnostic()

### READ ONLY @property
    def __bool__(self):
        return self.diagnostic is False

    @property
    def _stream(self) -> Stream:
        """The current Stream."""
        return self._streams[-1]

    @property
    def nstream(self) -> int:
        """Return the number of opened stream"""
        return len(self._streams)

    @property
    def rules(self) -> dict:
        """
        Return the grammar dict
        """
        return self._rules

### Rule Nodes

    def push_rule_nodes(self) -> bool:
        """Push context variable to store rule nodes."""
        if self.rule_nodes is None:
            self.rule_nodes = collections.ChainMap()
            self.tag_cache = collections.ChainMap()
            self.id_cache = collections.ChainMap()
        else:
            self.rule_nodes = self.rule_nodes.new_child()
            self.tag_cache = self.tag_cache.new_child()
            self.id_cache = self.id_cache.new_child()
        return True

    def pop_rule_nodes(self) -> bool:
        """Pop context variable that store rule nodes"""
        self.rule_nodes = self.rule_nodes.parents
        self.tag_cache = self.tag_cache.parents
        self.id_cache = self.id_cache.parents
        return True

    def value(self, n: Node) -> str:
        """Return the text value of the node"""
        id_n = id(n)
        idcache = self.id_cache
        if id_n not in idcache:
            return ""
        name = idcache[id_n]
        tag_cache = self.tag_cache
        if name not in tag_cache:
            raise Exception("Incoherent tag cache")
        tag = tag_cache[name]
        k = "%d:%d" % (tag._begin, tag._end)
        valcache = self._streams[-1].value_cache
        if k not in valcache:
            valcache[k] = str(tag)
        return valcache[k]

### STREAM

    def parsed_stream(self, content: str, name: str=None):
        """Push a new Stream into the parser.
        All subsequent called functions will parse this new stream,
        until the 'popStream' function is called.
        """
        self._streams.append(Stream(content, name))

    def pop_stream(self):
        """Pop the last Stream pushed on to the parser stack."""
        s = self._streams.pop()
        self.clean_tmp(s)

### VARIABLE PRIMITIVES

    def begin_tag(self, name: str) -> Node:
        """Save the current index under the given name."""
        # Check if we could attach tag cache to current rule_nodes scope
        self.tag_cache[name] = Tag(self._stream, self._stream.index)
        return True

    def end_tag(self, name: str) -> Node:
        """Extract the string between saved and current index."""
        self.tag_cache[name].set_end(self._stream.index)
        return True

    def get_tag(self, name: str) -> Tag:
        """Extract the string previously saved."""
        return self.tag_cache[name]

    def tag_node(self, name: str, node: Node):
        self.id_cache[id(node)] = name

####

    @classmethod
    def set_rules(cls, rules: dict) -> bool:
        """
        Merge internal rules set with the given rules
        """
        cls._rules = cls._rules.new_child()
        for rule_name, rule_pt in rules.items():
            if '.' not in rule_name:
                rule_name = cls.__module__ \
                    + '.' + cls.__name__ \
                    + '.' + rule_name
            meta.set_one(cls._rules, rule_name, rule_pt)
        return True

    @classmethod
    def set_hooks(cls, hooks: dict) -> bool:
        """
        Merge internal hooks set with the given hooks
        """
        cls._hooks = cls._hooks.new_child()
        for hook_name, hook_pt in hooks.items():
            if '.' not in hook_name:
                hook_name = cls.__module__ \
                    + '.' + cls.__name__ \
                    + '.' + hook_name
            meta.set_one(cls._hooks, hook_name, hook_pt)
        return True

    @classmethod
    def set_directives(cls, directives: dict) -> bool:
        """
        Merge internal directives set with the given directives.
        For working directives, attach it only in the dsl.Parser class
        """
        meta._directives = meta._directives.new_child()
        for dir_name, dir_pt in directives.items():
            meta.set_one(meta._directives, dir_name, dir_pt)
            dir_pt.ns_name = dir_name
        return True

    def eval_rule(self, name: str) -> Node:
        """Evaluate a rule by name."""
        # context created by caller
        n = Node()
        id_n = id(n)
        self.rule_nodes['_'] = n
        self.id_cache[id_n] = '_'
        # TODO: other behavior for  empty rules?
        if name not in self.__class__._rules:
            self.diagnostic.notify(
                error.Severity.ERROR,
                "Unknown rule : %s" % name,
                error.LocationInfo.from_stream(self._stream, is_error=True)
            )
            raise self.diagnostic
        self._lastRule = name
        rule_to_eval = self.__class__._rules[name]
        # TODO: add packrat cache here, same rule - same pos == same res
        res = rule_to_eval(self)
        if res:
            res = self.rule_nodes['_']
        return res

    def eval_hook(self, name: str, ctx: list) -> Node:
        """Evaluate the hook by its name"""
        if name not in self.__class__._hooks:
            # TODO: don't always throw error, could have return True by default
            self.diagnostic.notify(
                error.Severity.ERROR,
                "Unknown hook : %s" % name,
                error.LocationInfo.from_stream(self._stream, is_error=True)
            )
            raise self.diagnostic
        self._lastRule = '#' + name
        res = self.__class__._hooks[name](self, *ctx)
        if type(res) is not bool:
            raise TypeError("Your hook %r didn't return a bool value" % name)
        return res

### PARSING PRIMITIVES

    def peek_char(self, c: str) -> bool:
        if self.read_eof():
            return False
        return self._stream.peek_char == c

    def peek_text(self, text: str) -> bool:
        """Same as readText but doesn't consume the stream."""
        start = self._stream.index
        stop = start + len(text)
        if stop > self._stream.eos_index:
            return False
        return self._stream[self._stream.index:stop] == text

    def one_char(self) -> bool:
        """Read one byte in stream"""
        if self.read_eof():
            return False
        self._stream.incpos()
        return True

    def read_char(self, c: str) -> bool:
        """
        Consume the c head byte, increment current index and return True
        else return False. It use peekchar and it's the same as '' in BNF.
        """
        if self.read_eof():
            return False
        self._stream.save_context()
        if c == self._stream.peek_char:
            self._stream.incpos()
            return self._stream.validate_context()
        return self._stream.restore_context()

    def read_until(self, c: str, inhibitor='\\') -> bool:
        """
        Consume the stream while the c byte is not read, else return false
        ex : if stream is " abcdef ", read_until("d"); consume "abcd".
        """
        if self.read_eof():
            return False
        self._stream.save_context()
        while not self.read_eof():
            if self.peek_char(inhibitor):
                # Delete inhibitor and inhibited character
                self.one_char()
                self.one_char()
            if self.peek_char(c):
                self._stream.incpos()
                return self._stream.validate_context()
            self._stream.incpos()
        return self._stream.restore_context()

    def read_until_eof(self) -> bool:
        """Consume all the stream. Same as EOF in BNF."""
        if self.read_eof():
            return True
        # TODO: read ALL
        self._stream.save_context()
        while not self.read_eof():
            self._stream.incpos()
        return self._stream.validate_context()

    def read_text(self, text: str) -> bool:
        """
        Consume a strlen(text) text at current position in the stream
        else return False.
        Same as "" in BNF
        ex : read_text("ls");.
        """
        if self.read_eof():
            return False
        self._stream.save_context()
        if self.peek_text(text):
            self._stream.incpos(len(text))
            return self._stream.validate_context()
        return self._stream.restore_context()

    def read_range(self, begin: str, end: str) -> int:
        """
        Consume head byte if it is >= begin and <= end else return false
        Same as 'a'..'z' in BNF
        """
        if self.read_eof():
            return False
        c = self._stream.peek_char
        if begin <= c <= end:
            self._stream.incpos()
            return True
        return False

### IGNORE CONVENTION

    def ignore_null(self) -> bool:
        """
        Empty ignore convention for notignore
        """
        return True

    def ignore_blanks(self) -> bool:
        """Consume whitespace characters."""
        self._stream.save_context()
        if not self.read_eof() and self._stream.peek_char in " \t\v\f\r\n":
            while (not self.read_eof()
                   and self._stream.peek_char in " \t\v\f\r\n"):
                self._stream.incpos()
            return self._stream.validate_context()
        return self._stream.validate_context()

    def push_ignore(self, ignoreConvention) -> bool:
        """Set the ignore convention"""
        self._ignores.append(ignoreConvention)
        return True

    def pop_ignore(self) -> bool:
        """Remove the last ignore convention"""
        self._ignores.pop()
        return True

    def skip_ignore(self) -> bool:
        if len(self._ignores) > 0:
            self._ignores[-1](self)
        self._lastIgnore = (self._stream.index != self._lastIgnoreIndex)
        self._lastIgnoreIndex = self._stream.index
        return True

    def undo_last_ignore(self) -> bool:
        # TODO(iopi): wrong don't work in all case
        if (self._stream.index > self._lastIgnoreIndex):
            self._stream.decpos(self._stream.index - self._lastIgnoreIndex)
            self._lastIgnoreIndex = self._stream.index
            #self._lastIgnore = False
        return True

class Parser(BasicParser):
    """An ascii parsing primitive library."""
    pass

### BASE RULES

@meta.hook(BasicParser)
def bind(self, dst: str, src: Node) -> bool:
    """Allow to alias a node to another name.

    Useful to bind a node to _ as return of Rule::

        R = [
            __scope__:L [item:I #add_item(L, I]* #bind('_', L)
        ]

    It's also the default behaviour of ':>'

    """

    for m in self.rule_nodes.maps:
        for k, v in m.items():
            if k == dst:
                m[k] = src
                return True
    raise Exception('%s not found' % dst)

@meta.rule(BasicParser, "Base.read_char")
def read_one_char(self) -> bool:
    return self.one_char()

@meta.rule(BasicParser, "Base.eof")
def read_eof(self) -> bool:
    """Returns true if reached end of the stream."""
    # TODO: handle multi-stream, pop last stream and continue if not the last
    return self._stream.index == self._stream.eos_index

@meta.rule(Parser, "Base.eol")
def read_eol(self) -> bool:
    """Return True if the parser can consume an EOL byte sequence."""
    if self.read_eof():
        return False
    self._stream.save_context()
    self.read_char('\r')
    if self.read_char('\n'):
        return self._stream.validate_context()
    return self._stream.restore_context()

@meta.rule(Parser, "Base.hex_num")
def read_hex_integer(self) -> bool:
    """
    read a hexadecimal number
    Read the following BNF rule else return False::

        readHexInteger = [
            [ '0'..'9' | 'a'..'f' | 'A'..'F' ]+
        ]
    """
    if self.read_eof():
        return False
    self._stream.save_context()
    c = self._stream.peek_char
    if c.isdigit() or ('a' <= c.lower() and c.lower() <= 'f'):
        self._stream.incpos()
        while not self.read_eof():
            c = self._stream.peek_char
            if not (c.isdigit() or ('a' <= c.lower() and c.lower() <= 'f')):
                break
            self._stream.incpos()
        return self._stream.validate_context()
    return self._stream.restore_context()

@meta.rule(Parser, "Base.oct_num")
def read_oct_integer(self) -> bool:
    """
    read an octal number
    Read the following BNF rule else return False::

        readOctInteger = [
            [ '0'..'7' ]+
        ]
    """
    if self.read_eof():
        return False
    self._stream.save_context()
    c = self._stream.peek_char
    if c.isdigit() and 0 <= int(c) and int(c) <= 7:
        self._stream.incpos()
        while not self.read_eof():
            c = self._stream.peek_char
            if not (c.isdigit() and 0 <= int(c) and int(c) <= 7):
                break
            self._stream.incpos()
        return self._stream.validate_context()
    return self._stream.restore_context()

@meta.rule(Parser, "Base.num")
def read_integer(self) -> bool:
    """
    read a number
    Read following BNF rule else return False::

        readInteger = [
            ['0'..'9']+
        ]

    """
    if self.read_eof():
        return False
    self._stream.save_context()
    c = self._stream.peek_char
    if c.isdigit():
            self._stream.incpos()
            while not self.read_eof():
                c = self._stream.peek_char
                if not c.isdigit():
                    break
                self._stream.incpos()
            return self._stream.validate_context()
    return self._stream.restore_context()

# `Base.id`
@meta.rule(Parser, "Base.id")
def read_identifier(self) -> bool:
    """
    read an identifier
    Read following BNF rule else return False::

        readIdentifier = [
            ['a'..'z'|'A'..'Z'|'_']['0'..'9'|'a'..'z'|'A'..'Z'|'_']*
        ]

    """
    if self.read_eof():
        return False
    self._stream.save_context()
    c = self._stream.peek_char
    if c.isalpha() or c == '_':
            self._stream.incpos()
            while not self.read_eof():
                c = self._stream.peek_char
                if not (c.isalpha() or c.isdigit() or c == '_'):
                    break
                self._stream.incpos()
            return self._stream.validate_context()
    return self._stream.restore_context()

@meta.rule(Parser, "Base.string")
def read_cstring(self) -> bool:
    """
    read a double quoted string
    Read following BNF rule else return False::

        '"' -> ['\\' #char | ~'\\'] '"'

    """
    self._stream.save_context()
    idx = self._stream.index
    if self.read_char("\"") and self.read_until("\"", "\\"):
        txt = self._stream[idx:self._stream.index]
        return self._stream.validate_context()
    return self._stream.restore_context()

@meta.rule(Parser, "Base.char")
def read_cchar(self) -> bool:
    # TODO(iopi): octal digit, hex digit
    """
    Read following BNF rule else return False::

        "'" -> ['\\' #char | ~'\\'] "'"

    """
    self._stream.save_context()
    idx = self._stream.index
    if self.read_char("\'") and self.read_until("\'", "\\"):
        txt = self._stream[idx:self._stream.index]
        return self._stream.validate_context()
    return self._stream.restore_context()

@meta.rule(Parser, "Base.qstring")
def read_cchar(self) -> bool:
    """
    read a quoted string ''
    Read following BNF rule else return False::

        "'" -> ['\\' #char | ~'\\'] "'"

    """
    self._stream.save_context()
    idx = self._stream.index
    if self.read_char("\'") and self.read_until("\'", "\\"):
        txt = self._stream[idx:self._stream.index]
        return self._stream.validate_context()
    return self._stream.restore_context()

@meta.rule(Parser, "__scope__")
def scope_nodes(self) -> bool:
    """Used for create scoped nodes"""
    return True


import inspect
import types
from pyrser import meta, error
from pyrser.parsing.base import BasicParser
from pyrser.parsing.node import Node
from pyrser.parsing.stream import Tag

_decorators = []

class Functor:
    """ Dummy Base class for all parse tree classes.

    common property:
        pt if contain a Functor
        ptlist if contain a list of Functor
    """

    def do_call(self, parser: BasicParser) -> Node:
        pass

    def __call__(self, parser: BasicParser) -> Node:
        global _decorators
        # call the begin methods in order
        for i in range(0, len(_decorators)):
            _decorators[i].begin(parser, self)
        # forward the call to the functor
        res = self.do_call(parser)
        # call the end methods in reverse order
        for i in range(len(_decorators) - 1, -1, -1):
            _decorators[i].end(res, parser, self)
        return res

class Directive2(Functor):
    def __init__(self, name, param: [(object, type)], pt: Functor):
        Functor.__init__(self)
        self.name = name
        self.pt = pt
        # compose the list of value param, check type
        for v, t in param:
            if type(t) is not type:
                raise TypeError(
                    "Must be pair of value and type (i.e: int, str, Node)")
        self.param = param

    def do_call(self, parser: BasicParser) -> bool:
        raise TypeError("Must be rewrite before execution")
        return False

class RewritingRules(Functor):
    """ Allow to write rewriting rules that transform
        the AST before code generation/interpretation.
    """

    def __init__(self, name: str=""):
        self.name = name

class SkipIgnore(Functor):
    """ Call Ignore Convention primitive functor. """

    def __init__(self, convention: str=""):
        """TODO: Could be better to implement Directive thru functors???"""
        self.convention = convention

    def do_call(self, parser: BasicParser) -> bool:
        #if len(parser._ignores) > 0:
        #    parser._ignores[-1](parser)
        parser.skip_ignore()
        return True

class Leaf:
    pass

class PeekChar(Functor, Leaf):
    """ !!'A' bnf primitive functor. """

    def __init__(self, c: str):
        self.char = c

    def do_call(self, parser: BasicParser) -> bool:
        return parser.peek_char(self.char)

class PeekText(Functor, Leaf):
    """ !!"TXT" bnf primitive functor. """

    def __init__(self, c: str):
        self.char = c

    def do_call(self, parser: BasicParser) -> bool:
        return parser.peek_text(self.char)

class Text(Functor, Leaf):
    """ "TXT" bnf primitive functor. """

    def __init__(self, txt: str):
        self.text = txt

    def do_call(self, parser: BasicParser) -> bool:
        return parser.read_text(self.text)

class Char(Functor, Leaf):
    """ 'A' bnf primitive functor. """

    def __init__(self, c: str):
        self.char = c

    def do_call(self, parser: BasicParser) -> bool:
        return parser.read_char(self.char)

class Range(Functor, Leaf):
    """ 'A'..'Z' bnf primitive functor. """

    def __init__(self, begin: str, end: str):
        self.begin = begin
        self.end = end

    def do_call(self, parser: BasicParser) -> bool:
        return parser.read_range(self.begin, self.end)

class UntilChar(Functor, Leaf):
    """ ->'A' bnf primitive functor. """

    def __init__(self, c: str):
        self.char = c

    def do_call(self, parser: BasicParser) -> bool:
        return parser.read_until(self.char)

class Seq(Functor):
    """ A B C bnf primitive as a functor. """

    def __init__(self, *ptlist: Functor):
        Functor.__init__(self)
        if len(ptlist) == 0:
            raise TypeError("Expected Functor")
        self.ptlist = []
        for it in ptlist:
            if not isinstance(it, SkipIgnore):
                self.ptlist.append(it)
                self.ptlist.append(SkipIgnore())
        if not isinstance(self.ptlist[0], SkipIgnore):
            self.ptlist.insert(0, SkipIgnore())

    def __getitem__(self, idx) -> Functor:
        """ Hide SkipIgnore object from outside """
        if idx >= 0:
            idx = (idx * 2) + 1
        else:
            idx = len(self.ptlist) - ((idx + 1) * 2) - 2
        return self.ptlist[idx]

    def do_call(self, parser: BasicParser) -> bool:
        parser._stream.save_context()
        for pt in self.ptlist:
            if not pt(parser):
                return parser._stream.restore_context()
        return parser._stream.validate_context()

class Scope(Functor):
    """ functor to wrap SCOPE/rule directive or just []. """

    def __init__(self, begin: Seq, end: Seq, pt: Seq):
        Functor.__init__(self)
        self.begin = begin
        self.end = end
        self.pt = pt

    def do_call(self, parser: BasicParser) -> Node:
        if not self.begin(parser):
            return False
        res = self.pt(parser)
        if not self.end(parser):
            return False
        return res

class LookAhead(Functor):
    """ !!A bnf primitive as a functor. """
    def __init__(self, pt: Functor):
        Functor.__init__(self)
        self.pt = pt
        if isinstance(self.pt, Seq):
            if isinstance(self.pt.ptlist[0], SkipIgnore):
                self.pt.ptlist.pop(0)
            if isinstance(self.pt.ptlist[-1], SkipIgnore):
                self.pt.ptlist.pop()

    def do_call(self, parser: BasicParser) -> bool:
        parser._stream.save_context()
        res = self.pt(parser)
        parser._stream.restore_context()
        return res

class Neg(Functor):
    """ !A bnf primitive as a functor. """

    def __init__(self, pt: Functor):
        Functor.__init__(self)
        self.pt = pt
        if isinstance(self.pt, Seq):
            if isinstance(self.pt.ptlist[0], SkipIgnore):
                self.pt.ptlist.pop(0)
            if isinstance(self.pt.ptlist[-1], SkipIgnore):
                self.pt.ptlist.pop()

    def do_call(self, parser: BasicParser):
        parser._stream.save_context()
        if self.pt(parser):
            res = parser._stream.restore_context()
            return res
        return parser._stream.validate_context()

class Complement(Functor):
    """ ~A bnf primitive as a functor. """

    def __init__(self, pt: Functor):
        Functor.__init__(self)
        self.pt = pt
        if isinstance(self.pt, Seq):
            if isinstance(self.pt.ptlist[0], SkipIgnore):
                self.pt.ptlist.pop(0)
            if isinstance(self.pt.ptlist[-1], SkipIgnore):
                self.pt.ptlist.pop()

    def do_call(self, parser: BasicParser) -> bool:
        if parser.read_eof():
            return False
        parser._stream.save_context()
        res = self.pt(parser)
        if not res:
            parser._stream.incpos()
            return parser._stream.validate_context()
        parser._stream.restore_context()
        return False

class Until(Functor):
    """ ->A bnf primitive as a functor. """

    def __init__(self, pt: Functor):
        Functor.__init__(self)
        self.pt = pt
        if isinstance(self.pt, Seq):
            if isinstance(self.pt.ptlist[0], SkipIgnore):
                self.pt.ptlist.pop(0)
            if isinstance(self.pt.ptlist[-1], SkipIgnore):
                self.pt.ptlist.pop()

    def do_call(self, parser: BasicParser) -> bool:
        parser._stream.save_context()
        while not parser.read_eof():
            res = self.pt(parser)
            if res:
                return parser._stream.validate_context()
            parser._stream.incpos()
        parser._stream.restore_context()
        parser.undo_last_ignore()
        return False

class Call(Functor):
    """ Functor wrapping a BasicParser method call in a BNF clause. """

    def __init__(self, callObject, *params):
        Functor.__init__(self)
        self.callObject = callObject
        self.params = params

    def do_call(self, parser: BasicParser) -> Node:
        return self.callObject(parser, *self.params)

class CallTrue(Call):
    """ Functor to wrap arbitrary callable object in BNF clause. """

    def do_call(self, parser: BasicParser) -> Node:
        self.callObject(*self.params)
        return True

class Capture(Functor):
    """ Functor to handle capture nodes. """

    def __init__(self, tagname: str, pt: Functor):
        Functor.__init__(self)
        if not isinstance(tagname, str) or len(tagname) == 0:
            raise TypeError("Illegal tagname for capture")
        self.tagname = tagname
        self.pt = pt
        if isinstance(self.pt, Seq):
            if isinstance(self.pt.ptlist[-1], SkipIgnore):
                self.pt.ptlist.pop()

    def do_call(self, parser: BasicParser) -> Node:
        if parser.begin_tag(self.tagname):
            # subcontext
            parser.push_rule_nodes()
            res = self.pt(parser)
            parser.pop_rule_nodes()
            if res and parser.end_tag(self.tagname):
                tag = parser.get_tag(self.tagname)
                # no bindings, wrap it in a Node instance
                if type(res) is bool:
                    res = Node()
                # update node cache
                parser.tag_node(self.tagname, res)
                parser.rule_nodes[self.tagname] = res
                # forward nodes
                return res
        return False

class DeclNode(Functor):
    """ Functor to handle node declaration with __scope__:N. """

    def __init__(self, tagname: str):
        Functor.__init__(self)
        if not isinstance(tagname, str) or len(tagname) == 0:
            raise TypeError("Illegal tagname for capture")
        self.tagname = tagname

    def do_call(self, parser: BasicParser) -> Node:
        parser.rule_nodes[self.tagname] = Node()
        return True

class Bind(Functor):
    """ Functor to handle the binding of a resulting nodes
        to an existing name.
    """

    def __init__(self, tagname: str, pt: Functor):
        Functor.__init__(self)
        if not isinstance(tagname, str) or len(tagname) == 0:
            raise TypeError("Illegal tagname for capture")
        self.tagname = tagname
        self.pt = pt

    def do_call(self, parser: BasicParser) -> Node:
        res = self.pt(parser)
        if res:
            parser.bind(self.tagname, res)
            return res
        return False

class Alt(Functor):
    """ A | B bnf primitive as a functor. """

    def __init__(self, *ptlist: Seq):
        Functor.__init__(self)
        self.ptlist = ptlist

    def __getitem__(self, idx) -> Functor:
        return self.ptlist[idx]

    def do_call(self, parser: BasicParser) -> Node:
        # save result of current rule
        parser.push_rule_nodes()
        for pt in self.ptlist:
            parser._stream.save_context()
            parser.push_rule_nodes()
            res = pt(parser)
            if res:
                parser.pop_rule_nodes()
                parser.pop_rule_nodes()
                parser._stream.validate_context()
                return res
            parser.pop_rule_nodes()
            parser._stream.restore_context()
        parser.pop_rule_nodes()
        return False

class RepOptional(Functor):
    """ []? bnf primitive as a functor. """
    def __init__(self, pt: Seq):
        Functor.__init__(self)
        self.pt = pt
        if (isinstance(self.pt, Text) or isinstance(self.pt, Char)
            or isinstance(self.pt, Range) or isinstance(self.pt, Directive)
        ):
            self.pt = Seq(self.pt)

    def do_call(self, parser: BasicParser) -> bool:
        res = self.pt(parser)
        if res:
            return res
        return True

class Rep0N(Functor):
    """ []* bnf primitive as a functor. """

    def __init__(self, pt: Seq):
        Functor.__init__(self)
        self.pt = pt
        if (isinstance(self.pt, Text) or isinstance(self.pt, Char)
            or isinstance(self.pt, Range) or isinstance(self.pt, Directive)
        ):
            self.pt = Seq(self.pt)

    def do_call(self, parser: BasicParser) -> bool:
        parser._stream.save_context()
        parser.push_rule_nodes()
        while self.pt(parser):
            pass
        parser.pop_rule_nodes()
        return parser._stream.validate_context()

class Rep1N(Functor):
    """ []+ bnf primitive as a functor. """

    def __init__(self, pt: Seq):
        Functor.__init__(self)
        self.pt = pt
        if (isinstance(self.pt, Text) or isinstance(self.pt, Char)
            or isinstance(self.pt, Range) or isinstance(self.pt, Directive)
        ):
            self.pt = Seq(self.pt)

    def do_call(self, parser: BasicParser) -> bool:
        parser._stream.save_context()
        parser.push_rule_nodes()
        if self.pt(parser):
            while self.pt(parser):
                pass
            parser.pop_rule_nodes()
            return parser._stream.validate_context()
        parser.pop_rule_nodes()
        return parser._stream.restore_context()

class Error(Functor):
    """ Raise an error. """

    def __init__(self, msg: str, **kwargs):
        self.msg = msg
        self.kw = kwargs

    def do_call(self, parser: BasicParser) -> bool:
        parser.diagnostic.notify(
            error.Severity.ERROR,
            self.msg,
            error.LocationInfo.from_stream(parser._stream, is_error=True)
        )
        raise parser.diagnostic

class Rule(Functor, Leaf):
    """ Call a rule by its name. """

    def __init__(self, name: str):
        Functor.__init__(self)
        self.name = name

    def do_call(self, parser: BasicParser) -> Node:
        parser.push_rule_nodes()
        res = parser.eval_rule(self.name)
        parser.pop_rule_nodes()
        return res

class Hook(Functor, Leaf):
    """ Call a hook by his name. """

    def __init__(self, name: str, param: [(object, type)]):
        Functor.__init__(self)
        self.name = name
        # compose the list of value param, check type
        for v, t in param:
            if type(t) is not type:
                raise TypeError("Must be pair of value and type (i.e: int, "
                                "str, Node)")
        self.param = param

    def do_call(self, parser: BasicParser) -> bool:
        valueparam = []
        for v, t in self.param:
            if t is Node:
                if v not in parser.rule_nodes:
                    parser.diagnostic.notify(
                        error.Severity.ERROR,
                        "Unknown capture variable : %s" % v,
                        error.LocationInfo.from_stream(
                            parser._stream,
                            is_error=True
                        )
                    )
                    raise parser.diagnostic
                valueparam.append(parser.rule_nodes[v])
            elif type(v) is t:
                valueparam.append(v)
            else:
                raise TypeError("Type mismatch expected {} got {}".format(
                    t, type(v)))
        return parser.eval_hook(self.name, valueparam)

class MetaDirectiveWrapper(type):
    """ metaclass of all DirectiveWrapper subclasses.
    ensure that begin and end exists in subclasses as method
    """
    def __new__(metacls, name, bases, namespace):
        cls = type.__new__(metacls, name, bases, namespace)
        if 'begin' not in namespace:
            raise TypeError(
                "DirectiveWrapper %s must have a begin method" % name)
        if not(isinstance(namespace['begin'], types.FunctionType)):
            raise TypeError(
                "'begin' not a function class in DirectiveWrapper %s" % name)
        if 'end' not in namespace:
            raise TypeError(
                "DirectiveWrapper %s subclasse must have a end method" % name)
        if not(isinstance(namespace['end'], types.FunctionType)):
            raise TypeError(
                "'end' not a function class in DirectiveWrapper %s" % name)
        return cls

class DirectiveWrapper(Functor, metaclass=MetaDirectiveWrapper):
    """ functor to wrap begin/end directive """

    def __init__(self, ):
        Functor.__init__(self)

    def checkParam(self, params: list):
        if (not hasattr(self.__class__, 'begin') or
                not hasattr(self.__class__, 'end')):
            return False
        sbegin = inspect.signature(self.begin)
        send = inspect.signature(self.end)

        idx = 0
        for param in list(sbegin.parameters.values())[1:]:
            if idx >= len(params) and param.default is inspect.Parameter.empty:
                raise RuntimeError("{}: No parameter given to begin"
                                   " method for argument {}, expected {}".
                                   format(
                                       self.__class__.__name__,
                                       idx, param.annotation))
            elif (idx < len(params)
                  and not isinstance(params[idx], param.annotation)):
                raise TypeError(
                    "{}: Wrong parameter in begin method parameter {} "
                    "expected {} got {}".format(
                        self.__class__.__name__,
                        idx, type(params[idx]),
                        param.annotation))
            idx += 1

        idx = 0
        for param in list(send.parameters.values())[1:]:
            if idx >= len(params) and param.default is inspect.Parameter.empty:
                raise RuntimeError("{}: No parameter given to end"
                                   " method for argument {}, expected {}".
                                   format(
                                       self.__class__.__name__,
                                       idx, param.annotation))
            elif (idx < len(params)
                  and not isinstance(params[idx], param.annotation)):
                raise TypeError(
                    "{}: Wrong parameter in end method parameter {} "
                    "expected {} got {}".format(
                        self.__class__.__name__,
                        idx, type(params[idx]),
                        param.annotation))
            idx += 1

        return True

    def begin(self):
        pass

    def end(self):
        pass

class Directive(Functor):
    """ functor to wrap directive HOOKS """

    def __init__(self, directive: DirectiveWrapper, param: [(object, type)],
                 pt: Functor):
        Functor.__init__(self)
        self.directive = directive
        self.pt = pt
        # compose the list of value param, check type
        for v, t in param:
            if type(t) is not type:
                raise TypeError(
                    "Must be pair of value and type (i.e: int, str, Node)")
        self.param = param

    def do_call(self, parser: BasicParser) -> Node:
        valueparam = []
        for v, t in self.param:
            if t is Node:
                valueparam.append(parser.rule_nodes[v])
            elif type(v) is t:
                valueparam.append(v)
            else:
                raise TypeError(
                    "Type mismatch expected {} got {}".format(t, type(v)))
        if not self.directive.checkParam(valueparam):
            return False
        if not self.directive.begin(parser, *valueparam):
            return False
        res = self.pt(parser)
        if not self.directive.end(parser, *valueparam):
            return False
        return res

class MetaDecoratorWrapper(type):
    """ metaclass of all DecoratorWrapper subclasses.
    ensure that begin and end exists in subclasses as method
    """
    def __new__(metacls, name, bases, namespace):
        cls = type.__new__(metacls, name, bases, namespace)
        if 'begin' not in namespace:
            raise TypeError(
                "DirectiveWrapper %s must have a begin method" % name)
        if not(isinstance(namespace['begin'], types.FunctionType)):
            raise TypeError(
                "'begin' not a function class in DirectiveWrapper %s" % name)
        if 'end' not in namespace:
            raise TypeError(
                "DirectiveWrapper %s subclasse must have a end method" % name)
        if not(isinstance(namespace['end'], types.FunctionType)):
            raise TypeError(
                "'end' not a function class in DirectiveWrapper %s" % name)
        return cls

class DecoratorWrapper(Functor, metaclass=MetaDecoratorWrapper):

    def begin(self):
        pass

    def end(self):
        pass

class Decorator(Functor):
    def __init__(self, decoratorClass: type, param: [(object, type)],
                 pt: Functor):
        self.decorator_class = decoratorClass
        self.pt = pt
        # compose the list of value param, check type
        for v, t in param:
            if type(t) is not type:
                raise TypeError(
                    "Must be pair of value and type (i.e: int, str, Node)")
        self.param = param

    def checkParam(self, the_class: type, params: list) -> bool:
        sinit = inspect.signature(the_class.__init__)

        idx = 0
        for param in list(sinit.parameters.values())[1:]:
            if idx >= len(params) and param.default is inspect.Parameter.empty:
                raise RuntimeError("{}: No parameter given to begin"
                                   " method for argument {}, expected {}".
                                   format(
                                       the_class.__name__,
                                       idx, param.annotation))
            elif (idx < len(params)
                  and not isinstance(params[idx], param.annotation)):
                raise TypeError(
                    "{}: Wrong parameter in begin method parameter {} "
                    "expected {} got {}".format(
                        the_class.__name__,
                        idx, type(params[idx]),
                        param.annotation))
            idx += 1

        return True

    def do_call(self, parser: BasicParser) -> Node:
        """
            The Decorator call is the one that actually pushes/pops
            the decorator in the active decorators list (parsing._decorators)
        """
        valueparam = []
        for v, t in self.param:
            if t is Node:
                valueparam.append(parser.rule_nodes[v])
            elif type(v) is t:
                valueparam.append(v)
            else:
                raise TypeError(
                    "Type mismatch expected {} got {}".format(t, type(v)))

        if not self.checkParam(self.decorator_class, valueparam):
            return False

        decorator = self.decorator_class(*valueparam)

        global _decorators
        _decorators.append(decorator)
        res = self.pt(parser)
        _decorators.pop()

        return res


# fun for type checking (functions signatures)
from pyrser.type_system.signature import *
from pyrser.type_system.type_name import *

class Fun(Signature):
    """
    Describe a function signature for the language
    """

    def __init__(
        self,
        name: str,
        tret: str,
        tparams: list=None,
        variadic=None
    ):
        if tparams is not None and not isinstance(tparams, list):
            raise TypeError("Fun's parameter list must be a list")
        super().__init__(name)
        if not isinstance(tret, TypeName):
            tret = TypeName(tret)
        self.tret = tret
        self.variadic = variadic
        self.tparams = None
        if tparams is not None and len(tparams) > 0:
            self.tparams = []
            for p in tparams:
                if not isinstance(p, TypeName):
                    p = TypeName(p)
                self.tparams.append(p)

    @property
    def arity(self) -> int:
        return len(self.tparams)

    @property
    def return_type(self) -> str:
        return self.tret

    @property
    def this_type(self) -> str:
        return self.tparams[0]

    @property
    def is_polymorphic(self) -> bool:
        if self.tret.is_polymorphic:
            return True
        for p in self.tparams:
            if p.is_polymorphic:
                return True

    @property
    def is_fun(self) -> bool:
        return True

    def internal_name(self):
        """
        Return the unique internal name
        """
        unq = 'f_' + super().internal_name()
        if self.tparams is not None:
            unq += "_" + "_".join(self.tparams)
        if self.tret is not None:
            unq += "_" + self.tret
        return unq

    def __str__(self) -> str:
        import pyrser.type_system.to_fmt
        return str(self.to_fmt())