import logging
import os

import numpy as np

hit_data_type = np.dtype([('event_number', '<i8'),
                          ('frame', '<u1'),
                          ('column', '<u2'),
                          ('row', '<u2'),
                          ('charge', '<u2')])

def np_uint_type_chooser(number):
    for dtype in [np.uint8, np.uint16, np.uint32, np.uint64]:
        if number <= np.iinfo(dtype).max:
            return dtype
    raise ValueError('{} is too big!'.format(number))

def np_int_type_chooser(number):
    for dtype in [np.int8, np.int16, np.int32, np.int64]:
        if number <= np.iinfo(dtype).max:
            return dtype
    raise ValueError('{} is too big!'.format(number))

class HitClusterizer(object):

    ''' Clusterizer class providing an interface for the jitted functions and stores settings.'''

    def __init__(self, hit_fields=None, hit_dtype=None, cluster_fields=None, cluster_dtype=None, pure_python=False, min_hit_charge=0, max_hit_charge=None, column_cluster_distance=1, row_cluster_distance=1, frame_cluster_distance=0, ignore_same_hits=True):
        # Activate pute python mode by setting the evnironment variable NUMBA_DISABLE_JIT
        self.pure_python = pure_python
        if self.pure_python:
            logging.warning('PURE PYTHON MODE: USE FOR TESTING ONLY! YOU CANNOT SWITCH THE MODE WITHIN ONE PYTHON INTERPRETER INSTANCE!')
            os.environ['NUMBA_DISABLE_JIT'] = '1'
        else:
            os.environ['NUMBA_DISABLE_JIT'] = '0'

        # Delayed import of numba.njit, since the environment 'NUMBA_DISABLE_JIT' is evaluated on import.
        # To allow pure_python mode this dirty hack is needed; issues occur when within the same python instance the mode is switched, since python does
        # NOT provide a proper method to reload modules.
        self.cluster_functions = __import__('pixel_clusterizer.cluster_functions').cluster_functions

        # Set the translation dictionary for the important hit value names
        self._default_hit_fields_mapping = {'event_number': 'event_number',
                                            'frame': 'frame',
                                            'column': 'column',
                                            'row': 'row',
                                            'charge': 'charge',
                                            'cluster_ID': 'cluster_ID',
                                            'is_seed': 'is_seed',
                                            'cluster_size': 'cluster_size',
                                            'n_cluster': 'n_cluster'}
        if hit_fields:
            self.set_hit_fields(hit_fields)
        else:
            self.set_hit_fields(None)

        # Set the translation dictionary for the important hit value names
        self._default_cluster_fields_mapping = {'event_number': 'event_number',
                                                'ID': 'ID',
                                                'n_hits': 'n_hits',
                                                'charge': 'charge',
                                                'seed_column': 'seed_column',
                                                'seed_row': 'seed_row',
                                                'mean_column': 'mean_column',
                                                'mean_row': 'mean_row'}
        if cluster_fields:
            self.set_cluster_fields(cluster_fields)
        else:
            self.set_cluster_fields(None)

        # Set hit data structure for clustered hits
        self._default_cluster_hits_descr = [('event_number', '<i8'),
                                            ('frame', '<u1'),
                                            ('column', '<u2'),
                                            ('row', '<u2'),
                                            ('charge', '<u2'),
                                            ('cluster_ID', '<i2'),
                                            ('is_seed', '<u1'),
                                            ('cluster_size', '<u2'),
                                            ('n_cluster', '<u2')]
        if hit_dtype:
            self.set_hit_dtype(hit_dtype)
        else:
            self.set_hit_dtype(None)

        # Set cluster data struct for clustered hits
        self._default_cluster_descr = [('event_number', '<i8'),
                                       ('ID', '<u2'),
                                       ('n_hits', '<u2'),
                                       ('charge', '<f4'),
                                       ('seed_column', '<u2'),
                                       ('seed_row', '<u2'),
                                       ('mean_column', '<f4'),
                                       ('mean_row', '<f4')]
        if cluster_dtype:
            self.set_cluster_dtype(cluster_dtype)
        else:
            self.set_cluster_dtype(None)

        self._initialized = True

        # Std. settings
        self.set_min_hit_charge(min_hit_charge)
        self.set_max_hit_charge(max_hit_charge)
        self.set_column_cluster_distance(column_cluster_distance)
        self.set_row_cluster_distance(row_cluster_distance)
        self.set_frame_cluster_distance(frame_cluster_distance)
        self.ignore_same_hits(ignore_same_hits)

        self.reset()

    @property
    def initialized(self):
        return hasattr(self, "_initialized") and self._initialized

    def _init_arrays(self, size=0):
        if self.initialized:
            self._cluster_hits = np.zeros(shape=(size, ), dtype=np.dtype(self._cluster_hits_descr))
            self._clusters = np.zeros(shape=(size, ), dtype=np.dtype(self._cluster_descr))
            self._assigned_hit_array = np.zeros(shape=(size, ), dtype=np.bool)
            self._cluster_hit_indices = np.empty(shape=(size, ), dtype=np_int_type_chooser(size))
            self._cluster_hit_indices.fill(-1)

    def reset(self):  # Resets the overwritten function hooks, otherwise they are stored as a module global and not reset on clusterizer initialization
        self._init_arrays(size=0)

        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):
            pass

        def end_of_event_function(hits, clusters, start_event_hit_index, stop_event_hit_index, start_event_cluster_index, stop_event_cluster_index):
            pass

        self.set_end_of_cluster_function(end_of_cluster_function)
        self.set_end_of_event_function(end_of_event_function)

    def set_hit_fields(self, hit_fields):
        ''' Tell the clusterizer the meaning of the field names.

        The hit_fields parameter is a dict, e.g., {"new field name": "standard field name"}.

        If None default mapping is set.

        Example:
        --------
        Internally, the clusterizer uses the hit fields names "column"/"row". If the name of the hits fields are "x"/"y", call:
        set_hit_fields(hit_fields={'x': 'column',
                                   'y': 'row'})
        '''
        if not hit_fields:
            hit_fields_mapping_inverse = {}
            hit_fields_mapping = {}
        else:
            # Create also the inverse dictionary for faster lookup
            hit_fields_mapping_inverse = dict((k, v) for k, v in hit_fields.items())
            hit_fields_mapping = dict((v, k) for k, v in hit_fields.items())

        for old_name, new_name in self._default_hit_fields_mapping.items():
            if old_name not in hit_fields_mapping:
                hit_fields_mapping[old_name] = new_name
                hit_fields_mapping_inverse[new_name] = old_name

        self._hit_fields_mapping = hit_fields_mapping
        self._hit_fields_mapping_inverse = hit_fields_mapping_inverse

    def set_cluster_fields(self, cluster_fields):
        ''' Tell the clusterizer the meaning of the field names.

        The cluster_fields parameter is a dict, e.g., {"new filed name": "standard field name"}.
        '''
        if not cluster_fields:
            cluster_fields_mapping_inverse = {}
            cluster_fields_mapping = {}
        else:
            # Create also the inverse dictionary for faster lookup
            cluster_fields_mapping_inverse = dict((k, v) for k, v in cluster_fields.items())
            cluster_fields_mapping = dict((v, k) for k, v in cluster_fields.items())

        for old_name, new_name in self._default_cluster_fields_mapping.items():
            if old_name not in cluster_fields_mapping:
                cluster_fields_mapping[old_name] = new_name
                cluster_fields_mapping_inverse[new_name] = old_name

        self._cluster_fields_mapping = cluster_fields_mapping
        self._cluster_fields_mapping_inverse = cluster_fields_mapping_inverse

    def set_hit_dtype(self, hit_dtype):
        ''' Set the data type of the hits.

        Fields that are not mentioned here are NOT copied into the clustered hits array.
        Clusterizer has to know the hit data type to produce the clustered hit result with the same data types.

        Parameters:
        -----------
        hit_dtype : numpy.dtype or equivalent
            Defines the dtype of the hit array.

        Example:
        --------
        hit_dtype = [("column", np.uint16), ("row", np.uint16)], where
        "column", "row" is the field name of the input hit array.
        '''

        if not hit_dtype:
            hit_dtype = np.dtype([])
        else:
            hit_dtype = np.dtype(hit_dtype)
        cluster_hits_descr = hit_dtype.descr

        # Add default back to description
        for dtype_name, dtype in self._default_cluster_hits_descr:
            if self._hit_fields_mapping[dtype_name] not in hit_dtype.fields:
                cluster_hits_descr.append((dtype_name, dtype))
        self._cluster_hits_descr = cluster_hits_descr
        self._init_arrays(size=0)

    def set_cluster_dtype(self, cluster_dtype):
        ''' Set the data type of the cluster.

        Parameters:
        -----------
        cluster_dtype : numpy.dtype or equivalent
            Defines the dtype of the cluster array.
        '''
        if not cluster_dtype:
            cluster_dtype = np.dtype([])
        else:
            cluster_dtype = np.dtype(cluster_dtype)
        cluster_descr = cluster_dtype.descr

        for dtype_name, dtype in self._default_cluster_descr:
            if self._cluster_fields_mapping[dtype_name] not in cluster_dtype.fields:
                cluster_descr.append((dtype_name, dtype))

        self._cluster_descr = cluster_descr
        self._init_arrays(size=0)

    def add_cluster_field(self, description):
        ''' Adds a field or a list of fields to the cluster result array. Has to be defined as a numpy dtype entry, e.g.: ('parameter', '<i4') '''
        if isinstance(description, list):
            for item in description:
                if len(item) != 2:
                    raise TypeError("Description needs to be a list of 2-tuples of a string and a dtype.")
                self._cluster_descr.append(item)
        else:
            if len(description) != 2:
                raise TypeError("Description needs to be a 2-tuple of a string and a dtype.")
            self._cluster_descr.append(description)
        self._init_arrays(size=0)

    def set_end_of_cluster_function(self, function):
        ''' Adding function to module.
        This is maybe the only way to make the clusterizer to work with multiprocessing.
        '''
        self.cluster_functions._end_of_cluster_function = self._jitted(function)
        self._end_of_cluster_function = function

    def set_end_of_event_function(self, function):
        ''' Adding function to module.
        This is maybe the only way to make the clusterizer to work with multiprocessing.
        '''
        self.cluster_functions._end_of_event_function = self._jitted(function)
        self._end_of_event_function = function

    def set_min_hit_charge(self, value):
        ''' Charge values below this value will effectively ignore the hit.
        Value has influence on clustering charge weighting.
        '''
        self._min_hit_charge = value

    def set_max_hit_charge(self, value):
        ''' Charge values above this value will effectively ignore the hit.
        Value of None or 0 will deactivate this feature.
        '''
        if value is None:
            value = 0
        self._max_hit_charge = value

    def set_column_cluster_distance(self, value):
        ''' Setting up max. column cluster distance.
        '''
        self._column_cluster_distance = value

    def set_row_cluster_distance(self, value):
        ''' Setting up max. row cluster distance.
        '''
        self._row_cluster_distance = value

    def set_frame_cluster_distance(self, value):
        ''' Setting up max. frame cluster distance.
        '''
        self._frame_cluster_distance = value

    def ignore_same_hits(self, value):
        ''' Whether a duplicate hit in the event with the same column and row is ignored or not.
        '''
        self._ignore_same_hits = value

    def cluster_hits(self, hits, noisy_pixels=None, disabled_pixels=None):
        ''' Cluster given hit array.

        The noisy_pixels and disabled_pixels parameters are iterables of column/row index pairs, e.g. [[column_1, row_1], [column_2, row_2], ...].
        The noisy_pixels parameter allows for removing clusters that consist of a single noisy pixels. Clusters with 2 or more noisy pixels are not removed.
        The disabled_pixels parameter allows for ignoring pixels.
        '''
        # Jitting a second time to workaround different bahavior of the installation methods on different platforms (pip install vs. python setup.py).
        # In some circumstances, the Numba compiler can't compile functions that were pickled previously.
        self.cluster_functions._end_of_cluster_function = self._jitted(self._end_of_cluster_function)
        self.cluster_functions._end_of_event_function = self._jitted(self._end_of_event_function)

        n_hits = hits.shape[0]  # Set n_hits to new size

        if (n_hits < int(0.5 * self._cluster_hits.size)) or (n_hits > self._cluster_hits.size):
            self._init_arrays(size=int(1.1 * n_hits))  # oversize buffer slightly to reduce allocations
        else:
            self._assigned_hit_array.fill(0)  # The hit indices of the actual cluster, 0 means not assigned
            self._cluster_hit_indices.fill(-1)  # The hit indices of the actual cluster, -1 means not assigned

        self._clusters.dtype.names = self._unmap_cluster_field_names(self._clusters.dtype.names)  # Reset the data fields from previous renaming
        self._cluster_hits.dtype.names = self._unmap_hit_field_names(self._cluster_hits.dtype.names)  # Reset the data fields from previous renaming
        self._check_struct_compatibility(hits)

        # The hit info is extended by the cluster info; this is only possible by creating a new hit info array and copy data
        for field_name in hits.dtype.fields:
            if field_name in self._hit_fields_mapping_inverse:
                cluster_hits_field_name = self._hit_fields_mapping_inverse[field_name]
            else:
                cluster_hits_field_name = field_name
            if cluster_hits_field_name in self._cluster_hits.dtype.fields:
                self._cluster_hits[cluster_hits_field_name][:n_hits] = hits[field_name]

        noisy_pixels_array = np.array([]) if noisy_pixels is None else np.array(noisy_pixels)
        if noisy_pixels_array.shape[0] != 0:
            noisy_pixels_max_range = np.array([max(0, np.max(noisy_pixels_array[:, 0])), max(0, np.max(noisy_pixels_array[:, 1]))])
            noisy_pixels = np.zeros(noisy_pixels_max_range + 1, dtype=np.bool)
            noisy_pixels[noisy_pixels_array[:, 0], noisy_pixels_array[:, 1]] = 1
        else:
            noisy_pixels = np.zeros((0, 0), dtype=np.bool)

        disabled_pixels_array = np.array([]) if disabled_pixels is None else np.array(disabled_pixels)
        if disabled_pixels_array.shape[0] != 0:
            disabled_pixels_max_range = np.array([np.max(disabled_pixels_array[:, 0]), np.max(disabled_pixels_array[:, 1])])
            disabled_pixels = np.zeros(disabled_pixels_max_range + 1, dtype=np.bool)
            disabled_pixels[disabled_pixels_array[:, 0], disabled_pixels_array[:, 1]] = 1
        else:
            disabled_pixels = np.zeros((0, 0), dtype=np.bool)

#         col_dtype = self._cluster_hits.dtype.fields["column"][0]
#         row_dtype = self._cluster_hits.dtype.fields["row"][0]
#         mask_dtype = {"names": ["column", "row"],
#                       "formats": [col_dtype, row_dtype]}
#         noisy_pixels = np.recarray(noisy_pixels_array.shape[0], dtype=mask_dtype)
#         noisy_pixels[:] = [(item[0], item[1]) for item in noisy_pixels_array]
#         disabled_pixels = np.recarray(disabled_pixels_array.shape[0], dtype=mask_dtype)
#         disabled_pixels[:] = [(item[0], item[1]) for item in disabled_pixels_array]

        n_clusters = self.cluster_functions._cluster_hits(  # Set n_clusters to new size
            hits=self._cluster_hits[:n_hits],
            clusters=self._clusters[:n_hits],
            assigned_hit_array=self._assigned_hit_array[:n_hits],
            cluster_hit_indices=self._cluster_hit_indices[:n_hits],
            column_cluster_distance=self._column_cluster_distance,
            row_cluster_distance=self._row_cluster_distance,
            frame_cluster_distance=self._frame_cluster_distance,
            min_hit_charge=self._min_hit_charge,
            max_hit_charge=self._max_hit_charge,
            ignore_same_hits=self._ignore_same_hits,
            noisy_pixels=noisy_pixels,
            disabled_pixels=disabled_pixels)

        self._cluster_hits.dtype.names = self._map_hit_field_names(self._cluster_hits.dtype.names)  # Rename the data fields for the result
        self._clusters.dtype.names = self._map_cluster_field_names(self._clusters.dtype.names)  # Rename the data fields for the result

        return self._cluster_hits[:n_hits], self._clusters[:n_clusters]

    def _jitted(self, function):
        from numba import njit
        if self.pure_python:
            return function
        else:
            try:
                # test whether the function is already jitted or not
                function.py_func
            except AttributeError:
                # Adding decorator to function
                return njit()(function)
            else:
                # already jitted
                return function

    def _map_hit_field_names(self, dtype_names):  # Maps the hit field names from the internal convention to the external defined one
        unpatched_field_names = list(dtype_names)
        for index, unpatched_field_name in enumerate(unpatched_field_names):
            if unpatched_field_name in self._hit_fields_mapping.keys():
                unpatched_field_names[index] = self._hit_fields_mapping[unpatched_field_name]
        return tuple(unpatched_field_names)

    def _unmap_hit_field_names(self, dtype_names):  # Unmaps the hit field names from the external convention to the internal defined one
        unpatched_field_names = list(dtype_names)
        for index, unpatched_field_name in enumerate(unpatched_field_names):
            if unpatched_field_name in self._hit_fields_mapping_inverse.keys():
                unpatched_field_names[index] = self._hit_fields_mapping_inverse[unpatched_field_name]
        return tuple(unpatched_field_names)

    def _map_cluster_field_names(self, dtype_names):  # Maps the cluster field names from the internal convention to the external defined one
        unpatched_field_names = list(dtype_names)
        for index, unpatched_field_name in enumerate(unpatched_field_names):
            if unpatched_field_name in self._cluster_fields_mapping.keys():
                unpatched_field_names[index] = self._cluster_fields_mapping[unpatched_field_name]
        return tuple(unpatched_field_names)

    def _unmap_cluster_field_names(self, dtype_names):  # Unmaps the cluster field names from the external convention to the internal defined one
        unpatched_field_names = list(dtype_names)
        for index, unpatched_field_name in enumerate(unpatched_field_names):
            if unpatched_field_name in self._cluster_fields_mapping_inverse.keys():
                unpatched_field_names[index] = self._cluster_fields_mapping_inverse[unpatched_field_name]
        return tuple(unpatched_field_names)

    def _check_struct_compatibility(self, hits):
        ''' Takes the hit array and checks if the important data fields have the same data type than the hit clustered array and that the field names are correct.'''
        for key, _ in self._cluster_hits_descr:
            if key in self._hit_fields_mapping_inverse:
                mapped_key = self._hit_fields_mapping_inverse[key]
            else:
                mapped_key = key
            # Only check hit fields that contain hit information
            if mapped_key in ['cluster_ID', 'is_seed', 'cluster_size', 'n_cluster']:
                continue
            if key not in hits.dtype.names:
                raise TypeError('Required hit field "%s" not found.' % key)
            if self._cluster_hits.dtype[mapped_key] != hits.dtype[key]:
                raise TypeError('The dtype for hit data field "%s" does not match. Got/expected: %s/%s.' % (key, hits.dtype[key], self._cluster_hits.dtype[mapped_key]))
        additional_hit_fields = set(hits.dtype.names) - set([key for key, val in self._cluster_hits_descr])
        if additional_hit_fields:
            logging.warning('Found additional hit fields: %s' % ", ".join(additional_hit_fields))


from pyrser import parsing
from pyrser import meta
from pyrser import error
from pyrser.directives import ignore

class EBNF(parsing.Parser):
    """
    Basic class for BNF DSL PARSING.

    A full parser for the BNF is provided by this class.
    We construct a tree to represents, thru functors, BNF semantics.
    """

    def get_rules(self) -> parsing.Node:
        """
        Parse the DSL and provide a dictionnaries of all resulting rules.
        Call by the MetaGrammar class.

        TODO: could be done in the rules property of parsing.BasicParser???
        """
        res = None
        try:
            res = self.eval_rule('bnf_dsl')
            if not res:
                # we fail to parse, but error is not set
                self.diagnostic.notify(
                    error.Severity.ERROR,
                    "Parse error in '%s' in EBNF bnf" % self._lastRule,
                    error.LocationInfo.from_maxstream(self._stream)
                )
                raise self.diagnostic
        except error.Diagnostic as d:
            d.notify(
                error.Severity.ERROR,
                "Parse error in '%s' in EBNF bnf" % self._lastRule
            )
            raise d
        return res

    @property
    def rules(self) -> dict:
        print("USE rules PROPERTY")
        return self._rules

    def __init__(self, content='', sname=None):
        """
        Define the DSL parser.
        """
        super().__init__(content, sname)
        self.set_rules({
            #
            # bnf_dsl = [ @ignore("C/C++") bnf_stmts ]
            # //todo: bnf_dsl = [ @ignore("C/C++") [bnf_stmts] eof ]
            #
            'bnf_dsl': parsing.Seq(
                # tree is not already construct but Directive need it
                # forward it thru a lambda
                parsing.Directive(ignore.Ignore(),
                                  [("C/C++", str)],
                                  lambda parser:
                                  self.__class__._rules['bnf_stmts'](parser)),
            ),

            #
            # bnf_stmts = [ [rule : r #add_rules(_, r) ]+ Base.eof ]
            # //todo: bnf_stmts = [ [rule : r #add_rules(_, r) ]+]
            #
            'bnf_stmts': parsing.Seq(
                parsing.Rep1N(parsing.Seq(
                    parsing.Capture("r", parsing.Rule('rule')),
                    parsing.Hook('add_rules', [("_", parsing.Node),
                                               ("r", parsing.Node)])
                )),
                parsing.Rule('Base.eof')
            ),

            # TODO: add directive hooks / change ns_name by def_rule
            #
            # rule = [ ns_name : rn '=' '[' alternatives : alts
            #                             #add_rule(_, rn, alts) ']' ]
            #
            'rule': parsing.Seq(
                parsing.Capture("rn", parsing.Rule('ns_name')),
                parsing.Alt(
                    parsing.Char("="),
                    parsing.Error("Expected '='")),
                parsing.Alt(
                    parsing.Char("["),
                    parsing.Error("Expected '['")),
                parsing.Capture("alts", parsing.Rule('alternatives')),
                parsing.Hook('add_rule', [("_", parsing.Node),
                                          ("rn", parsing.Node),
                                          ("alts", parsing.Node)]),
                parsing.Alt(
                    #parsing.Call(parsing.Parser.read_char, ']'),
                    parsing.Char(']'),
                    parsing.Error("Expected ']'"))
            ),

            #
            # alternatives =
            # [
            #       sequences : alt #add_alt(_, alt)
            #       ['|' sequences : alt #add_alt(_, alt) ]*
            # ]
            #
            'alternatives': parsing.Seq(
                parsing.Capture('alt', parsing.Rule('sequences')),
                parsing.Hook('add_alt', [("_", parsing.Node),
                                         ("alt", parsing.Node)]),
                parsing.Rep0N(
                    parsing.Seq(
                        parsing.Char('|'),
                        parsing.Capture('alt', parsing.Rule('sequences')),
                        parsing.Hook('add_alt',
                                     [("_", parsing.Node),
                                      ("alt", parsing.Node)])
                    )
                )
            ),

            #
            # sequences = [ [ sequence : cla #add_sequences(_, cla) ]+ ]
            #
            'sequences': parsing.Rep1N(
                parsing.Seq(
                    parsing.Capture('cla', parsing.Rule('sequence')),
                    parsing.Hook('add_sequences',
                                 [("_", parsing.Node),
                                  ("cla", parsing.Node)])
                )
            ),

            #
            # sequence = [
            #   [
            #     [ '~' | "!!" | '!' | "->" ]?: mod
            #     [ ns_name : rid #add_ruleclause_name(_, rid)
            #       | Base.string : txt #add_read_dqstring(_, txt)
            #       | Base.char : begin ".." Base.char : end
            #         #add_range(_, begin, end)
            #       | Base.char : c #add_read_sqstring(_, c)
            #       | '[' alternatives : subsequence ']'
            #         #add_subsequence(_, subsequence)
            #     ] #add_mod(_, mod)
            #     [ repeat : rpt #add_rpt(_, mod, rpt) ]?
            #   | hook : h #add_hook(_, h)
            #   | directive2 : d sequences : s #add_directive2(_, d, s)
            #   | directive : d sequences : s #add_directive(_, d, s)
            #   ]
            #     [
            #       ":>" Base.id : bind #add_bind(_, bind)
            #       | ':' Base.id : cpt #add_capture(_, cpt)
            #     ]?
            # ]
            #
            'sequence':
            parsing.Seq(
                parsing.Alt(
                    parsing.Seq(
                        parsing.Capture(
                            'mod',
                            parsing.RepOptional(
                                parsing.Alt(
                                    parsing.Char('~'),
                                    parsing.Text('!!'),
                                    parsing.Char('!'),
                                    parsing.Text('->')
                                )
                            )
                        ),
                        parsing.Alt(
                            parsing.Seq(
                                parsing.Capture(
                                    'rid',
                                    parsing.Rule('ns_name')
                                ),
                                parsing.Hook('add_ruleclause_name',
                                             [("_", parsing.Node),
                                              ("rid", parsing.Node)])
                            ),
                            parsing.Seq(
                                parsing.Capture('txt',
                                                parsing.Rule('Base.string')),
                                parsing.Hook('add_read_dqstring',
                                             [("_", parsing.Node),
                                              ("txt", parsing.Node)])
                            ),
                            parsing.Seq(
                                parsing.Capture('begin',
                                                parsing.Rule('Base.char')),
                                parsing.Text(".."),
                                parsing.Capture(
                                    'end',
                                    parsing.Rule('Base.char')
                                ),
                                parsing.Hook('add_range',
                                             [("_", parsing.Node),
                                              ("begin", parsing.Node),
                                              ("end", parsing.Node)])
                            ),
                            parsing.Seq(
                                parsing.Capture(
                                    'c',
                                    parsing.Rule('Base.char')
                                ),
                                parsing.Hook('add_read_sqstring',
                                             [("_", parsing.Node),
                                              ("c", parsing.Node)])
                            ),
                            parsing.Seq(
                                parsing.Char('['),
                                parsing.Capture(
                                    'subsequence',
                                    parsing.Alt(
                                        parsing.Rule('alternatives'),
                                        parsing.Error("Expected sequences"))),
                                parsing.Alt(
                                    parsing.Char(']'),
                                    parsing.Error("Expected ']'")),
                                parsing.Hook('add_subsequence',
                                             [("_", parsing.Node),
                                              ("subsequence", parsing.Node)]),
                            )
                        ),
                        parsing.Hook('add_mod', [("_", parsing.Node),
                                                 ("mod", parsing.Node)]),
                        parsing.RepOptional(
                            parsing.Seq(
                                parsing.Capture(
                                    'rpt',
                                    parsing.Rule('repeat')
                                ),
                                parsing.Hook('add_rpt',
                                             [("_", parsing.Node),
                                              ("mod", parsing.Node),
                                              ("rpt", parsing.Node)])
                            )
                        ),
                    ),
                    parsing.Seq(
                        parsing.Capture('h', parsing.Rule('hook')),
                        parsing.Hook('add_hook', [('_', parsing.Node),
                                                  ('h', parsing.Node)])
                    ),
                    parsing.Seq(
                        parsing.Capture('d', parsing.Rule('directive2')),
                        parsing.Capture('s', parsing.Rule('sequences')),
                        parsing.Hook('add_directive2', [('_', parsing.Node),
                                                        ('d', parsing.Node),
                                                        ('s', parsing.Node)])
                    ),
                    parsing.Seq(
                        parsing.Capture('d', parsing.Rule('directive')),
                        parsing.Capture('s', parsing.Rule('sequences')),
                        parsing.Hook('add_directive', [('_', parsing.Node),
                                                       ('d', parsing.Node),
                                                       ('s', parsing.Node)])
                    )
                ),
                parsing.RepOptional(
                    parsing.Alt(
                        parsing.Seq(
                            parsing.Text(':>'),
                            parsing.Capture(
                                'bind',
                                parsing.Rule('Base.id')),
                            parsing.Hook('add_bind',
                                         [('_', parsing.Node),
                                          ('bind', parsing.Node)])
                        ),
                        parsing.Seq(
                            parsing.Char(':'),
                            parsing.Capture(
                                'cpt',
                                parsing.Rule('Base.id')),
                            parsing.Hook('add_capture',
                                         [('_', parsing.Node),
                                          ('cpt', parsing.Node)])
                        )
                    )
                )
            ),

            # ns_name = [ [@ignore("null") [ Base.id ['.' Base.id]* ]]: rid ]
            #
            'ns_name': parsing.Capture(
                'rid',
                parsing.Scope(
                    parsing.Call(parsing.Parser.push_ignore,
                                 parsing.Parser.ignore_null),
                    parsing.Call(parsing.Parser.pop_ignore),
                    parsing.Seq(
                        parsing.Rule('Base.id'),
                        parsing.Rep0N(
                            parsing.Seq(
                                parsing.Char('.'),
                                parsing.Alt(
                                    parsing.Rule('Base.id'),
                                    parsing.Error(
                                        "Expected identifier after '.'"))
                            )
                        )
                    )
                )
            ),

            #
            # repeat = [ '?' #add_optional(_)
            #          | '*' #add_0N(_)
            #          | '+' #add_1N(_)
            # ]
            #
            'repeat': parsing.Alt(
                parsing.Seq(
                    parsing.Char('?'),
                    parsing.Hook('add_optional', [("_", parsing.Node)])
                ),
                parsing.Seq(
                    parsing.Char('*'),
                    parsing.Hook('add_0N', [("_", parsing.Node)])
                ),
                parsing.Seq(
                    parsing.Char('+'),
                    parsing.Hook('add_1N', [("_", parsing.Node)])
                ),
            ),

            #
            # hook = [ '#' ns_name : n #hook_name(_, n)
            #          ['(' [ param : p #hook_param(_, p)
            #              [',' param : p #hook_param(_, p)]*
            #           ]? ')']?
            # ]
            #
            'hook': parsing.Seq(
                parsing.Char('#'),
                parsing.Capture('n', parsing.Rule('ns_name')),
                parsing.Hook('hook_name',
                             [('_', parsing.Node), ('n', parsing.Node)]),
                parsing.RepOptional(
                    parsing.Seq(
                        parsing.Char('('),
                        parsing.RepOptional(
                            parsing.Seq(
                                parsing.Capture(
                                    'p',
                                    parsing.Rule('param'),
                                ),
                                parsing.Hook('hook_param', [('_', parsing.Node),
                                                            ('p', parsing.Node)]),
                                parsing.Rep0N(
                                    parsing.Seq(
                                        parsing.Char(','),
                                        parsing.Capture(
                                            'p',
                                            parsing.Alt(
                                                parsing.Rule('param'),
                                                parsing.Error("Expected parameter"))),
                                        parsing.Hook('hook_param',
                                                     [('_', parsing.Node),
                                                      ('p', parsing.Node)]))
                                )
                            )
                        ),
                        parsing.Alt(
                            parsing.Char(')'),
                            parsing.Error("Expected ')'"))
                    )
                ),
            ),

            #
            # directive2 = [ '$' ns_name : n #hook_name(_, n)
            #               ['(' param : p #hook_param(_, p)
            #                   [',' param : p #hook_param(_, p)]*
            #                ')']?
            # ]
            'directive2': parsing.Seq(
                parsing.Char('$'),
                parsing.Capture('n', parsing.Rule('ns_name')),
                parsing.Hook('hook_name', [('_', parsing.Node),
                                           ('n', parsing.Node)]),
                parsing.RepOptional(
                    parsing.Seq(
                        parsing.Char('('),
                        parsing.Capture(
                            'p',
                            parsing.Alt(
                                parsing.Rule('param'),
                                parsing.Error("Expected parameter"))),
                        parsing.Hook('hook_param',
                                     [('_', parsing.Node),
                                      ('p', parsing.Node)]),
                        parsing.Rep0N(
                            parsing.Seq(
                                parsing.Char(','),
                                parsing.Capture(
                                    'p',
                                    parsing.Alt(
                                        parsing.Rule('param'),
                                        parsing.Error("Expected parameter"))),
                                parsing.Hook('hook_param',
                                             [('_', parsing.Node),
                                              ('p', parsing.Node)]),
                            )
                        ),
                        parsing.Alt(
                            parsing.Char(')'),
                            parsing.Error("Expected ')'"))
                    )
                ),
            ),

            #
            # directive = [ '@' ns_name : n #hook_name(_, n)
            #               ['(' param : p #hook_param(_, p)
            #                   [',' param : p #hook_param(_, p)]*
            #                ')']?
            # ]
            'directive': parsing.Seq(
                parsing.Char('@'),
                parsing.Capture('n', parsing.Rule('ns_name')),
                parsing.Hook('hook_name', [('_', parsing.Node),
                                           ('n', parsing.Node)]),
                parsing.RepOptional(
                    parsing.Seq(
                        parsing.Char('('),
                        parsing.Capture(
                            'p',
                            parsing.Alt(
                                parsing.Rule('param'),
                                parsing.Error("Expected parameter"))),
                        parsing.Hook('hook_param',
                                     [('_', parsing.Node),
                                      ('p', parsing.Node)]),
                        parsing.Rep0N(
                            parsing.Seq(
                                parsing.Char(','),
                                parsing.Capture(
                                    'p',
                                    parsing.Alt(
                                        parsing.Rule('param'),
                                        parsing.Error("Expected parameter"))),
                                parsing.Hook('hook_param',
                                             [('_', parsing.Node),
                                              ('p', parsing.Node)]),
                            )
                        ),
                        parsing.Alt(
                            parsing.Char(')'),
                            parsing.Error("Expected ')'"))
                    )
                ),
            ),

            #
            # param = [ Base.num :n #param_num(_, n)
            #         | Base.string : s #param_str(_, s)
            #         | Base.char : c #param_char(_, c)
            #         | ns_name : i #param_id(_, i)
            # ]
            #
            'param': parsing.Alt(
                parsing.Seq(
                    parsing.Capture('n', parsing.Rule('Base.num')),
                    parsing.Hook('param_num', [('_', parsing.Node),
                                               ('n', parsing.Node)])
                ),
                parsing.Seq(
                    parsing.Capture('s', parsing.Rule('Base.string')),
                    parsing.Hook('param_str', [('_', parsing.Node),
                                               ('s', parsing.Node)])
                ),
                parsing.Seq(
                    parsing.Capture('c', parsing.Rule('Base.char')),
                    parsing.Hook('param_char', [('_', parsing.Node),
                                                ('c', parsing.Node)])
                ),
                parsing.Seq(
                    parsing.Capture('i', parsing.Rule('ns_name')),
                    parsing.Hook('param_id', [('_', parsing.Node),
                                              ('i', parsing.Node)])
                ),
            ),
        })

# Hooks part
# ----------
# All these functions are automatically added to class EBNF

@meta.hook(EBNF, "EBNF.add_mod")
def add_mod(self, seq, mod):
    """Create a tree.{Complement, LookAhead, Neg, Until}"""
    modstr = self.value(mod)
    if modstr == '~':
        seq.parser_tree = parsing.Complement(seq.parser_tree)
    elif modstr == '!!':
        seq.parser_tree = parsing.LookAhead(seq.parser_tree)
    elif modstr == '!':
        seq.parser_tree = parsing.Neg(seq.parser_tree)
    elif modstr == '->':
        seq.parser_tree = parsing.Until(seq.parser_tree)
    return True

@meta.hook(EBNF, "EBNF.add_ruleclause_name")
def add_ruleclause_name(self, ns_name, rid) -> bool:
    """Create a tree.Rule"""
    ns_name.parser_tree = parsing.Rule(self.value(rid))
    return True

@meta.hook(EBNF, "EBNF.add_rules")
def add_rules(self, bnf, r) -> bool:
    """Attach a parser tree to the dict of rules"""
    bnf[r.rulename] = r.parser_tree
    return True

@meta.hook(EBNF, "EBNF.add_rule")
def add_rule(self, rule, rn, alts) -> bool:
    """Add the rule name"""
    rule.rulename = self.value(rn)
    rule.parser_tree = alts.parser_tree
    return True

@meta.hook(EBNF, "EBNF.add_sequences")
def add_sequences(self, sequences, cla) -> bool:
    """Create a tree.Seq"""
    if not hasattr(sequences, 'parser_tree'):
        # forward sublevel of sequence as is
        sequences.parser_tree = cla.parser_tree
    else:
        oldnode = sequences
        if isinstance(oldnode.parser_tree, parsing.Seq):
            oldpt = list(oldnode.parser_tree.ptlist)
        else:
            oldpt = [oldnode.parser_tree]
        oldpt.append(cla.parser_tree)
        sequences.parser_tree = parsing.Seq(*tuple(oldpt))
    return True

@meta.hook(EBNF, "EBNF.add_alt")
def add_alt(self, alternatives, alt) -> bool:
    """Create a tree.Alt"""
    if not hasattr(alternatives, 'parser_tree'):
        # forward sublevel of alt as is
        if hasattr(alt, 'parser_tree'):
            alternatives.parser_tree = alt.parser_tree
        else:
            alternatives.parser_tree = alt
    else:
        oldnode = alternatives
        if isinstance(oldnode.parser_tree, parsing.Alt):
            oldpt = list(oldnode.parser_tree.ptlist)
        else:
            oldpt = [oldnode.parser_tree]
        oldpt.append(alt.parser_tree)
        alternatives.parser_tree = parsing.Alt(*tuple(oldpt))
    return True

@meta.hook(EBNF, "EBNF.add_read_sqstring")
def add_read_sqstring(self, sequence, s):
    """Add a read_char/read_text primitive from simple quote string"""
    v = self.value(s).strip("'")
    if len(v) > 1:
        sequence.parser_tree = parsing.Text(v)
        return True
    sequence.parser_tree = parsing.Char(v)
    return True

@meta.hook(EBNF, "EBNF.add_read_dqstring")
def add_read_dqstring(self, sequence, s):
    """Add a read_char/read_text primitive from a double quote string"""
    v = self.value(s).strip('"')
    if len(v) > 1:
        sequence.parser_tree = parsing.Text(v)
        return True
    sequence.parser_tree = parsing.Char(v)
    return True

@meta.hook(EBNF, "EBNF.add_range")
def add_range(self, sequence, begin, end):
    """Add a read_range primitive"""
    sequence.parser_tree = parsing.Range(self.value(begin).strip("'"),
                                         self.value(end).strip("'"))
    return True

@meta.hook(EBNF, "EBNF.add_rpt")
def add_rpt(self, sequence, mod, pt):
    """Add a repeater to the previous sequence"""
    modstr = self.value(mod)
    if modstr == '!!':
        # cursor on the REPEATER
        self._stream.restore_context()
        # log the error
        self.diagnostic.notify(
            error.Severity.ERROR,
            "Cannot repeat a lookahead rule",
            error.LocationInfo.from_stream(self._stream, is_error=True)
        )
        raise self.diagnostic
    if modstr == '!':
        # cursor on the REPEATER
        self._stream.restore_context()
        # log the error
        self.diagnostic.notify(
            error.Severity.ERROR,
            "Cannot repeat a negated rule",
            error.LocationInfo.from_stream(self._stream, is_error=True)
        )
        raise self.diagnostic
    oldnode = sequence
    sequence.parser_tree = pt.functor(oldnode.parser_tree)
    return True

@meta.hook(EBNF, "EBNF.add_capture")
def add_capture(self, sequence, cpt):
    """Create a tree.Capture"""
    cpt_value = self.value(cpt)
    sequence.parser_tree = parsing.Capture(cpt_value, sequence.parser_tree)
    return True

@meta.hook(EBNF, "EBNF.add_bind")
def add_bind(self, sequence, cpt):
    """Create a tree.Bind"""
    cpt_value = self.value(cpt)
    sequence.parser_tree = parsing.Bind(cpt_value, sequence.parser_tree)
    return True

@meta.hook(EBNF, "EBNF.add_subsequence")
def add_subsequence(self, sequence, subsequence):
    """Add a subsequence into a sequence"""
    sequence.parser_tree = subsequence.parser_tree
    return True

@meta.hook(EBNF, "EBNF.add_optional")
def add_optional(self, repeat):
    """Create a tree.RepOptional"""
    repeat.functor = parsing.RepOptional
    return True

@meta.hook(EBNF, "EBNF.add_0N")
def add_0N(self, repeat):
    """Create a tree.Rep0N"""
    repeat.functor = parsing.Rep0N
    return True

@meta.hook(EBNF, "EBNF.add_1N")
def add_1N(self, repeat):
    """Create a tree.Rep1N"""
    repeat.functor = parsing.Rep1N
    return True

@meta.hook(EBNF, "EBNF.add_hook")
def add_hook(self, sequence, h):
    """Create a tree.Hook"""
    sequence.parser_tree = parsing.Hook(h.name, h.listparam)
    return True

@meta.hook(EBNF, "EBNF.param_num")
def param_num(self, param, n):
    """Parse a int in parameter list"""
    param.pair = (int(self.value(n)), int)
    return True

@meta.hook(EBNF, "EBNF.param_str")
def param_str(self, param, s):
    """Parse a str in parameter list"""
    param.pair = (self.value(s).strip('"'), str)
    return True

@meta.hook(EBNF, "EBNF.param_char")
def param_char(self, param, c):
    """Parse a char in parameter list"""
    param.pair = (self.value(c).strip("'"), str)
    return True

@meta.hook(EBNF, "EBNF.param_id")
def param_id(self, param, i):
    """Parse a node name in parameter list"""
    param.pair = (self.value(i), parsing.Node)
    return True

@meta.hook(EBNF, "EBNF.hook_name")
def hook_name(self, hook, n):
    """Parse a hook name"""
    hook.name = self.value(n)
    hook.listparam = []
    return True

@meta.hook(EBNF, "EBNF.hook_param")
def hook_param(self, hook, p):
    """Parse a hook parameter"""
    hook.listparam.append(p.pair)
    return True

@meta.hook(EBNF, "EBNF.add_directive2")
def add_directive2(self, sequence, d, s):
    """Add a directive in the sequence"""
    sequence.parser_tree = parsing.Directive2(
        d.name,
        d.listparam,
        s.parser_tree
    )
    return True

@meta.hook(EBNF, "EBNF.add_directive")
def add_directive(self, sequence, d, s):
    """Add a directive in the sequence"""
    if d.name in meta._directives:
        the_class = meta._directives[d.name]
        sequence.parser_tree = parsing.Directive(the_class(), d.listparam,
                                                 s.parser_tree)
    elif d.name in meta._decorators:
        the_class = meta._decorators[d.name]
        sequence.parser_tree = parsing.Decorator(the_class, d.listparam,
                                                 s.parser_tree)
    else:
        raise TypeError("Unkown directive or decorator %s" % d.name)
    return True


# This pass is for debug only
from pyrser import meta, fmt
from pyrser.parsing import node
import weakref

scalar = {'bool': bool, 'int': int, 'float': float, 'str': str}
composed = {'list': list, 'dict': dict, 'set': set}

@meta.add_method(node.Node)
def to_yml(self):
    """
    Allow to get the YML string representation of a Node.::

        from pyrser.passes import to_yml

        t = Node()
        ...
        print(str(t.to_yml()))
    """
    pp = fmt.tab([])
    to_yml_item(self, pp.lsdata, "")
    return str(pp)

def yml_attr(k, v):
    return fmt.sep("=", [k, v])

def to_yml_item(item, pp, name):
    global scalar
    refcount = weakref.getweakrefcount(item)
    if refcount > 0:
        name += " &" + str(id(item))
    if type(item).__name__ in scalar:
        tag = fmt.end(
            '\n',
            fmt.sep(
                "",
                [
                    name,
                    " ",
                    yml_attr(
                        type(item).__name__,
                        repr(item)
                    )
                ]
            )
        )
        pp.append(tag)
        return
    if isinstance(item, weakref.ref):
        name += " *" + str(id(item()))
        tag = fmt.end('\n', fmt.sep("", [name, " ", repr(item)]))
        pp.append(tag)
        return
    if isinstance(item, bytes) or isinstance(item, bytearray):
        inner = fmt.tab([])
        tag = fmt.block(name + " " + str(yml_attr('type', 'bytes')) + ':\n',
                        '----' + name + '----\n', inner)
        inner.lsdata.append(fmt.sep(" ", []))
        bindata = inner.lsdata[-1].lsdata
        i = 0
        for b in item:
            bindata.append("%02X" % b)
            i += 1
            if i > 16:
                bindata.append("\n")
                i = 0
        bindata.append("\n")
        pp.append(tag)
        return
    if isinstance(item, object) and hasattr(item, '__dict__'):
        inner = fmt.tab([])
        tag = fmt.block(name + " " +
                        str(yml_attr('type', item.__class__.__name__))
                        + ':\n',
                        '', inner)
        for attr in sorted(vars(item)):
            to_yml_item(getattr(item, attr), inner.lsdata, attr)
        if len(vars(item)) == 0:
            inner.lsdata.append("\n")
        pp.append(tag)
        return
    if isinstance(item, list):
        inner = fmt.tab([])
        tag = fmt.block(
            name + " " + str(yml_attr('type', 'list')) + ':\n',
            '',
            inner
        )
        i = 0
        for subitem in item:
            idxname = str(fmt.sep(" ", ['[', i, ']']))
            to_yml_item(subitem, inner.lsdata, idxname)
            i += 1
        if len(item) == 0:
            inner.lsdata.append("\n")
        pp.append(tag)
        return
    if isinstance(item, tuple):
        inner = fmt.tab([])
        tag = fmt.block(name + " " + str(yml_attr('type', 'tuple')) + ':\n',
                        '', inner)
        i = 0
        for subitem in item:
            idxname = str(fmt.sep(" ", ["[", i, "]"]))
            to_yml_item(subitem, inner.lsdata, idxname)
            i += 1
        if len(item) == 0:
            inner.lsdata.append("\n")
        pp.append(tag)
        return
    if isinstance(item, dict):
        inner = fmt.tab([])
        tag = fmt.block(name + " " + str(yml_attr('type', 'dict')) + ':\n',
                        '', inner)
        for k in sorted(item.keys()):
            idxname = str(fmt.sep(" ", ["[", repr(k), "]"]))
            to_yml_item(item[k], inner.lsdata, idxname)
        if len(item.keys()) == 0:
            inner.lsdata.append("\n")
        pp.append(tag)
        return
    if isinstance(item, set):
        inner = fmt.tab([])
        tag = fmt.block(name + " " + str(yml_attr('type', 'set')) + ':\n',
                        '', inner)
        for subitem in sorted(item):
            idxname = str(fmt.sep(", ", [repr(subitem)]))
            to_yml_item(subitem, inner.lsdata, idxname)
        if len(item) == 0:
            inner.lsdata.append("\n")
        pp.append(tag)
        return
    if item is None:
        tag = fmt.end('\n', [name])
        pp.append(tag)
        return


from pyrser import meta, parsing

@meta.rule(parsing.Parser, "Base.ignore_cxx")
def ignore_cxx(self) -> bool:
    """Consume comments and whitespace characters."""
    self._stream.save_context()
    while not self.read_eof():
        idxref = self._stream.index
        if 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()
        if self.peek_text("//"):
            while not self.read_eof() and not self.peek_char("\n"):
                self._stream.incpos()
            if not self.read_char("\n") and self.read_eof():
                return self._stream.validate_context()
        if self.peek_text("/*"):
            while not self.read_eof() and not self.peek_text("*/"):
                self._stream.incpos()
            if not self.read_text("*/") and self.read_eof():
                return self._stream.restore_context()
        if idxref == self._stream.index:
            break
    return self._stream.validate_context()

@meta.directive("ignore")
class Ignore(parsing.DirectiveWrapper):
    def begin(self, parser, convention: str):
        if convention == "null":
            parser.push_ignore(parsing.Parser.ignore_null)
        if convention == "C/C++":
            parser.push_ignore(parsing.Parser.ignore_cxx)
        if convention == "blanks":
            parser.push_ignore(parsing.Parser.ignore_blanks)
        return True

    def end(self, parser, convention: str):
        parser.pop_ignore()
        return True


"""
This module implement the states classes for tree automata.
"""

from pyrser import fmt
import os
import weakref
import pipes
import tempfile

# just a forward declaration -- for annotation
class State:
    pass

class StateRegister():
    """
    All State belongs to a global register that manage named events.
    """
    def __init__(self, named_events: dict={}, label=None):
        # default state
        self.__default = None
        # all state belonging to this register
        self.states = dict()
        # label store the string representation of the PSL expression
        # that is responsible of all these states
        self.label = label
        # handle events
        self.named_events = named_events

    def cleanAll(self):
        """
        Set all events to empty.
        """
        self.named_events.clear()

    def set_default_state(self, s: State):
        if id(s) not in self.states:
            raise ValueError("%d not in StateRegister" % id(s))
        self.__default = s

    @property
    def default(self) -> State:
        return self.__default

    def add_state(self, s: State):
        """
        all state in the register have a uid
        """
        ids = id(s)
        uid = len(self.states)
        if ids not in self.states:
            self.states[ids] = (uid, s)

    def get_uid(self, s: State) -> int:
        if id(s) not in self.states:
            raise ValueError("%d not in StateRegister" % id(s))
        return self.states[id(s)][0]

    def __contains__(self, s: State) -> bool:
        sts = set(list(self.states.keys()))
        return id(s) in sts

    def to_dot(self) -> str:
        """
        Provide a '.dot' representation of all State in the register.
        """
        txt = ""
        txt += "digraph S%d {\n" % id(self)
        if self.label is not None:
            txt += '\tlabel="%s";\n' % (self.label + '\l').replace('\n', '\l')
        txt += "\trankdir=LR;\n"
        #txt += '\tlabelloc="t";\n'
        txt += '\tgraph [labeljust=l, labelloc=t, nojustify=true];\n'
        txt += "\tesep=1;\n"
        txt += '\tranksep="equally";\n'
        txt += "\tnode [shape = circle];\n"
        txt += "\tsplines = ortho;\n"
        for s in self.states.values():
            txt += s[1].to_dot()
        txt += "}\n"
        return txt

    def to_dot_file(self, fname: str):
        """
        write a '.dot' file.
        """
        with open(fname, 'w') as f:
            f.write(self.to_dot())

    def to_png_file(self, fname: str):
        """
        write a '.png' file.
        """
        cmd = pipes.Template()
        cmd.append('dot -Tpng > %s' % fname, '-.')
        with cmd.open('pipefile', 'w') as f:
            f.write(self.to_dot())

    def to_fmt(self) -> str:
        """
        Provide a useful representation of the register.
        """
        infos = fmt.end(";\n", [])
        s = fmt.sep(', ', [])
        for ids in sorted(self.states.keys()):
            s.lsdata.append(str(ids))
        infos.lsdata.append(fmt.block('(', ')', [s]))
        infos.lsdata.append("events:" + repr(self.events))
        infos.lsdata.append(
            "named_events:" + repr(list(self.named_events.keys()))
        )
        infos.lsdata.append("uid_events:" + repr(list(self.uid_events.keys())))
        return infos

    def __repr__(self) -> str:
        return str(self.to_fmt())

class EventExpr:
    """
    Ast Node for predicate Expression.
    """
    def checkEvent(self, sr: StateRegister, collect: dict) -> bool:
        return False

    def clean(self, sr: StateRegister, updown: bool):
        pass

class EventAlt(EventExpr):
    """
    Ast Node for a alternative predicate expression.
    """
    def __init__(self, alt: list):
        self.alt = alt

    def checkEvent(self, sr: StateRegister, collect: dict) -> bool:
        res = False
        for a in self.alt:
            b = a.checkEvent(sr, collect)
            res |= b
        return res

    def clean(self, sr: StateRegister, updown: bool) -> bool:
        for a in self.alt:
            a.clean(sr, updown)

    def to_fmt(self) -> fmt.indentable:
        res = fmt.sep(' | ', [])
        for a in self.alt:
            res.lsdata.append(a.to_fmt())
        return res

    def __repr__(self) -> str:
        return str(self.to_fmt())

class EventSeq(EventExpr):
    """
    Ast Node for a sequence predicate expression.
    """
    def __init__(self, seq: list):
        self.seq = seq

    def checkEvent(self, sr: StateRegister, collect: dict) -> bool:
        res = True
        for s in self.seq:
            b = s.checkEvent(sr, collect)
            res &= b
        return res

    def clean(self, sr: StateRegister, updown: bool) -> bool:
        for s in self.seq:
            s.clean(sr, updown)

    def to_fmt(self) -> fmt.indentable:
        res = fmt.sep(' & ', [])
        for s in self.seq:
            res.lsdata.append(s.to_fmt())
        return res

    def __repr__(self) -> str:
        return str(self.to_fmt())

class EventNot(EventExpr):
    """
    Ast Node for a not predicate expression.
    """
    def __init__(self, subexpr: EventExpr):
        self.subexpr = subexpr

    def checkEvent(self, sr: StateRegister, collect: dict) -> bool:
        c2 = {}
        r = not self.subexpr.checkEvent(sr, c2)
        return r

    def clean(self, sr: StateRegister, updown: bool) -> bool:
        self.subexpr.clean(sr, not updown)

    def to_fmt(self) -> fmt.indentable:
        res = fmt.block('!', '', [self.subexpr.to_fmt()])
        return res

    def __repr__(self) -> str:
        return str(self.to_fmt())

class EventParen(EventExpr):
    """
    Ast Node for a parenthesis predicate expression.
    """
    def __init__(self, subexpr: EventExpr):
        self.subexpr = subexpr

    def checkEvent(self, sr: StateRegister, collect: dict) -> bool:
        return self.subexpr.checkEvent(sr, collect)

    def clean(self, sr: StateRegister, updown: bool) -> bool:
        self.subexpr.clean(sr, updown)

    def to_fmt(self) -> fmt.indentable:
        res = fmt.block('(', ')', [self.subexpr.to_fmt()])
        return res

    def __repr__(self) -> str:
        return str(self.to_fmt())

class EventNamed(EventExpr):
    """
    Ast Node for a named predicate expression.
    """
    def __init__(self, name: str):
        self.name = name

    def checkEvent(self, sr: StateRegister, collect: dict) -> bool:
        if self.name in sr.named_events:
            collect[self.name] = True
            return True
        return False

    def clean(self, sr: StateRegister, updown: bool) -> bool:
        self.subexpr.clean(sr, updown)

    def to_fmt(self) -> fmt.indentable:
        return self.name

    def __repr__(self) -> str:
        return str(self.to_fmt())

class State:
    """
    Base class for a State and his transitions.
    """
    def __init__(self, sr: StateRegister):
        # register this state
        sr.add_state(self)
        self.state_register = sr
        # for predicates
        self.state_event = None
        self.events_expr = list()
        self.precond = None
        # for matching
        self.attrs = dict()
        self.indices = dict()
        self.keys = dict()
        self.values = dict()
        # wildcards
        self.wild_value = False
        self.wild_indice = False
        #...
        self.minsubelmt = 0
        # for exact type
        self.types_list = list()
        self.types = dict()
        # for iskind of type
        self.ktypes_list = list()
        self.ktypes = dict()
        # for defaults
        self.default_event = None
        self.default_hook = None
        self.default = None

    def nextstate(self, newstate, treenode=None, user_data=None):
        """
        Manage transition of state.
        """
        if newstate is None:
            return self
        if isinstance(newstate, State) and id(newstate) != id(self):
            return newstate
        elif isinstance(newstate, StateEvent):
            self.state_register.named_events[newstate.name] = True
            return newstate.st
        elif isinstance(newstate, StatePrecond):
            return newstate.st
        elif isinstance(newstate, StateHook):
            # final API using PSL
            newstate.call(treenode, user_data)
            return newstate.st
        return self

    ### Custom Named Events
    ## we could write boolean expression of events...

    def checkEventExpr(self) -> State:
        # check all free events Expressions...
        for e in self.events_expr:
            collect = {}
            if e.expr.checkEvent(self.state_register, collect):
                # TODO: clean or not event
                if e.clean_event:
                    for k in collect.keys():
                        del self.state_register.named_events[k]
                return self.nextstate(e)
        return self

    def matchEvent(self, n, state: State):
        # state Result Event
        se = StateEvent(n, state)
        self.default_event = se

    def matchEventExpr(self, e: EventExpr, state: State, clean_event: bool):
        # match Event Expression
        se = StatePrecond(e, state, clean_event)
        self.events_expr.append(se)

    ### ATTR

    def checkAttr(self, a) -> State:
        if a in self.attrs:
            return self.nextstate(self.attrs[a])
        return self

    def matchAttr(self, a, state: State):
        self.attrs[a] = state

    ### INDICE

    def checkIndice(self, i) -> State:
        if self.wild_indice:
            print("WILD INDEX")
            return self.nextstate(self.indices['*'])
        elif i in self.indices:
            return self.nextstate(self.indices[i])
        return self

    def matchIndice(self, i, state: State):
        if i is None:
            self.wild_indice = True
            self.indices['*'] = state
        else:
            self.indices[i] = state

    ### KEY

    def checkKey(self, k) -> State:
        if k in self.keys:
            return self.nextstate(self.keys[k])
        return self

    def matchKey(self, k, state: State):
        self.keys[k] = state

    ### TYPE (exact type)

    def checkKindOfType(self, t) -> State:
        for it in self.ktypes_list:
            if issubclass(t, it):
                return self.nextstate(self.ktypes[it.__name__])
        return self

    def checkType(self, t) -> State:
        for k in self.types.keys():
            if t.__name__ == k:
                return self.nextstate(self.types[k])
        return self

    def matchKindType(self, t, state: State):
        if type(t) is not type:
            raise ValueError("parameter 't' is a type")
        # TODO: check if an subclass object is the list...
        self.ktypes_list.append(t)
        self.ktypes[t.__name__] = state

    def matchType(self, t, state: State):
        if type(t) is not type:
            raise ValueError("parameter 't' is a type")
        # TODO: check if an subclass object is the list...
        self.types_list.append(t)
        self.types[t.__name__] = state

    ### VALUE

    def checkValue(self, v) -> State:
        """the str() of Values are stored internally for convenience"""
        if self.wild_value:
            return self.nextstate(self.values['*'])
        elif str(v) in self.values:
            return self.nextstate(self.values[str(v)])
        return self

    def matchValue(self, v, state: State):
        if v is None:
            self.wild_value = state
            self.values['*'] = state
        else:
            self.values[str(v)] = state

    ### HOOK

    def matchHook(self, call, state: State):
        self.default_hook = StateHook(call, state)

    ### DEFAULT

    def doResultHook(self, treenode=None, user_data=None) -> State:
        return self.nextstate(self.default_hook, treenode, user_data)

    def doResultEvent(self) -> State:
        return self.nextstate(self.default_event)

    def doDefault(self) -> State:
        return self.nextstate(self.default)

    def matchDefault(self, state: State):
        self.default = state

    # internal transition
    def cleanAll(self):
        self.state_register.cleanAll()

    def _str_state(self, s) -> str:
        if isinstance(s, State):
            return str(id(s))
        return repr(s)

    def _dot_state(self, s) -> str:
        return 'S' + str(self.state_register.get_uid(s))

    def _dot_relation(self, s, label) -> str:
        txt = ""
        dst = ""
        if isinstance(s, StateEvent):
            dst = "ent" + str(id(s))
            txt += ("\t"
                    + dst
                    + ' [shape=box xlabel="<'
                    + s.name
                    + '>"];\n'
                    )
            txt += ("\t"
                    + dst
                    + ' -> '
                    + self._dot_state(s.st)
                    + ';\n'
                    )
        elif isinstance(s, StatePrecond):
            dst = "ent" + str(id(s))
            txt += ("\t"
                    + dst
                    + ' [shape=box xlabel=" ? ('
                    + s.txtevent
                    + ')"];\n'
                    )
            txt += ("\t"
                    + dst
                    + ' -> '
                    + self._dot_state(s.st)
                    + ';\n'
                    )
        elif isinstance(s, StateHook):
            dst = "ent" + str(id(s))
            txt += ("\t"
                    + dst
                    + ' [shape=box xlabel="'
                    + repr(s.call)
                    + '"];\n'
                    )
            txt += ("\t"
                    + dst
                    + ' -> '
                    + self._dot_state(s.st)
                    + '[xlabel = "return"];\n'
                    )
        else:
            dst = self._dot_state(s)
        txt += ("\t"
                + self._dot_state(self)
                + ' -> '
                + dst
                + ' [xlabel = "'
                + label
                + '" ];\n'
                )
        return txt

    def to_dot(self) -> str:
        txt = ""
        if len(self.events_expr) > 0:
            for e in self.events_expr:
                txt += self._dot_relation(e, '')
        if len(self.attrs) > 0:
            for k in sorted(self.attrs.keys()):
                event = self.attrs[k]
                txt += self._dot_relation(event, '.' + k)
        if len(self.indices) > 0:
            for k in sorted(self.indices.keys()):
                event = self.indices[k]
                txt += self._dot_relation(event, '[' + repr(k) + ']')
        if len(self.keys) > 0:
            for k in sorted(self.keys.keys()):
                event = self.keys[k]
                txt += self._dot_relation(event, '{' + repr(k) + '}')
        if len(self.types) > 0:
            for k in sorted(self.types.keys()):
                event = self.types[k]
                txt += self._dot_relation(event, k + '(...)')
        if len(self.values) > 0:
            for k in sorted(self.values.keys()):
                event = self.values[k]
                txt += self._dot_relation(event, '=' + k)
        if self.default_hook is not None:
            txt += self._dot_relation(self.default_hook, 'hook')
        if self.default_event is not None:
            txt += self._dot_relation(self.default_event, 'event')
        if (self.default is not None
            and self.default is not self.state_register.default
        ):
            txt += self._dot_relation(self.default, '...')
        elif self.default is None:
            nodename = self._dot_state(self)
            txt += "\t" + nodename + '[label="' + nodename + '*"];\n'
        return txt

    def __repr__(self):
        # to expand for dbg
        txt = ""
        if len(self.events_expr) > 0:
            txt += "EXPR EVENT:\n"
            for e in self.events_expr:
                txt += repr(e) + '\n'
            txt += '-----\n'
        if len(self.attrs) > 0:
            txt += "ATTR EVENT:\n"
            for k in sorted(self.attrs.keys()):
                txt += repr(k) + ': ' + self._str_state(self.attrs[k]) + '\n'
            txt += '-----\n'
        if len(self.indices) > 0:
            txt += "INDICE EVENT:\n"
            for k in sorted(self.indices.keys()):
                txt += repr(k) + ': ' + self._str_state(self.indices[k]) + '\n'
            txt += '-----\n'
        if len(self.keys) > 0:
            txt += "KEY EVENT:\n"
            for k in sorted(self.keys.keys()):
                txt += repr(k) + ': ' + self._str_state(self.keys[k]) + '\n'
            txt += '-----\n'
        if len(self.types) > 0:
            txt += "TYPE EVENT:\n"
            for k in sorted(self.types.keys()):
                txt += repr(k) + ': ' + self._str_state(self.types[k]) + '\n'
            txt += '-----\n'
        if len(self.values) > 0:
            txt += "VALUE EVENT:\n"
            for k in sorted(self.values.keys()):
                txt += repr(k) + ': ' + self._str_state(self.values[k]) + '\n'
            txt += '-----\n'
        if self.default_hook is not None:
            txt += self._dot_relation(self.default_hook, 'hook')
        if self.default_event is not None:
            txt += self._dot_relation(self.default_event, 'event')
        if (self.default is not None
            and self.default is not self.state_register.default
        ):
            txt += "DEFAULT: %s\n" % self._str_state(self.default)
        return txt

class StateHook:
    """
    State for handling Resulting Hooks.
    """
    def __init__(self, call: callable, st: State):
        if not callable(call):
            raise ValueError("'call' argument must be a callable")
        self.call = call
        self.st = st

class StateEvent:
    """
    State for handling Resulting Event.
    """
    def __init__(self, n: str, st: State):
        self.name = n
        self.st = st

class StatePrecond:
    """
    State for handling Precondition using predicates.
    """
    def __init__(self, e: EventExpr, st: State, clean_event: bool):
        self.expr = e
        self.txtevent = repr(e)
        self.st = st
        self.clean_event = clean_event

    def to_fmt(self) -> fmt.indentable:
        res = fmt.sep(' : ', [])
        res.lsdata.append(self.txtevent)
        res.lsdata.append(id(self.st))
        res.lsdata.append(self.clean_event)
        return res

    def __repr__(self) -> str:
        return str(self.to_fmt())

class CaptureContext:
    pass

class CaptureContext:
    kind_of_node = {'node': 1, 'attr': 2, 'indice': 3, 'key': 4}
    map_intern = {2: 'attrs', 3: 'indices', 4: 'keys'}

    def __init__(self):
        # a ref
        self.parent = None
        # a ref
        self.node = None
        # one of kind_of_node
        self.kind = None
        # value of attrs/indices/keys
        self.value = None

    def make_from_unorder_list(ls: [CaptureContext]) -> CaptureContext:
        self = CaptureContext()
        for it in ls:
            self.add_sub(it)
        return self

    def add_sub(self, it):
        if it.kind in CaptureContext.map_intern:
            ck = CaptureContext.map_intern[it.kind]
            if not hasattr(self, ck):
                setattr(self, ck, [])
            getattr(self, ck).append(it)
        return self

    ###
    def get(self):
        if self.kind == CaptureContext.kind_of_node['attr']:
            return getattr(self.node(), self.value)
        if (self.kind == CaptureContext.kind_of_node['indice']
            or self.kind == CaptureContext.kind_of_node['key']
        ):
            return self.node()[self.value]
        return self.node()

    def set(self, v):
        if self.kind == CaptureContext.kind_of_node['attr']:
            setattr(self.node(), self.value, v)
            return
        if (self.kind == CaptureContext.kind_of_node['indice']
            or self.kind == CaptureContext.kind_of_node['key']
        ):
            self.node()[self.value] = v
            return
        if hasattr(self, 'attrs'):
            delattr(self, 'attrs')
        if hasattr(self, 'indices'):
            delattr(self, 'indices')
        if hasattr(self, 'keys'):
            delattr(self, 'keys')
        self.node().set(v)

    ###
    def is_attr(self, n, a) -> CaptureContext:
        self.kind = CaptureContext.kind_of_node['attr']
        self.value = a
        self.node = weakref.ref(n)
        return self

    def is_indice(self, n, a) -> CaptureContext:
        self.kind = CaptureContext.kind_of_node['indice']
        self.value = a
        if type(n) != list:
            self.node = weakref.ref(n)
        else:
            raise ValueError("PSL don't work on builtin list")
        return self

    def is_key(self, n, a) -> CaptureContext:
        self.kind = CaptureContext.kind_of_node['key']
        self.value = a
        if type(n) != dict:
            self.node = weakref.ref(n)
        else:
            raise ValueError("PSL don't work on builtin dict")
        return self

    def is_node(self, n, a, p=None) -> CaptureContext:
        #print("SET THE NODE TO %s %s" % (type(n), a))
        self.kind = CaptureContext.kind_of_node['node']
        self.value = a
        self.node = weakref.ref(n)
        self.parent = p
        return self
    ####

    def to_fmt(self) -> fmt.indentable:
        res = None
        showlist = {
            'attr': {
                "res": fmt.sep('', ['.']),
                "inblock": fmt.sep('=', [self.value])
            },
            'indice': {
                "res": fmt.block('[', ']', []),
                "inblock": fmt.sep(': ', [repr(self.value)])
            },
            'key': {
                "res": fmt.block('{', '}', []),
                "inblock": fmt.sep(': ', [repr(self.value)])
            }
            }
        for k in sorted(showlist.keys()):
            v = showlist[k]
            if self.kind == CaptureContext.kind_of_node[k]:
                res = v["res"]
                inblock = v["inblock"]
                if self.node is not None:
                    if hasattr(self.node, 'to_fmt'):
                        inblock.lsdata.append(self.node.to_fmt())
                    elif type(self.node) is weakref.ReferenceType:
                        inblock.lsdata.append(repr(self.get()))
                res.lsdata.append(inblock)
        ######
        if self.kind == CaptureContext.kind_of_node['node']:
            res = fmt.sep('', [self.value])
            elmts = fmt.sep(',\n', [])
            for sk in sorted(CaptureContext.map_intern.values()):
                subelmt = None
                if hasattr(self, sk):
                    subelmt = getattr(self, sk)
                if subelmt is not None:
                    for sube in subelmt:
                        elmts.lsdata.append(sube.to_fmt())
            se = fmt.tab(fmt.block('(\n', '\n)', elmts))
            res.lsdata.append(se)
        return res

    def dbg_str(self) -> str:
        txt = fmt.sep('\n', [])
        txt.lsdata.append("id parent = %d" % id(self.parent))
        n = self.node()
        txt.lsdata.append("node = %s - %s" % (type(n), str(n)))
        d = CaptureContext.kind_of_node
        txt.lsdata.append("kind = %s" % list(d.keys())[list(d.values()).index(self.kind)])
        txt.lsdata.append("value = %s" % repr(self.value))
        return str(txt)

    def __repr__(self) -> str:
        return str(self.to_fmt())

class LivingState:
    """ State instances in a StateRegister represents all the tree automata
    but only LivingState that reference a State instance are used
    during the walk.
    """
    def __init__(self, s: State):
        self.alive = False
        self.have_finish = False
        self.thestate = weakref.ref(s)
        # list of CaptureContext
        # TODO: MUST be surround by another
        # when the living state return to first state
        self.nodectx = CaptureContext()
        # for attrs,indices,keys collect id of parents
        self.subelmtctx = []
        self.subelmt = []

    #TODO: forward call to internal state
    def checkEventExpr(self):
        s = self.thestate().checkEventExpr()
        if id(s) != id(self.thestate()):
            self.thestate = weakref.ref(s)
            self.alive = True

    def checkAttr(self, a, thenode=None):
        s = self.thestate().checkAttr(a)
        if id(s) != id(self.thestate()):
            ctx = CaptureContext()
            ctx.is_attr(thenode, a)
            self.subelmtctx.append(ctx)
            self.subelmt.append(id(thenode))
            #print("ADDSUBELMT %s in %d" % (self.subelmtctx, id(self)))
            self.thestate = weakref.ref(s)
            self.alive = True

    def checkIndice(self, i, thenode=None):
        s = self.thestate().checkIndice(i)
        if id(s) != id(self.thestate()):
            ctx = CaptureContext()
            ctx.is_indice(thenode, i)
            self.subelmtctx.append(ctx)
            self.subelmt.append(id(thenode))
            self.thestate = weakref.ref(s)
            self.alive = True

    def checkKey(self, k, thenode=None):
        s = self.thestate().checkKey(k)
        if id(s) != id(self.thestate()):
            ctx = CaptureContext()
            ctx.is_key(thenode, k)
            self.subelmtctx.append(ctx)
            self.subelmt.append(id(thenode))
            self.thestate = weakref.ref(s)
            self.alive = True

    def checkType(self, t, thenode=None, parent=None):
        statetype = self.thestate()
        # count the number of subelmt that belong to this type
        nsubelm = 0
        toremove = []
        l = len(self.subelmt)
        for idx, subelmtid in zip(range(l), self.subelmt):
            if id(thenode) == subelmtid:
                toremove.append(idx)
                nsubelm += 1
        # check the num min of subelmt associate with this type
        if nsubelm >= statetype.minsubelmt:
            s = self.thestate().checkType(t)
            if id(s) == id(self.thestate()):
                s = self.thestate().checkKindOfType(t)
            if id(s) != id(self.thestate()):
                #print("ADD THENODE: %s" % thenode)
                subnodes = []
                for idx in toremove:
                    subnodes.append(self.subelmtctx[idx])
                ctx = CaptureContext.make_from_unorder_list(subnodes)
                # TODO: must extract the correct sublist
                # to surround by the node
                #self.nodectx.is_node(thenode, t.__name__, parent)
                ctx.is_node(thenode, t.__name__, parent)
                self.nodectx = ctx
                self.subelmtctx.append(ctx)
                self.subelmt.append(id(parent))
                # TODO: think a better way to store
                # in // old ref for ancestors...siblings...
                self.thestate = weakref.ref(s)
                self.alive = True
                for idx in reversed(toremove):
                    self.subelmt.pop(idx)
                    #print("remove %d in %d" % (idx, id(self)))
                    self.subelmtctx.pop(idx)

    def checkValue(self, v):
        s = self.thestate().checkValue(v)
        if id(s) != id(self.thestate()):
            self.thestate = weakref.ref(s)
            self.alive = True

    def doResultHook(self, thenode=None, user_data=None, parent=None):
        #TODO: must handle parent
        s = self.thestate().doResultHook(self.nodectx, user_data)
        if id(s) != id(self.thestate()):
            #print("RESET MATCHED NODES HOOK")
            #self.matched_nodes = []
            #self.old_ref = []
            self.thestate = weakref.ref(s)
            self.alive = True

    def doResultEvent(self):
        # TODO: event stored in the LivingState
        olds = self.thestate()
        s = self.thestate().doResultEvent()
        if id(s) != id(self.thestate()):
            #print("RESET MATCHED NODES EV")
            ##self.matched_nodes = []
            ##self.old_ref = []
            self.thestate = weakref.ref(s)
            self.alive = True

    def doDefault(self):
        # TODO: ??? kill livingState in LivingContext??
        s = self.thestate().doDefault()
        if id(s) != id(self.thestate()):
            #print("RESET MATCHED NODES DEFAULT")
            #self.matched_nodes = []
            #self.old_ref = []
            self.thestate = weakref.ref(s)
            self.alive = True

class LivingContext:
    """ Create & Destroy Living State
    during tree walking for a current StateRegister.
    """
    def __init__(self):
        self.mblock = []
        self.nsr = []
        self.ls = []
        self.named_events = {}

    def add_match_block(self, m):
        self.mblock.append(m)

    def build_automata(self):
        for b in self.mblock:
            tree = list()
            nsr = StateRegister(named_events=self.named_events)
            b.build_state_tree(tree, nsr)
            nsr.label = repr(b)
            self.nsr.append(nsr)
        self.ls = []
        self.init_all()

    def init_all(self):
        for nsr in self.nsr:
            self.ls.append((nsr.default, LivingState(nsr.default)))

    def is_in_stable_state(self) -> bool:
        return len(self.nsr) == len(self.ls)

    #TODO: forward call to internal state
    def checkEventExpr(self):
        for ls in self.ls:
            ls[1].checkEventExpr()

    def checkAttr(self, a, thenode=None):
        for ls in self.ls:
            ls[1].checkAttr(a, thenode)

    def checkIndice(self, i, thenode=None):
        # TODO: must store indice and restore state...
        # if wildcard, type are not strict...
        # so set an event after parsing all indices
        fork_state = []
        l = len(self.ls)
        for idx, ls in zip(range(l), self.ls):
            store_indice = False
            if ls[1].thestate().wild_indice:
                store_indice = True
            ls[1].checkIndice(i, thenode)

    def checkKey(self, k, thenode=None):
        for ls in self.ls:
            ls[1].checkKey(k, thenode)

    def checkType(self, t, thenode=None, parent=None):
        for ls in self.ls:
            ls[1].checkType(t, thenode, parent)

    def checkValue(self, v):
        #print("check value")
        for ls in self.ls:
            ls[1].checkValue(v)

    def doResultHook(self, thenode=None, user_data=None, parent=None):
        for ls in self.ls:
            ls[1].doResultHook(thenode, user_data, parent)
            #TODO: check subEvent for put it in LivingState
            ls[1].have_finish = True

    def doSubEvent(self):
        # TODO: doSubEvent on livingState and State
        for ls in self.ls:
            ls[1].doResultEvent()

    def doResultEvent(self):
        for ls in self.ls:
            ls[1].doResultEvent()
            #TODO: check subEvent for put it in LivingState
            ls[1].have_finish = True

    def doDefault(self):
        for ls in self.ls:
            if ls[1].alive is False:
                ls[1].doDefault()

    def resetLivingState(self):
        """Only one Living State on the S0 of each StateRegister"""
        # TODO: add some test to control number of instanciation of LivingState
        # clean all living state on S0
        must_delete = []
        l = len(self.ls)
        for idx, ls in zip(range(l), self.ls):
            # TODO: alive by default on False, change to True on the first match
            ids = id(ls[1].thestate())
            if ids == id(ls[0]) and (ls[1].have_finish or not ls[1].alive):
                must_delete.append(idx)
            elif ls[1].alive:
                ls[1].alive = False
        for delete in reversed(must_delete):
            self.ls.pop(delete)
        self.init_all()
    #############

    def to_dot(self) -> str:
        lscat = []
        for nsr in self.nsr:
            f = tempfile.NamedTemporaryFile(delete=False)
            f.close()
            lscat.append(f.name)
            nsr.to_dot_file(f.name)
        cmd = pipes.Template()
        cmd.prepend('gvpack 2>/dev/null -u ' + ' '.join(lscat), '.-')
        content = ""
        with cmd.open('pipefile', 'r') as f:
            content += f.read()
        for f in lscat:
            os.unlink(f)
        return content

    def to_dot_file(self, fname: str):
        with open(fname, 'w') as f:
            f.write(self.to_dot())

    def to_png_file(self, fname: str):
        cmd = pipes.Template()
        cmd.append('dot -Tpng > %s' % fname, '-.')
        with cmd.open('pipefile', 'w') as f:
            f.write(self.to_dot())