import atexit
import cmd
import json
import logging
import re
import time
import os

import geohash

from . import hooks
from .config import config
from .core import Result, Search, compute_geohash_key, reverse
from .db import DB
from .ds import get_document
from .helpers import (blue, cyan, green, haversine_distance, keys, km_to_score,
                      magenta, red, scripts, white, yellow)
from .helpers.index import token_frequency
from .helpers.search import preprocess_query
from .helpers.text import compare_str

try:
    import gnureadline as readline  # For OSX.
except ImportError:
    import readline  # Normal way.

class Cmd(cmd.Cmd):

    intro = (white('\nWelcome to the Addok shell o/\n') +
             magenta('Type HELP or ? to list commands.\n') +
             magenta('Type QUIT or ctrl-C or ctrl-D to quit.\n'))
    prompt = '> '
    HISTORY_FILE = '.addok_shell_history'

    def __init__(self):
        self._init_history_file()
        super().__init__()
        self.do_HELP = self.do_help

    def _init_history_file(self):
        if hasattr(readline, "read_history_file"):
            try:
                readline.read_history_file(self.history_file)
            except FileNotFoundError:
                pass
            atexit.register(self.save_history)

    def save_history(self):
        try:
            readline.write_history_file(self.history_file)
        except FileNotFoundError:
            print(red('Unable to write history file to '
                      '{}.'.format(self.history_file)))

    @property
    def history_file(self):
        return os.path.join(os.path.expanduser('~'), self.HISTORY_FILE)

    def error(self, message):
        print(red(message))

    def default(self, line):
        if line == 'EOF':
            return self.quit()
        return self.do_SEARCH(line)

    def quit(self):
        print(red("Bye!"))
        return True

    def loop(self, *args):
        try:
            self.cmdloop()
        except KeyboardInterrupt:
            return self.quit()

    def completenames(self, text, *ignored):
        return super().completenames(text.upper(), *ignored)

    def get_names(self):
        special = ['do_help', 'do_QUIT']
        return [n for n in super().get_names() if n not in special]

    def postcmd(self, stop, line):
        if line != 'EOF':
            print(yellow('-' * 80))
        return super().postcmd(stop, line)

    @classmethod
    def register_command(cls, command, name=None, doc=None):
        if name is None:
            name = command.__name__
        if doc is None:
            doc = command.__doc__
        if name.startswith('do_'):
            name = name[3:]
        name = 'do_' + name.upper()
        setattr(cls, name, command)
        attr = getattr(cls, name)
        attr.__doc__ = doc

    @classmethod
    def register_commands(cls, *commands, **named):
        for command in commands:
            cls.register_command(command)
        for name, command in named.items():
            cls.register_command(command, name)

    def do_help(self, command):
        """Display this help message."""
        if command:
            doc = getattr(self, 'do_' + command).__doc__
            print(cyan(doc.replace(' ' * 8, '')))
        else:
            print(magenta('Available commands:'))
            print(magenta('Type "HELP <command>" to get more info.'))
            names = self.get_names()
            names.sort()
            for name in names:
                if name[:3] != 'do_':
                    continue
                doc = getattr(self, name).__doc__
                doc = doc.split('\n')[0]
                print('{} {}'.format(yellow(name[3:]),
                                     cyan(doc.replace(' ' * 8, ' ')
                                             .replace('\n', ''))))

    @staticmethod
    def _match_option(key, string):
        matchs = re.findall('{}[= ][^ ]*'.format(key), string)
        option = None
        if matchs:
            option = matchs[0]
            string = string.replace(option, '')
            option = option.replace(key, '')
        return string.strip(), option.strip(' =') if option else option

    def _search(self, query, verbose=False, bucket=False, count=1):
        limit = 10
        autocomplete = True
        lat = None
        lon = None
        filters = {}
        if 'AUTOCOMPLETE' in query:
            query, autocomplete = self._match_option('AUTOCOMPLETE', query)
            autocomplete = bool(int(autocomplete))
        if 'LIMIT' in query:
            query, limit = self._match_option('LIMIT', query)
            limit = int(limit)
        if 'CENTER' in query:
            query, center = self._match_option('CENTER', query)
            lat, lon = center.split()
            lat = float(lat)
            lon = float(lon)
        for name in config.FILTERS:
            name = name.upper()
            if name in query:
                query, value = self._match_option(name, query)
                if value:
                    filters[name.lower()] = value.strip()
        helper = Search(limit=limit, verbose=verbose,
                        autocomplete=autocomplete)
        start = time.time()
        for i in range(0, count):
            results = helper(query, lat=lat, lon=lon, **filters)
        if bucket:  # Means we want all the bucket
            results = helper._sorted_bucket
        duration = round((time.time() - start) * 1000 / count, 1)
        if verbose:
            helper.report()

        def format_scores(result):
            if verbose or bucket:
                return (', '.join('{}: {}/{}'.format(k, round(v[0], 4), v[1])
                                  for k, v in result._scores.items()))
            else:
                return result.score

        for result in results:
            print('{} ({} | {})'.format(white(result),
                                        blue(result._id),
                                        blue(format_scores(result))))
        formatter = red if duration > 50 else green
        print('{} — {} — {}'.format(formatter("{} ms".format(duration)),
                                    magenta("{} run(s)".format(count)),
                                    cyan('{} results'.format(len(results)))))

    def do_QUIT(self, *args):
        """Quit this shell. Also ctrl-C or Ctrl-D."""
        return self.quit()

    def do_SEARCH(self, query):
        """Issue a search (default command, can be omitted):
        SEARCH rue des Lilas [CENTER lat lon] [LIMIT 10] [AUTOCOMPLETE 0] [FILTER VALUE…]"""  # noqa
        self._search(query)

    def do_EXPLAIN(self, query):
        """Issue a search with debug info:
        EXPLAIN rue des Lilas"""
        self._search(query, verbose=True)

    def do_BUCKET(self, query):
        """Issue a search and return all the collected bucket, not only up to
        limit elements:
        BUCKET rue des Lilas"""
        self._search(query, bucket=True)

    def do_BENCH(self, query):
        """Run a search many times to benchmark it.
        BENCH [100] rue des Lilas"""
        try:
            count = int(re.match(r'^(\d+).*', query).group(1))
        except AttributeError:
            count = 100
        self._search(query, count=count)

    def do_INTERSECT(self, words):
        """Do a raw intersect between tokens (default limit 100).
        INTERSECT rue des lilas [LIMIT 100]"""
        start = time.time()
        limit = 100
        if 'LIMIT' in words:
            words, limit = words.split('LIMIT')
            limit = int(limit)
        tokens = [keys.token_key(w) for w in preprocess_query(words)]
        DB.zinterstore(words, tokens)
        results = DB.zrevrange(words, 0, limit, withscores=True)
        DB.delete(words)
        for id_, score in results:
            r = Result(id_)
            print('{} {} {}'.format(white(r), blue(r._id), cyan(score)))
        duration = round((time.time() - start) * 1000, 1)
        print(magenta("({} in {} ms)".format(len(results), duration)))

    def do_DBINFO(self, *args):
        """Print some useful infos from Redis DB."""
        info = DB.info()
        keys = [
            'keyspace_misses', 'keyspace_hits', 'used_memory_human',
            'total_commands_processed', 'total_connections_received',
            'connected_clients']
        for key in keys:
            print('{}: {}'.format(white(key), blue(info[key])))
        nb_of_redis_db = int(DB.config_get('databases')['databases'])
        for db_index in range(nb_of_redis_db - 1):
            db_name = 'db{}'.format(db_index)
            if db_name in info:
                label = white('nb keys (db {})'.format(db_index))
                print('{}: {}'.format(label, blue(info[db_name]['keys'])))

    def do_DBKEY(self, key):
        """Print raw content of a DB key.
        DBKEY g|u09tyzfe"""
        type_ = DB.type(key).decode()
        if type_ == 'set':
            out = DB.smembers(key)
        elif type_ == 'string':
            out = DB.get(key)
        else:
            out = 'Unsupported type {}'.format(type_)
        print('type:', magenta(type_))
        print('value:', white(out))

    def do_GEODISTANCE(self, s):
        """Compute geodistance from a result to a point.
        GEODISTANCE 772210180J 48.1234 2.9876"""
        try:
            _id, lat, lon = s.split()
        except:
            return self.error('Malformed query. Use: ID lat lon')
        try:
            result = Result(keys.document_key(_id))
        except ValueError as e:
            return self.error(e)
        center = (float(lat), float(lon))
        km = haversine_distance((float(result.lat), float(result.lon)), center)
        score = km_to_score(km)
        print('km: {} | score: {}'.format(white(km), blue(score)))

    def do_GEOHASHTOGEOJSON(self, geoh):
        """Build GeoJSON corresponding to geohash given as parameter.
        GEOHASHTOGEOJSON u09vej04 [NEIGHBORS 0|1|2]"""
        geoh, with_neighbors = self._match_option('NEIGHBORS', geoh)
        bbox = geohash.bbox(geoh)
        try:
            with_neighbors = int(with_neighbors)
        except TypeError:
            with_neighbors = 0

        def expand(bbox, geoh, depth):
            neighbors = geohash.neighbors(geoh)
            for neighbor in neighbors:
                other = geohash.bbox(neighbor)
                if with_neighbors > depth:
                    expand(bbox, neighbor, depth + 1)
                else:
                    if other['n'] > bbox['n']:
                        bbox['n'] = other['n']
                    if other['s'] < bbox['s']:
                        bbox['s'] = other['s']
                    if other['e'] > bbox['e']:
                        bbox['e'] = other['e']
                    if other['w'] < bbox['w']:
                        bbox['w'] = other['w']

        if with_neighbors > 0:
            expand(bbox, geoh, 0)

        geojson = {
            "type": "Polygon",
            "coordinates": [[
                [bbox['w'], bbox['n']],
                [bbox['e'], bbox['n']],
                [bbox['e'], bbox['s']],
                [bbox['w'], bbox['s']],
                [bbox['w'], bbox['n']]
            ]]
        }
        print(white(json.dumps(geojson)))

    def do_GEOHASH(self, latlon):
        """Compute a geohash from latitude and longitude.
        GEOHASH 48.1234 2.9876"""
        try:
            lat, lon = map(float, latlon.split())
        except ValueError:
            print(red('Invalid lat and lon {}'.format(latlon)))
        else:
            print(white(geohash.encode(lat, lon, config.GEOHASH_PRECISION)))

    def do_GEOHASHMEMBERS(self, geoh):
        """Return members of a geohash and its neighbors.
        GEOHASHMEMBERS u09vej04 [NEIGHBORS 0]"""
        geoh, with_neighbors = self._match_option('NEIGHBORS', geoh)
        key = compute_geohash_key(geoh, with_neighbors != '0')
        if key:
            for id_ in DB.smembers(key):
                r = Result(id_)
                print('{} {}'.format(white(r), blue(r._id)))

    def do_GET(self, _id):
        """Get document from index with its id.
        GET 772210180J"""
        doc = doc_by_id(_id)
        if not doc:
            return self.error('id "{}" not found'.format(_id))
        for key, value in doc.items():
            if key == config.HOUSENUMBERS_FIELD:
                continue
            print('{} {}'.format(white(key), magenta(value)))
        if doc.get('housenumbers'):
            def sorter(v):
                try:
                    return int(re.match(r'^\d+', v['raw']).group())
                except AttributeError:
                    return -1
            housenumbers = sorted(doc['housenumbers'].values(), key=sorter)
            print(white('housenumbers'),
                  magenta(', '.join(v['raw'] for v in housenumbers)))

    def do_FREQUENCY(self, word):
        """Return word frequency in index.
        FREQUENCY lilas"""
        print(white(word_frequency(word)))

    def _print_field_index_details(self, field, _id):
        for token in indexed_string(field):
            print(
                white(token),
                blue(DB.zscore(keys.token_key(token), keys.document_key(_id))),
                blue(DB.zrevrank(keys.token_key(token),
                     keys.document_key(_id))))

    def do_INDEX(self, _id):
        """Get index details for a document by its id.
        INDEX 772210180J"""
        doc = doc_by_id(_id)
        if not doc:
            return self.error('id "{}" not found'.format(_id))
        for field in config.FIELDS:
            key = field['key']
            if key in doc:
                self._print_field_index_details(doc[key], _id)

    def do_BESTSCORE(self, word):
        """Return document linked to word with higher score.
        BESTSCORE lilas"""
        key = keys.token_key(indexed_string(word)[0])
        for _id, score in DB.zrevrange(key, 0, 20, withscores=True):
            result = Result(_id)
            print(white(result), blue(score), green(result._id))

    def do_REVERSE(self, latlon):
        """Do a reverse search. Args: lat lon.
        REVERSE 48.1234 2.9876"""
        lat, lon = latlon.split()
        for r in reverse(float(lat), float(lon)):
            print('{} ({} | {} km | {})'.format(white(r), blue(r.score),
                                                blue(r.distance), blue(r._id)))

    def do_TOKENIZE(self, string):
        """Inspect how a string is tokenized before being indexed.
        TOKENIZE Rue des Lilas"""
        print(white(' '.join(indexed_string(string))))

    def do_STRDISTANCE(self, s):
        """Print the distance score between two strings. Use | as separator.
        STRDISTANCE rue des lilas|porte des lilas"""
        s = s.split('|')
        if not len(s) == 2:
            print(red('Malformed string. Use | between the two strings.'))
            return
        one, two = s
        print(white(compare_str(one, two)))

    def do_CONFIG(self, name):
        """Inspect loaded Addok config. Output all config without argument.
        CONFIG [CONFIG_KEY]"""
        if not name:
            for name in self.complete_CONFIG():
                self.do_CONFIG(name)
            return
        value = getattr(config, name.upper(), 'Not found.')
        print(blue(name), white(format_config(value)))

    def complete_CONFIG(self, text=None, *ignored):
        text = text or ''
        return [a for a in config.keys() if a.startswith(text) and a.isupper()]

    def do_SCRIPT(self, args):
        """Run a Lua script. Takes the raw Redis arguments.
        SCRIPT script_name number_of_keys key1 key2… arg1 arg2
        """
        try:
            name, keys_count, *args = args.split()
        except ValueError:
            print(red('Not enough arguments'))
            return
        try:
            keys_count = int(keys_count)
        except ValueError:
            print(red('You must pass the number of keys as first argument'))
            self.do_HELP('SCRIPT')
            return
        keys = args[:keys_count]
        args = args[keys_count:]
        try:
            output = getattr(scripts, name)(keys=keys, args=args)
        except AttributeError:
            print(red('No script named {}'.format(name)))
            return
        except DB.Error as e:
            print(red(e))
            return
        if not isinstance(output, list):
            # Script may return just an integer.
            output = [output]
        for line in output:
            print(white(line))

def format_config(value):
    out = ''
    if isinstance(value, (list, tuple)):
        for item in value:
            out += format_config(item)
    elif type(value).__name__ == 'function':
        out = '\n{}.{}'.format(str(value.__module__), value.__name__)
    else:
        out = '\n{}'.format(value)
    return out

def invoke(args=None):
    cmd = Cmd()
    hooks.register_shell_command(cmd)
    cmd.loop()

def pyinvoke(args=None):
    try:
        from IPython import start_ipython
    except ImportError:
        print(red('Import is not installed. Type "pip install ipython"'))
    else:
        start_ipython(argv=[], user_ns={'DB': DB, 'config': config,
                                        'get_document': get_document})

def register_command(subparsers):
    parser = subparsers.add_parser('shell',
                                   help='Run a shell to inspect Addok')
    parser.set_defaults(func=invoke)
    parser = subparsers.add_parser('pyshell',
                                   help='Run a python shell with Addok config')
    parser.set_defaults(func=pyinvoke)

def doc_by_id(_id):
    return get_document(keys.document_key(_id).encode())

def indexed_string(s):
    return list(preprocess_query(s))

def word_frequency(word):
    try:
        token = list(preprocess_query(word))[0]
    except IndexError:
        # Word has been filtered out.
        return
    return token_frequency(token)

def set_debug():
    logging.basicConfig(level=logging.DEBUG)


"""
prequests
~~~~~~~~~

An easy copy of `https://github.com/leVirve/prequests` for convenient.

This module contains an asynchronous replica of ``requests.api``, powered
by futures. All API methods return a ``Request`` instance (as opposed to
``Response``). A list of requests can be sent with ``map()``.
"""

import traceback
import contextlib
from functools import partial
from multiprocessing.dummy import Pool

try:
    from requests import Session
    from fake_useragent import UserAgent
except ImportError:
    raise RuntimeError('`requests` and `fake_useragent` are required.')

__all__ = (
    'map', 'imap', 'imap_unordered',
    'get', 'options', 'head', 'post', 'put', 'patch', 'delete', 'request'
)

ua = UserAgent()

class AsyncRequest(object):
    """ Asynchronous request.

    Accept same parameters as ``Session.request`` and some additional:

    :param session: Session which will do request
    :param callback: Callback called on response.
    Same as passing ``hooks={'response': callback}``
    """
    def __init__(self, method, url, **kwargs):
        #: Request method
        self.method = method
        #: URL to request
        self.url = url
        #: Associated ``Session``
        self.session = kwargs.pop('session', None)
        if self.session is None:
            self.session = Session()
            self.session.headers['User-Agent'] = ua.random

        callback = kwargs.pop('callback', None)
        if callback:
            kwargs['hooks'] = {'response': callback}

        #: The rest arguments for ``Session.request``
        self.kwargs = kwargs
        #: Resulting ``Response``
        self.response = None

    def send(self, **kwargs):
        """
        Prepares request based on parameter passed to constructor and optional
        ``kwargs```. Then sends request and saves response to :attr:`response`.

        :returns: ``Response``
        """
        merged_kwargs = {}
        merged_kwargs.update(self.kwargs)
        merged_kwargs.update(kwargs)
        try:
            self.response = self.session.request(
                                self.method, self.url, **merged_kwargs)
        except Exception as e:
            self.exception = e
            self.traceback = traceback.format_exc()
        return self

def send(r, stream=False):
    """Just sends the request using its send method and returns its response.  """
    r.send(stream=stream)
    return r.response

# Shortcuts for creating AsyncRequest with appropriate HTTP method
get = partial(AsyncRequest, 'GET')
options = partial(AsyncRequest, 'OPTIONS')
head = partial(AsyncRequest, 'HEAD')
post = partial(AsyncRequest, 'POST')
put = partial(AsyncRequest, 'PUT')
patch = partial(AsyncRequest, 'PATCH')
delete = partial(AsyncRequest, 'DELETE')

# synonym
def request(method, url, **kwargs):
    return AsyncRequest(method, url, **kwargs)

def map(requests, stream=True, pool=None, size=1, exception_handler=None):
    """Concurrently converts a list of Requests to Responses.

    :param requests: a collection of Request objects.
    :param stream: If False, the content will not be downloaded immediately.
    :param size: Specifies the number of workers to run at a time. If 1, no parallel processing.
    :param exception_handler: Callback function, called when exception occured. Params: Request, Exception
    """

    pool = pool if pool else Pool(size)
    requests = list(requests)

    requests = pool.map(send, requests)

    ret = []
    for request in requests:
        if request.response is not None:
            ret.append(request.response)
        elif exception_handler and hasattr(request, 'exception'):
            ret.append(exception_handler(request, request.exception))
        else:
            ret.append(None)

    if not pool:
        pool.close()

    return ret

def imap(requests, stream=True, pool=None, size=2, exception_handler=None):
    """Concurrently converts a generator object of Requests to
    a generator of Responses.

    :param requests: a generator of Request objects.
    :param stream: If False, the content will not be downloaded immediately.
    :param size: Specifies the number of requests to make at a time. default is 2
    :param exception_handler: Callback function, called when exception occured. Params: Request, Exception
    """

    def send(r):
        return r.send(stream=stream)

    pool = pool if pool else Pool(size)

    for request in pool.imap(send, requests):
        if request.response is not None:
            yield request.response
        elif exception_handler:
            exception_handler(request, request.exception)

    if not pool:
        pool.close()

def imap_unordered(requests, stream=True, pool=None, size=2, exception_handler=None):
    """Concurrently converts a generator object of Requests to
    a generator of Responses.

    :param requests: a generator of Request objects.
    :param stream: If False, the content will not be downloaded immediately.
    :param size: Specifies the number of requests to make at a time. default is 2
    :param exception_handler: Callback function, called when exception occured. Params: Request, Exception
    """

    def send(r):
        return r.send(stream=stream)

    pool = pool if pool else Pool(size)

    with contextlib.closing(Pool(size)) as pool:
        for request in pool.imap_unordered(send, requests):
            if request.response is not None:
                yield request.response
            elif exception_handler:
                exception_handler(request, request.exception)

    if not pool:
        pool.close()


from hwt.bitmask import selectBitRange, mask
from hwt.code import Concat
from hwt.doc_markers import internal
from hwt.hdl.types.array import HArray
from hwt.hdl.types.bits import Bits
from hwt.hdl.types.hdlType import default_reinterpret_cast_fn
from hwt.synthesizer.exceptions import TypeConversionErr

@internal
def getBits_from_array(array, wordWidth, start, end,
                       reinterpretElmToType=None):
    """
    Gets value of bits between selected range from memory

    :param start: bit address of start of bit of bits
    :param end: bit address of first bit behind bits
    :return: instance of BitsVal (derived from SimBits type) which contains
        copy of selected bits
    """
    inPartOffset = 0
    value = Bits(end - start, None).fromPy(None)

    while start != end:
        assert start < end, (start, end)

        dataWordIndex = start // wordWidth

        v = array[dataWordIndex]
        if reinterpretElmToType is not None:
            v = v._reinterpret_cast(reinterpretElmToType)

        endOfWord = (dataWordIndex + 1) * wordWidth
        width = min(end, endOfWord) - start
        offset = start % wordWidth

        val = selectBitRange(v.val, offset, width)
        vldMask = selectBitRange(v.vldMask, offset, width)
        updateTime = v.updateTime

        m = mask(width)
        value.val |= (val & m) << inPartOffset
        value.vldMask |= (vldMask & m) << inPartOffset
        value.updateMask = max(value.updateTime, updateTime)

        inPartOffset += width
        start += width

    return value

@internal
def reinterptet_harray_to_bits(typeFrom, sigOrVal, bitsT):
    """
    Cast HArray signal or value to signal or value of type Bits
    """
    size = int(typeFrom.size)
    widthOfElm = typeFrom.elmType.bit_length()
    w = bitsT.bit_length()
    if size * widthOfElm != w:
        raise TypeConversionErr(
            "Size of types is different", size * widthOfElm, w)

    partT = Bits(widthOfElm)
    parts = [p._reinterpret_cast(partT) for p in sigOrVal]

    return Concat(*reversed(parts))._reinterpret_cast(bitsT)

@internal
def reinterpret_harray_to_harray(typeFrom, sigOrVal, arrayT):
    mySize = int(typeFrom.size)
    myWidthOfElm = typeFrom.elmType.bit_length()
    size = int(arrayT.size)
    widthOfElm = arrayT.elmType.bit_length()

    if size * widthOfElm != mySize * myWidthOfElm:
        raise TypeConversionErr("Size of types is different",
                                size * widthOfElm, mySize * myWidthOfElm)

    if isinstance(typeFrom.elmType, Bits):
        reinterpretElmToType = None
    else:
        reinterpretElmToType = Bits(myWidthOfElm)

    res = arrayT.fromPy(None)
    for i in range(size):
        start = i * widthOfElm
        end = (i + 1) * widthOfElm
        item = getBits_from_array(
            sigOrVal, myWidthOfElm, start, end, reinterpretElmToType)
        res[i] = item._reinterpret_cast(arrayT.elmType)

    return res

@internal
def reinterpret_cast_harray(typeFrom, sigOrVal, toType):
    if isinstance(toType, Bits):
        return reinterptet_harray_to_bits(typeFrom, sigOrVal, toType)
    elif isinstance(toType, HArray):
        return reinterpret_harray_to_harray(typeFrom, sigOrVal, toType)
    else:
        return default_reinterpret_cast_fn(typeFrom, sigOrVal, toType)


from hwt.hdl.types.defs import INT, SLICE
from hwt.hdl.types.slice import Slice
from hwt.hdl.types.typeCast import toHVal
from hwt.hdl.value import Value
from hwt.doc_markers import internal

@internal
def slice_to_SLICE(sliceVals, width):
    """convert python slice to value of SLICE hdl type"""
    if sliceVals.step is not None:
        raise NotImplementedError()

    start = sliceVals.start
    stop = sliceVals.stop

    if sliceVals.start is None:
        start = INT.fromPy(width)
    else:
        start = toHVal(sliceVals.start)

    if sliceVals.stop is None:
        stop = INT.fromPy(0)
    else:
        stop = toHVal(sliceVals.stop)

    startIsVal = isinstance(start, Value)
    stopIsVal = isinstance(stop, Value)

    indexesAreValues = startIsVal and stopIsVal
    if indexesAreValues:
        updateTime = max(start.updateTime, stop.updateTime)
    else:
        updateTime = -1

    return Slice.getValueCls()((start, stop), SLICE, 1, updateTime)


from typing import Union, Tuple

from hwt.hdl.types.hdlType import HdlType
from hwt.hdl.types.struct import HStructField

class TransPart(object):
    """
    Container for informations about parts of TransTmpl split on databus words

    :ivar parent: instance of FrameTmpl
    :ivar tmpl: origin template which is this representation of
        (StructField/HdlType instance)
    :ivar isPadding: flag which specifies if this TransaPart is just a padding
    :ivar startOfPart: bit addr of start of this part
    :ivar endOfPart: bit addr of end of this part
    :ivar inFieldOffset: bit offset of this part in parent field
    """

    def __init__(self, parent: 'FrameTmpl',
                 tmpl: Union[HStructField, HdlType],
                 startOfPart: int,
                 endOfPart: int,
                 inFieldOffset: int):
        self.parent = parent
        self.tmpl = tmpl
        self.isPadding = tmpl is None
        self.startOfPart = startOfPart
        self.endOfPart = endOfPart
        self.inFieldOffset = inFieldOffset

    def bit_length(self) -> int:
        """
        :return: bit length of this part
        """
        return self.endOfPart - self.startOfPart

    def getBusWordBitRange(self) -> Tuple[int, int]:
        """
        :return: bit range which contains data of this part on bus data signal
        """
        offset = self.startOfPart % self.parent.wordWidth
        return (offset + self.bit_length(), offset)

    def getFieldBitRange(self) -> Tuple[int, int]:
        """
        :return: bit range which contains data of this part on interface
            of field
        """
        offset = self.inFieldOffset
        return (self.bit_length() + offset, offset)

    def isLastPart(self) -> bool:
        """
        :return: True if this part is last in parts derived from original field
            else False
        """
        return self.tmpl.bitAddrEnd == self.endOfPart

    def __repr__(self):
        return "<TransPart %r, startOfPart:%d, endOfPart:%d>" % (
               self.tmpl, self.startOfPart, self.endOfPart)


from typing import Set, List, Dict, Union, Optional

from hwt.hdl.assignment import Assignment
from hwt.hdl.statements import HdlStatement
from hwt.hdl.value import Value
from hwt.synthesizer.rtlLevel.mainBases import RtlSignalBase
from hwt.doc_markers import internal

@internal
def fill_stm_list_with_enclosure(parentStm: Optional[HdlStatement],
                                 current_enclosure: Set[RtlSignalBase],
                                 statements: List["HdlStatement"],
                                 do_enclose_for: List[RtlSignalBase],
                                 enclosure: Dict[RtlSignalBase, Union[Value, RtlSignalBase]])\
        -> None:
    """
    Apply enclosure on list of statements
    (fill all unused code branches with assignments from value specified by enclosure)

    :param parentStm: optional parent statement where this list is some branch
    :param current_enclosure: list of signals for which this statement list is enclosed
    :param statements: list of statements
    :param do_enclose_for: selected signals for which enclosure should be used
    :param enclosure: enclosure values for signals

    :attention: original statements parameter can be modified
    :return: new statements
    """
    if statements is None:
        statements = []

    for e_sig in do_enclose_for:
        if e_sig in current_enclosure:
            continue
        enclosed = False
        for stm in statements:
            if e_sig in stm._outputs:
                if e_sig not in stm._enclosed_for:
                    stm._fill_enclosure(enclosure)
                enclosed = True
                break
        # any statement was not related with this signal,
        if not enclosed:
            e = enclosure[e_sig]
            a = Assignment(e, e_sig)
            statements.append(a)

            if parentStm is not None:
                a._set_parent_stm(parentStm)

    return statements


import fnmatch
import os

def find_files(directory, pattern, recursive=True):
    """
    Find files by pattern in directory
    """
    if not os.path.isdir(directory):
        if os.path.exists(directory):
            raise IOError(directory + ' is not directory')
        else:
            raise IOError(directory + " does not exists")
    if recursive:
        for root, _, files in os.walk(directory):
            for basename in files:
                if fnmatch.fnmatch(basename, pattern):
                    filename = os.path.join(root, basename)
                    yield filename
    else:
        root = directory
        for basename in os.listdir(root):
            if fnmatch.fnmatch(basename, pattern):
                filename = os.path.join(root, basename)
                if os.path.isfile(filename):
                    yield filename


import math
from operator import and_, or_, xor, add

from hwt.code_utils import _mkOp, _connect, _intfToSig
from hwt.hdl.ifContainter import IfContainer
from hwt.hdl.operatorDefs import concatFn
from hwt.hdl.statements import HwtSyntaxError
from hwt.hdl.switchContainer import SwitchContainer
from hwt.hdl.typeShortcuts import hInt, vec
from hwt.hdl.types.bits import Bits
from hwt.hdl.types.enum import HEnum
from hwt.hdl.types.typeCast import toHVal
from hwt.hdl.value import Value
from hwt.pyUtils.arrayQuery import arr_any
from hwt.synthesizer.exceptions import IntfLvlConfErr
from hwt.synthesizer.hObjList import HObjList
from hwt.synthesizer.rtlLevel.mainBases import RtlSignalBase
from hwt.synthesizer.rtlLevel.signalUtils.walkers import \
    discoverEventDependency
from hwt.doc_markers import internal

class If(IfContainer):
    """
    If statement generator

    :ivar _is_completly_event_dependent: flag wihich tells if all statement
        in this statements are event dependent
    :ivar _now_is_event_dependent: flag if current scope of if is event dependent
        (is used to mark statements as event dependent)
    """

    def __init__(self, cond, *statements):
        """
        :param cond: condition in if statement
        :param statements: list of statements which should be active
            if condition is met
        """
        cond_sig = _intfToSig(cond)
        if not isinstance(cond_sig, RtlSignalBase):
            raise IntfLvlConfErr("Condition is not signal, it is not certain"
                                 " if this an error or desire ", cond_sig)

        super(If, self).__init__(cond_sig)
        self.rank = 1
        self._inputs.append(cond_sig)
        cond_sig.endpoints.append(self)

        ev_dep = arr_any(discoverEventDependency(cond_sig), lambda x: True)
        self._is_completly_event_dependent = ev_dep
        self._now_is_event_dependent = ev_dep

        self._register_stements(statements, self.ifTrue)
        self._get_rtl_context().statements.add(self)

    def Elif(self, cond, *statements):
        assert self.parentStm is None
        self.rank += 1
        cond_sig = _intfToSig(cond)

        self._now_is_event_dependent = arr_any(
            discoverEventDependency(cond_sig), lambda x: True)

        self._inputs.append(cond_sig)
        cond_sig.endpoints.append(self)

        case = []
        self.elIfs.append((cond_sig, case))
        self._register_stements(statements, case)

        return self

    def Else(self, *statements):
        assert self.parentStm is None
        if self.ifFalse is not None:
            raise HwtSyntaxError(
                "Else on this if-then-else statemen was aready used")

        self.rank += 1

        self.ifFalse = []
        self._register_stements(statements, self.ifFalse)
        return self

class Switch(SwitchContainer):
    """
    Switch statement generator
    """

    def __init__(self, switchOn):
        switchOn = _intfToSig(switchOn)
        if not isinstance(switchOn, RtlSignalBase):
            raise HwtSyntaxError("Select is not signal, it is not certain"
                                 " if this an error or desire")
        if arr_any(discoverEventDependency(switchOn), lambda x: True):
            raise HwtSyntaxError("Can not switch on result of event operator")

        super(Switch, self).__init__(switchOn, [])
        switchOn.ctx.statements.add(self)

    @internal
    def _cut_off_drivers_of(self, sig: RtlSignalBase):
        raise NotImplementedError()

    def addCases(self, tupesValStmnts):
        """
        Add multiple case statements from iterable of tuleles
        (caseVal, statements)
        """
        s = self
        for val, statements in tupesValStmnts:
            s = s.Case(val, statements)
        return s

    def Case(self, caseVal, *statements):
        "c-like case of switch statement"
        assert self.parentStm is None
        caseVal = toHVal(caseVal, self.switchOn._dtype)

        assert isinstance(caseVal, Value), caseVal
        assert caseVal._isFullVld(), "Cmp with invalid value"
        assert caseVal not in self._case_value_index, (
            "Switch statement already has case for value ", caseVal)

        self.rank += 1
        case = []
        self._case_value_index[caseVal] = len(self.cases)
        self.cases.append((caseVal, case))

        cond = self.switchOn._eq(caseVal)
        self._inputs.append(cond)
        cond.endpoints.append(self)

        self._register_stements(statements, case)

        return self

    def Default(self, *statements):
        """c-like default of switch statement
        """
        assert self.parentStm is None
        self.rank += 1
        self.default = []
        self._register_stements(statements, self.default)
        return self

def SwitchLogic(cases, default=None):
    """
    Generate if tree for cases like (syntax shugar for large elifs)

    ..code-block:: python
        if cond0:
            statements0
        elif cond1:
            statements1
        else:
            default

    :param case: iterable of tuples (condition, statements)
    :param default: default statements
    """
    if default is not None:
        assigTop = default
    else:
        assigTop = []

    for cond, statements in reversed(cases):
        assigTop = If(cond,
                      statements
                      ).Else(
            assigTop
        )

    return assigTop

def In(sigOrVal, iterable):
    """
    Hdl convertible in operator, check if any of items
    in "iterable" equals "sigOrVal"
    """
    res = None
    for i in iterable:
        i = toHVal(i)
        if res is None:
            res = sigOrVal._eq(i)
        else:
            res = res | sigOrVal._eq(i)

    assert res is not None, "Parameter iterable is empty"
    return res

def StaticForEach(parentUnit, items, bodyFn, name=""):
    """
    Generate for loop for static items

    :param parentUnit: unit where this code should be instantiated
    :param items: items which this "for" itering on
    :param bodyFn: function which fn(item, index) or fn(item)
        returns (statementList, ack).
        It's content is performed in every iteration.
        When ack is high loop will fall to next iteration
    """

    items = list(items)
    itemsCnt = len(items)
    if itemsCnt == 0:
        # if there are no items there is nothing to generate
        return []
    elif itemsCnt == 1:
        # if there is only one item do not generate counter logic generate
        return bodyFn(items[0], 0)
    else:
        # if there is multiple items we have to generate counter logic
        index = parentUnit._reg(name + "for_index",
                                Bits(log2ceil(itemsCnt + 1), signed=False),
                                defVal=0)
        ackSig = parentUnit._sig(name + "for_ack")

        statementLists = []
        for i, (statementList, ack) in [(i, bodyFn(item, i))
                                        for i, item in enumerate(items)]:
            statementLists.append(statementList + [(ackSig(ack)), ])

        If(ackSig,
           If(index._eq(itemsCnt - 1),
              index(0)
              ).Else(
               index(index + 1)
           )
           )

        return Switch(index)\
            .addCases(
            enumerate(statementLists)
        ).Default(
            bodyFn(items[0], 0)[0],
            ackSig(True)
        )

class FsmBuilder(Switch):
    """
    :ivar stateReg: register with state
    """

    def __init__(self, parent, stateT, stateRegName="st"):
        """
        :param parent: parent unit where fsm should be builded
        :param stateT: enum type of state
        :param stateRegName: name of register where sate is stored
        """
        if isinstance(stateT, HEnum):
            beginVal = stateT.fromPy(stateT._allValues[0])
        else:
            beginVal = 0

        self.stateReg = parent._reg(stateRegName, stateT, beginVal)
        Switch.__init__(self, self.stateReg)

    def Trans(self, stateFrom, *condAndNextState):
        """
        :param stateFrom: apply when FSM is in this state
        :param condAndNextState: tupes (condition, newState),
            last does not to have condition

        :attention: transitions has priority, first has the biggest
        :attention: if stateFrom is None it is evaluated as default
        """
        top = []
        last = True

        for cAndS in reversed(condAndNextState):
            if last is True:
                last = False
                # if this is last trans. it does not have to condition
                try:
                    condition, newvalue = cAndS
                except TypeError:
                    top = self.stateReg(cAndS)
                    continue
                top = []

            else:
                condition, newvalue = cAndS

            # building decision tree
            top = \
                If(condition,
                   self.stateReg(newvalue)
                   ).Else(
                    top
                )
        if stateFrom is None:
            return Switch.Default(self, top)
        else:
            return Switch.Case(self, stateFrom, top)

    def Default(self, *condAndNextState):
        d = self.Trans(None, *condAndNextState)
        d.stateReg = self.stateReg
        return d

def connect(src, *destinations, exclude: set=None, fit=False):
    """
    Connect src (signals/interfaces/values) to all destinations

    :param exclude: interfaces on any level on src or destinations
        which should be excluded from connection process
    :param fit: auto fit source width to destination width
    """
    assignemnts = []

    if isinstance(src, HObjList):
        for dst in destinations:
            assert len(src) == len(dst), (src, dst)
        _destinations = [iter(d) for d in destinations]
        for _src in src:
            dsts = [next(d) for d in _destinations]
            assignemnts.append(connect(_src, *dsts, exclude=exclude, fit=fit))
    else:
        for dst in destinations:
            assignemnts.append(_connect(src, dst, exclude, fit))

    return assignemnts

# variadic operator functions
And = _mkOp(and_)
Add = _mkOp(add)
Or = _mkOp(or_)
Xor = _mkOp(xor)
Concat = _mkOp(concatFn)

def power(base, exp) -> RtlSignalBase:
    return toHVal(base)._pow(exp)

def ror(sig, howMany) -> RtlSignalBase:
    "Rotate right"
    return sig[howMany:]._concat(sig[:howMany])

def rol(sig, howMany) -> RtlSignalBase:
    "Rotate left"
    width = sig._dtype.bit_length()
    return sig[(width - howMany):]._concat(sig[:(width - howMany)])

def sll(sig, howMany) -> RtlSignalBase:
    "Logical shift left"
    width = sig._dtype.bit_length()
    return sig[(width - howMany):]._concat(vec(0, howMany))

def srl(sig, howMany) -> RtlSignalBase:
    "Logical shift right"
    return vec(0, howMany)._concat(sig[:howMany])

def log2ceil(x):
    """
    Returns no of bits required to store x-1
    for example x=8 returns 3
    """

    if not isinstance(x, (int, float)):
        x = int(x)

    if x == 0 or x == 1:
        res = 1
    else:
        res = math.ceil(math.log2(x))

    return hInt(res)

def isPow2(num) -> bool:
    """
    Check if number or constant is power of two
    """
    if not isinstance(num, int):
        num = int(num)
    return num != 0 and ((num & (num - 1)) == 0)

def binToGray(sigOrVal) -> RtlSignalBase:
    width = sigOrVal._dtype.bit_length()
    return Concat(sigOrVal[width - 1],
                  sigOrVal[width - 1:0] ^ sigOrVal[width:1])

def sizeof(_type) -> int:
    "get size of type in bytes"
    s = _type.bit_length()
    return math.ceil(s / 8)

# shortcuts
c = connect


from datetime import datetime
import sys
from typing import Union, Optional, Tuple, Callable

from pyDigitalWaveTools.vcd.common import VCD_SIG_TYPE
from pyDigitalWaveTools.vcd.writer import VcdWriter, VcdVarWritingScope, \
    vcdBitsFormatter, vcdEnumFormatter, VarAlreadyRegistered

from hwt.doc_markers import internal
from hwt.hdl.types.bits import Bits
from hwt.hdl.types.bool import HBool
from hwt.hdl.types.enum import HEnum
from hwt.hdl.value import Value
from hwt.simulator.hdlSimConfig import HdlSimConfig
from hwt.simulator.simModel import SimModel
from hwt.simulator.types.simBits import SimBitsT
from hwt.synthesizer.interface import Interface
from hwt.synthesizer.interfaceLevel.unitImplHelpers import getSignalName
from hwt.synthesizer.rtlLevel.mainBases import RtlSignalBase
from hwt.synthesizer.unit import Unit

@internal
def vcdTypeInfoForHType(t) -> Tuple[str, int, Callable[[RtlSignalBase, Value], str]]:
    """
    :return: (vcd type name, vcd width)
    """
    if isinstance(t, (SimBitsT, Bits, HBool)):
        return (VCD_SIG_TYPE.WIRE, t.bit_length(), vcdBitsFormatter)
    elif isinstance(t, HEnum):
        return (VCD_SIG_TYPE.REAL, 1, vcdEnumFormatter)
    else:
        raise ValueError(t)

class VcdHdlSimConfig(HdlSimConfig):
    supported_type_classes = (HBool, Bits, HEnum)

    def __init__(self, dumpFile=sys.stdout):
        self.vcdWriter = VcdWriter(dumpFile)
        self.logPropagation = False
        self.logApplyingValues = False
        self._obj2scope = {}

    def vcdRegisterInterfaces(self, obj: Union[Interface, Unit],
                              parent: Optional[VcdVarWritingScope]):
        """
        Register signals from interfaces for Interface or Unit instances
        """
        if hasattr(obj, "_interfaces") and obj._interfaces:
            name = obj._name
            parent_ = self.vcdWriter if parent is None else parent

            subScope = parent_.varScope(name)
            self._obj2scope[obj] = subScope

            with subScope:
                # register all subinterfaces
                for chIntf in obj._interfaces:
                    self.vcdRegisterInterfaces(chIntf, subScope)

                if isinstance(obj, (Unit, SimModel)):
                    # register interfaces from all subunits
                    for u in obj._units:
                        self.vcdRegisterInterfaces(u, subScope)

            return subScope
        else:
            t = obj._dtype
            if isinstance(t, self.supported_type_classes):
                tName, width, formatter = vcdTypeInfoForHType(t)
                try:
                    parent.addVar(obj, getSignalName(obj),
                                  tName, width, formatter)
                except VarAlreadyRegistered:
                    pass

    def vcdRegisterRemainingSignals(self, unit: Union[Interface, Unit]):
        unitScope = self._obj2scope[unit]
        for s in unit._ctx.signals:
            if s not in self.vcdWriter._idScope:
                t = s._dtype
                if isinstance(t, self.supported_type_classes):
                    tName, width, formatter = vcdTypeInfoForHType(t)
                    unitScope.addVar(s, getSignalName(
                        s), tName, width, formatter)

        for u in unit._units:
            self.vcdRegisterRemainingSignals(u)

    def initUnitSignalsForInterfaces(self, unit):
        self._scope = self.registerInterfaces(unit)
        for s in unit._ctx.signals:
            if s not in self.vcdWriter._idScope:
                self.registerSignal(s)

        for u in unit._units:
            self.initUnitSignals(u)

    def beforeSim(self, simulator, synthesisedUnit):
        """
        This method is called before first step of simulation.
        """
        vcd = self.vcdWriter
        vcd.date(datetime.now())
        vcd.timescale(1)

        self.vcdRegisterInterfaces(synthesisedUnit, None)
        self.vcdRegisterRemainingSignals(synthesisedUnit)

        vcd.enddefinitions()

    def logChange(self, nowTime, sig, nextVal):
        """
        This method is called for every value change of any signal.
        """
        try:
            self.vcdWriter.logChange(nowTime, sig, nextVal)
        except KeyError:
            # not every signal has to be registered
            pass


from hwt.hdl.variables import SignalItem
from hwt.serializer.exceptions import SerializerException
from hwt.serializer.generic.constants import SIGNAL_TYPE
from hwt.serializer.generic.indent import getIndent
from hwt.serializer.systemC.utils import systemCTypeOfSig
from hwt.doc_markers import internal

class SystemCSerializer_statements():

    @internal
    @classmethod
    def _Assignment(cls, dst, typeOfDst, src, ctx):
        indent_str = getIndent(ctx.indent)

        dstStr = cls.asHdl(dst, ctx.forTarget())
        if typeOfDst == SIGNAL_TYPE.REG:
            fmt = "%s%s = %s;"
        else:
            fmt = "%s%s.write(%s);"

        return fmt % (indent_str, dstStr, cls.Value(src, ctx))

    @classmethod
    def Assignment(cls, a, ctx):
        dst = a.dst
        assert isinstance(dst, SignalItem)
        assert not dst.virtualOnly, "should not be required"

        typeOfDst = systemCTypeOfSig(dst)
        if a.indexes is not None:
            for i in a.indexes:
                dst = dst[i]

        if dst._dtype == a.src._dtype:
            return cls._Assignment(dst, typeOfDst, a.src, ctx)
        else:
            raise SerializerException("%r <= %r  is not valid assignment\n"
                                      " because types are different (%r; %r) "
                                      % (dst, a.src, dst._dtype, a.src._dtype))

    @classmethod
    def HWProcess(cls, proc, ctx):
        """
        Serialize HWProcess instance

        :param scope: name scope to prevent name collisions
        """
        body = proc.statements
        childCtx = ctx.withIndent()
        statemets = [cls.asHdl(s, childCtx) for s in body]
        proc.name = ctx.scope.checkedName(proc.name, proc)

        return cls.methodTmpl.render(
            indent=getIndent(ctx.indent),
            name=proc.name,
            statements=statemets
        )


from hwt.doc_markers import internal

class LangueKeyword(object):
    """
    Base class for keywords of target language
    """
    pass

class NameOccupiedErr(Exception):
    def __init__(self, usedOn):
        self.usedOn = usedOn

class NameScopeItem(dict):
    """
    if name is discovered in scope it is converted to name_id
    where id is sequential number for prefix name\_
    """

    def __init__(self, myLvl):
        super().__init__()
        self.myLvl = myLvl

        # some names are specified just as prefix and serializer
        # should resolve correct name for object
        # this happens for most of generated objects
        self.cntrsForPrefixNames = {}

    def getChild(self, parent):
        try:
            return parent[self.myLvl + 1]
        except IndexError:
            return None

    def getParent(self, parent):

        i = self.myLvl - 1
        if i < 0:
            return None
        else:
            return parent[self.myLvl - 1]

    @internal
    def __incrPrefixCntrsForChilds(self, prefix, currentVal, parent):
        # [TODO] check if new name is not defined in any direction
        currentVal += 1
        self.cntrsForPrefixNames[prefix] = currentVal
        ch = self.getChild(parent)
        while ch:
            if prefix in ch.cntrsForPrefixNames:
                ch.cntrsForPrefixNames[prefix] = currentVal
                ch = ch.getChild(parent)
            else:
                # prefix is not registered at any child
                break

        usableName = prefix + str(currentVal)
        return usableName

    @internal
    def __registerName(self, name, obj, parent):
        # search if name is already defined on me and parents
        actual = self
        o = None

        if parent.ignorecase:
            _name = name.lower()
        else:
            _name = name

        while actual is not None:
            try:
                o = actual[_name]
            except KeyError:
                actual = actual.getParent(parent)
                continue
            break

        if o is None or o is obj:
            # we can use use the name, because it is not used
            self[_name] = obj
        else:
            raise NameOccupiedErr(o)

    def getUsableName(self, suggestedName, obj, parent):
        if not suggestedName.endswith("_"):
            try:
                self.__registerName(suggestedName, obj, parent)
                return suggestedName
            except NameOccupiedErr:
                suggestedName += "_"

        actual = self
        try:
            cntrVal = actual.cntrsForPrefixNames[suggestedName]
        except KeyError:
            cntrVal = -1

        # setup for me and propagate to children
        usableName = self.__incrPrefixCntrsForChilds(
            suggestedName, cntrVal, parent)
        self.__registerName(usableName, obj, parent)
        return usableName

class NameScope(list):
    """
    Scope of used names in hdl
    """

    def __init__(self, ignorecase):
        super().__init__()
        self.ignorecase = ignorecase

    def fork(self, lvl):
        f = self.__class__(self.ignorecase)
        for i in range(lvl):
            f.append(self[i])
        return f

    def setLevel(self, lvl):
        """
        Trim or extend scope
        lvl = 1 -> only one scope (global)
        """
        while len(self) != lvl:
            if len(self) > lvl:
                self.pop()
            else:
                self.append(NameScopeItem(len(self)))

    def checkedName(self, actualName, actualObj, isGlobal=False):
        if isGlobal:
            return self[0].getUsableName(actualName, actualObj, self)
        else:
            return self[-1].getUsableName(actualName, actualObj, self)


from hwt.hdl.types.arrayVal import HArrayVal
from hwt.hdl.value import Value

class SliceVal(HArrayVal):
    """
    Value class for Slice type
    """

    def _isFullVld(self):
        return self.val[0]._isFullVld() and self.val[1]._isFullVld()

    def toPy(self):
        """
        Convert to python slice object
        """
        return slice(int(self.val[0]), int(self.val[1]))

    def _size(self):
        """
        :return: how many bits is this slice selecting
        """
        assert isinstance(self, Value)
        return int(self.val[0]) - int(self.val[1])


from hwt.doc_markers import internal
from hwt.hdl.constants import INTF_DIRECTION

@internal
def autoAddAgents(unit):
    """
    Walk all interfaces on unit and instantiate agent for every interface.

    :return: all monitor/driver functions which should be added to simulation
         as processes
    """
    proc = []
    for intf in unit._interfaces:
        if not intf._isExtern:
            continue

        intf._initSimAgent()
        assert intf._ag is not None, intf
        agents = [intf._ag, ]

        if intf._direction == INTF_DIRECTION.MASTER:
            agProcs = list(map(lambda a: a.getMonitors(), agents))
        elif intf._direction == INTF_DIRECTION.SLAVE:
            agProcs = list(map(lambda a: a.getDrivers(), agents))
        else:
            raise NotImplementedError("intf._direction %r for %r" % (
                intf._direction, intf))

        for p in agProcs:
            proc.extend(p)

    return proc

def valuesToInts(values):
    """
    Iterable of values to ints (nonvalid = None)
    """
    res = []
    append = res.append
    for d in values:
        if isinstance(d, int):
            append(d)
        else:
            append(valToInt(d))
    return res

def valToInt(v):
    if v.vldMask == v._dtype.all_mask():
        return v.val
    else:
        return None

def agInts(interface):
    """
    Convert all values which has agent collected in time >=0 to integer array.
    Invalid value will be None.
    """
    return valuesToInts(interface._ag.data)


from hwt.synthesizer.interfaceLevel.interfaceUtils.utils import NotSpecified
from hwt.synthesizer.interfaceLevel.mainBases import UnitBase
from hwt.synthesizer.interfaceLevel.unitImplHelpers import getRst, getClk
from ipCorePackager.intfIpMeta import IntfIpMetaNotSpecified

class InterfaceceImplDependentFns():
    """
    Interface functions which have high potential to be overloaded
    in concrete interface implementation
    """

    def _getIpCoreIntfClass(self):
        raise IntfIpMetaNotSpecified()

    def _initSimAgent(self):
        raise NotSpecified("Override this function in your interface"
                           " implementation to have simultion agent"
                           " specified (%r)" % self)

    def _getAssociatedRst(self):
        """
        If interface has associated rst(_n) return it otherwise
        try to find rst(_n) on parent recursively
        """
        a = self._associatedRst

        if a is not None:
            return a

        p = self._parent
        assert p is not None

        if isinstance(p, UnitBase):
            return getRst(p)
        else:
            return p._getAssociatedRst()

    def _getAssociatedClk(self):
        """
        If interface has associated clk return it otherwise
        try to find clk on parent recursively
        """
        a = self._associatedClk

        if a is not None:
            return a

        p = self._parent
        assert p is not None

        if isinstance(p, UnitBase):
            return getClk(p)
        else:
            return p._getAssociatedClk()

    def _clone(self):
        """
        Create new instance of interface of same type and configuration
        """
        intf = self.__class__()
        intf._updateParamsFrom(self)
        return intf


from jinja2.environment import Environment
from jinja2.loaders import PackageLoader

from hwt.hdl.constants import DIRECTION
from hwt.hdl.entity import Entity
from hwt.interfaces.std import Clk
from hwt.serializer.generic.serializer import GenericSerializer
from hwt.serializer.generic.nameScope import LangueKeyword
from hwt.serializer.systemC.context import SystemCCtx
from hwt.serializer.systemC.keywords import SYSTEMC_KEYWORDS
from hwt.serializer.systemC.ops import SystemCSerializer_ops
from hwt.serializer.systemC.statements import SystemCSerializer_statements
from hwt.serializer.systemC.type import SystemCSerializer_type
from hwt.serializer.systemC.value import SystemCSerializer_value
from hwt.serializer.utils import maxStmId

class SystemCSerializer(SystemCSerializer_value, SystemCSerializer_type,
                        SystemCSerializer_statements, SystemCSerializer_ops,
                        GenericSerializer):
    """
    Serialized used to convert HWT design to SystemC code
    """
    fileExtension = '.cpp'
    _keywords_dict = {kw: LangueKeyword() for kw in SYSTEMC_KEYWORDS}
    env = Environment(loader=PackageLoader('hwt',
                                           'serializer/systemC/templates'))
    moduleTmpl = env.get_template('module.cpp.template')
    methodTmpl = env.get_template("method.cpp.template")
    ifTmpl = env.get_template("if.cpp.template")
    switchTmpl = env.get_template("switch.cpp.template")

    @classmethod
    def getBaseContext(cls):
        return SystemCCtx(cls.getBaseNameScope(), 0, None, None)

    @classmethod
    def comment(cls, comentStr):
        return "\n".join(["/*", comentStr, "*/"])

    @classmethod
    def PortItem(cls, p, ctx):
        d = cls.DIRECTION(p.direction)
        p.name = ctx.scope.checkedName(p.name, p)
        p.getInternSig().name = p.name
        if isinstance(p.getInternSig()._interface, Clk):
            return "sc_%s_clk %s;" % (d, p.name)

        return "sc_%s<%s> %s;" % (d,
                                  cls.HdlType(p._dtype, ctx),
                                  p.name)

    @classmethod
    def DIRECTION(cls, d):
        return d.name.lower()

    @classmethod
    def Entity(cls, ent, ctx):
        cls.Entity_prepare(ent, ctx)
        # [TODO] separate declarations from definitions
        doc = ent.__doc__
        if doc and id(doc) != id(Entity.__doc__):
            return cls.comment(doc) + "\n"
        return ""

    @classmethod
    def GenericItem(cls, g, ctx):
        # [TODO] params currently serialized evaluated
        return ""

    @classmethod
    def Architecture_var(cls, v, serializerVars, extraTypes,
                         extraTypes_serialized, ctx, childCtx):
        """
        :return: list of extra discovered processes
        """
        v.name = ctx.scope.checkedName(v.name, v)
        serializedVar = cls.SignalItem(v, childCtx, declaration=True)
        serializerVars.append(serializedVar)

    @classmethod
    def Architecture(cls, arch, ctx):
        serializerVars = []
        procs = []
        extraTypes = set()
        extraTypes_serialized = []
        arch.variables.sort(key=lambda x: (x.name, x._instId))
        arch.componentInstances.sort(key=lambda x: x._name)

        childCtx = ctx.withIndent()
        ports = list(map(lambda pi: cls.PortItem(
            pi, childCtx), arch.entity.ports))

        extraProcesses = []
        for v in arch.variables:
            cls.Architecture_var(v,
                                 serializerVars,
                                 extraTypes,
                                 extraTypes_serialized,
                                 ctx,
                                 childCtx)

        arch.processes.extend(extraProcesses)
        arch.processes.sort(key=lambda x: (x.name, maxStmId(x)))
        for p in arch.processes:
            procs.append(cls.HWProcess(p, childCtx))

        # architecture names can be same for different entities
        # arch.name = scope.checkedName(arch.name, arch, isGlobal=True)
        processesSensitivity = []
        sensitivityCtx = ctx.forSensitivityList()
        for p in arch.processes:
            sens = list(map(lambda s: cls.asHdl(
                s, sensitivityCtx), p.sensitivityList))
            processesSensitivity.append((p.name, sens))

        return cls.moduleTmpl.render(
            processesSensitivity=processesSensitivity,
            name=arch.getEntityName(),
            ports=ports,
            signals=serializerVars,
            extraTypes=extraTypes_serialized,
            processes=procs,
            processObjects=arch.processes,
            componentInstances=arch.componentInstances,
            DIRECTION=DIRECTION,
        )


# select = map, groupBy = itertools.groupby
from itertools import zip_longest
from math import inf
from types import GeneratorType

class DuplicitValueExc(Exception):
    """
    Exception which means that there are multiple items which this query
    selected but it should return only one
    """

class NoValueExc(Exception):
    """
    Exception which means that query did not selected any item
    """

def distinctBy(iterable, fn):
    """
    uniq operation with key selector
    """
    s = set()
    for i in iterable:
        r = fn(i)
        if r not in s:
            s.add(r)
            yield i

def single(iterable, fn):
    """
    Get value from iterable where fn(item) and check
    if there is not fn(other item)

    :raise DuplicitValueExc: when there are multiple items satisfying fn()
    :raise NoValueExc: when no value satisfying fn(item) found
    """
    found = False
    ret = None

    for i in iterable:
        if fn(i):
            if found:
                raise DuplicitValueExc(i)
            found = True
            ret = i

    if not found:
        raise NoValueExc()

    return ret

def arr_any(iterable, fn):
    """
    :return: True if fn(item) for any item else False
    """
    for item in iterable:
        if fn(item):
            return True
    return False

def arr_all(iterable, fn):
    """
    :return: True if fn(item) for all items in interable or iterable
        is empty else False
    """
    for item in iterable:
        if not fn(item):
            return False
    return True

def take(iterrable, howMay):
    """
    :return: generator of first n items from iterrable
    """
    assert howMay >= 0

    if not howMay:
        return

    last = howMay - 1
    for i, item in enumerate(iterrable):
        yield item
        if i == last:
            return

def where(iterable, fn):
    """
    :return: generator of items from iterable where fn(item)
    """
    for i in iterable:
        if fn(i):
            yield i

def iter_with_last(iterable):
    """
    :return: generator of tuples (isLastFlag, item)
    """
    # Ensure it's an iterator and get the first field
    iterable = iter(iterable)
    prev = next(iterable)
    for item in iterable:
        # Lag by one item so I know I'm not at the end
        yield False, prev
        prev = item
    # Last item
    yield True, prev

def groupedby(collection, fn):
    """
    same like itertools.groupby

    :note: This function does not needs initial sorting like itertools.groupby

    :attention: Order of pairs is not deterministic.
    """
    d = {}
    for item in collection:
        k = fn(item)
        try:
            arr = d[k]
        except KeyError:
            arr = []
            d[k] = arr
        arr.append(item)

    yield from d.items()

def flatten(iterables, level=inf):
    """
    Flatten nested lists, tuples, generators and maps

    :param level: maximum depth of flattening
    """
    if level >= 0 and isinstance(iterables, (list, tuple, GeneratorType,
                                             map, zip)):
        level -= 1
        for i in iterables:
            yield from flatten(i, level=level)
    else:
        yield iterables

def grouper(n, iterable, padvalue=None):
    """grouper(3, 'abcdefg', 'x') -->
       ('a','b','c'), ('d','e','f'), ('g','x','x')
    """
    return zip_longest(*[iter(iterable)] * n, fillvalue=padvalue)

def areSetsIntersets(setA, setB):
    """
    Check if intersection of sets is not empty
    """
    return any(x in setA for x in setB)

def balanced_reduce(arr, opFn):
    while len(arr) > 1:
        nextArr = []
        for a, b in grouper(2, arr):
            nextArr.append(opFn(a, b))
        arr = nextArr

    return arr[0]


from functools import reduce
from itertools import compress
from operator import and_
from typing import List, Tuple, Dict, Union

from hwt.hdl.sensitivityCtx import SensitivityCtx
from hwt.hdl.statementUtils import fill_stm_list_with_enclosure
from hwt.hdl.statements import HdlStatement, statementsAreSame,\
    isSameStatementList, seqEvalCond
from hwt.hdl.value import Value
from hwt.synthesizer.rtlLevel.mainBases import RtlSignalBase
from hwt.doc_markers import internal

class IfContainer(HdlStatement):
    """
    Structural container of if statement for hdl rendering

    :ivar _ifTrue_enclosed_for: set of signals for which if ifTrue branch enclosed
            (has not branch where signal is not assignment)
    :ivar _elIfs_enclosed_for: list of sets of enclosed signals for each elif
    :ivar _ifFalse_enclosed_for: set of enclosed signals for ifFalse branch
    """

    def __init__(self, cond, ifTrue=None, ifFalse=None, elIfs=None,
                 parentStm=None, is_completly_event_dependent=False):
        """
        :param cond: RtlSignal as conditions for this if
        :param ifTrue: list of statements which should be active if cond.
            is met
        :param elIfs: list of tuples (list of conditions, list of statements)
        :param ifFalse: list of statements which should be active if cond.
            and any other cond. in elIfs is met
        """
        assert isinstance(cond, RtlSignalBase)
        self.cond = cond
        super(IfContainer, self).__init__(
            parentStm,
            is_completly_event_dependent=is_completly_event_dependent)

        if ifTrue is None:
            self.ifTrue = []
        else:
            self.ifTrue = ifTrue

        if elIfs is None:
            self.elIfs = []
        else:
            self.elIfs = elIfs

        self.ifFalse = ifFalse
        self._ifTrue_enclosed_for = None
        self._elIfs_enclosed_for = None
        self._ifFalse_enclosed_for = None

    @internal
    def _collect_io(self):
        raise NotImplementedError()

    @internal
    def _clean_signal_meta(self):
        self._sensitivity = None
        self._ifTrue_enclosed_for = None
        self._elIfs_enclosed_for = None
        self._ifFalse_enclosed_for = None
        HdlStatement._clean_signal_meta(self)

    @internal
    def _cut_off_drivers_of(self, sig: RtlSignalBase):
        """
        Doc on parent class :meth:`HdlStatement._cut_off_drivers_of`
        """
        if len(self._outputs) == 1 and sig in self._outputs:
            self.parentStm = None
            return self

        # try to cut off all statements which are drivers of specified signal
        # in all branches
        child_keep_mask = []

        newIfTrue = []
        all_cut_off = True
        all_cut_off &= self._cut_off_drivers_of_list(
            sig, self.ifTrue, child_keep_mask, newIfTrue)
        self.ifTrue = list(compress(self.ifTrue, child_keep_mask))

        newElifs = []
        anyElifHit = False
        for cond, stms in self.elIfs:
            newCase = []
            child_keep_mask.clear()
            all_cut_off &= self._cut_off_drivers_of_list(
                sig, stms, child_keep_mask, newCase)

            _stms = list(compress(stms, child_keep_mask))
            stms.clear()
            stms.extend(_stms)

            if newCase:
                anyElifHit = True
            newElifs.append((cond, newCase))

        newIfFalse = None
        if self.ifFalse:
            newIfFalse = []
            child_keep_mask.clear()
            all_cut_off &= self._cut_off_drivers_of_list(
                sig, self.ifFalse, child_keep_mask, newIfFalse)
            self.ifFalse = list(compress(self.ifFalse, child_keep_mask))

        assert not all_cut_off, "everything was cut of but this should be already known at start"

        if newIfTrue or newIfFalse or anyElifHit or newIfFalse:
            # parts were cut off
            # generate new statement for them
            cond_sig = self.cond
            n = self.__class__(cond_sig, newIfTrue)
            for c, stms in newElifs:
                assert len(c) == 1
                c_sig = c[0]
                n.Elif(c_sig, stms)
            if newIfFalse is not None:
                n.Else(newIfFalse)

            if self.parentStm is None:
                ctx = n._get_rtl_context()
                ctx.statements.add(n)

            # update io of this
            self._inputs.clear()
            self._inputs.append(cond_sig)
            for c, _ in self.elIfs:
                self._inputs.extend(c)

            self._inputs.append(cond_sig)
            self._outputs.clear()

            out_add = self._outputs.append
            in_add = self._inputs.append

            for stm in self._iter_stms():
                for inp in stm._inputs:
                    in_add(inp)

                for outp in stm._outputs:
                    out_add(outp)

            if self._sensitivity is not None or self._enclosed_for is not None:
                raise NotImplementedError(
                    "Sensitivity and enclosure has to be cleaned first")

            return n

    @internal
    def _discover_enclosure(self):
        """
        Doc on parent class :meth:`HdlStatement._discover_enclosure`
        """
        outputs = self._outputs
        self._ifTrue_enclosed_for = self._discover_enclosure_for_statements(
            self.ifTrue, outputs)

        elif_encls = self._elIfs_enclosed_for = []
        for _, stms in self.elIfs:
            e = self._discover_enclosure_for_statements(
                stms, outputs)
            elif_encls.append(e)

        self._ifFalse_enclosed_for = self._discover_enclosure_for_statements(
            self.ifFalse, outputs)

        assert self._enclosed_for is None
        encl = self._enclosed_for = set()

        for s in self._ifTrue_enclosed_for:
            enclosed = True

            for elif_e in elif_encls:
                if s not in elif_e:
                    enclosed = False
                    break

            if enclosed and s in self._ifFalse_enclosed_for:
                encl.add(s)

    @internal
    def _discover_sensitivity(self, seen: set) -> None:
        """
        Doc on parent class :meth:`HdlStatement._discover_sensitivity`
        """
        assert self._sensitivity is None, self
        ctx = self._sensitivity = SensitivityCtx()

        self._discover_sensitivity_sig(self.cond, seen, ctx)
        if ctx.contains_ev_dependency:
            return

        for stm in self.ifTrue:
            stm._discover_sensitivity(seen)
            ctx.extend(stm._sensitivity)

        # elifs
        for cond, stms in self.elIfs:
            if ctx.contains_ev_dependency:
                break

            self._discover_sensitivity_sig(cond, seen, ctx)
            if ctx.contains_ev_dependency:
                break

            for stm in stms:
                if ctx.contains_ev_dependency:
                    break

                stm._discover_sensitivity(seen)
                ctx.extend(stm._sensitivity)

        if not ctx.contains_ev_dependency and self.ifFalse:
            # else
            for stm in self.ifFalse:
                stm._discover_sensitivity(seen)
                ctx.extend(stm._sensitivity)

        else:
            assert not self.ifFalse, "can not negate event"

    @internal
    def _fill_enclosure(self, enclosure: Dict[RtlSignalBase, Union[Value, RtlSignalBase]]) -> None:
        enc = []
        outputs = self._outputs
        for e in enclosure.keys():
            if e in outputs and e not in self._enclosed_for:
                enc.append(e)

        if not enc:
            return
        fill_stm_list_with_enclosure(self, self._ifTrue_enclosed_for,
                                     self.ifTrue, enc, enclosure)

        for (_, stms), e in zip(self.elIfs, self._elIfs_enclosed_for):
            fill_stm_list_with_enclosure(self, e, stms, enc, enclosure)

        self.ifFalse = fill_stm_list_with_enclosure(self, self._ifFalse_enclosed_for,
                                                    self.ifFalse, enc, enclosure)

        self._enclosed_for.update(enc)

    def _iter_stms(self):
        """
        Doc on parent class :meth:`HdlStatement._iter_stms`
        """
        yield from self.ifTrue
        for _, stms in self.elIfs:
            yield from stms
        if self.ifFalse is not None:
            yield from self.ifFalse

    @internal
    def _try_reduce(self) -> Tuple[bool, List[HdlStatement]]:
        """
        Doc on parent class :meth:`HdlStatement._try_reduce`
        """
        # flag if IO of statement has changed
        io_change = False

        self.ifTrue, rank_decrease, _io_change = self._try_reduce_list(
            self.ifTrue)
        self.rank -= rank_decrease
        io_change |= _io_change

        new_elifs = []
        for cond, statements in self.elIfs:
            _statements, rank_decrease, _io_change = self._try_reduce_list(
                statements)
            self.rank -= rank_decrease
            io_change |= _io_change
            new_elifs.append((cond, _statements))

        if self.ifFalse is not None:
            self.ifFalse, rank_decrease, _io_update_required = self._try_reduce_list(
                self.ifFalse)
            self.rank -= rank_decrease
            io_change |= _io_change

        reduce_self = not self.condHasEffect(
            self.ifTrue, self.ifFalse, self.elIfs)

        if reduce_self:
            res = self.ifTrue
        else:
            res = [self, ]

        self._on_reduce(reduce_self, io_change, res)

        # try merge nested ifs as elifs
        if self.ifFalse is not None and len(self.ifFalse) == 1:
            child = self.ifFalse[0]
            if isinstance(child, IfContainer):
                self._merge_nested_if_from_else(child)

        return res, io_change

    @internal
    def _merge_nested_if_from_else(self, ifStm: "IfContainer"):
        """
        Merge nested IfContarner form else branch to this IfContainer
        as elif and else branches
        """
        self.elIfs.append((ifStm.cond, ifStm.ifTrue))
        self.elIfs.extend(ifStm.elIfs)

        self.ifFalse = ifStm.ifFalse

    @internal
    def _is_mergable(self, other: HdlStatement) -> bool:
        if not isinstance(other, IfContainer):
            return False

        if (self.cond is not other.cond
                or not self._is_mergable_statement_list(self.ifTrue, other.ifTrue)):
            return False

        if len(self.elIfs) != len(other.elIfs):
            return False

        for (a_c, a_stm), (b_c, b_stm) in zip(self.elIfs, other.elIfs):
            if a_c is not b_c or self._is_mergable_statement_list(a_stm, b_stm):
                return False

        if not self._is_mergable_statement_list(self.ifFalse, other.ifFalse):
            return False
        return True

    @internal
    def _merge_with_other_stm(self, other: "IfContainer") -> None:
        """
        :attention: statements has to be mergable (to check use _is_mergable method)
        """
        merge = self._merge_statement_lists
        self.ifTrue = merge(self.ifTrue, other.ifTrue)

        new_elifs = []
        for ((c, elifA), (_, elifB)) in zip(self.elIfs, other.elIfs):
            new_elifs.append((c,  merge(elifA, elifB)))
        self.elIfs = new_elifs

        self.ifFalse = merge(self.ifFalse, other.ifFalse)

        other.ifTrue = []
        other.elIfs = []
        other.ifFalse = None

        self._on_merge(other)

    @internal
    @staticmethod
    def condHasEffect(ifTrue, ifFalse, elIfs):
        stmCnt = len(ifTrue)
        if ifFalse is not None \
                and stmCnt == len(ifFalse) \
                and reduce(and_,
                           [len(stm) == stmCnt
                            for _, stm in elIfs],
                           True):
            for stms in zip(ifTrue, ifFalse, *map(lambda x: x[1], elIfs)):
                if not statementsAreSame(stms):
                    return True
            return False
        return True

    def isSame(self, other: HdlStatement) -> bool:
        """
        :return: True if other has same meaning as this statement
        """
        if self is other:
            return True

        if self.rank != other.rank:
            return False

        if isinstance(other, IfContainer):
            if self.cond is other.cond:
                if len(self.ifTrue) == len(other.ifTrue) \
                        and len(self.ifFalse) == len(other.ifFalse) \
                        and len(self.elIfs) == len(other.elIfs):
                    if not isSameStatementList(self.ifTrue,
                                               other.ifTrue) \
                            or not isSameStatementList(self.ifFalse,
                                                       other.ifFalse):
                        return False
                    for (ac, astms), (bc, bstms) in zip(self.elIfs,
                                                        other.elIfs):
                        if not (ac == bc) or\
                                not isSameStatementList(astms, bstms):
                            return False
                    return True
        return False

    @internal
    def seqEval(self):
        if seqEvalCond(self.cond):
            for s in self.ifTrue:
                s.seqEval()
        else:
            for c in self.elIfs:
                if seqEvalCond(c[0]):
                    for s in c[1]:
                        s.seqEval()
                    return

            for s in self.ifFalse:
                s.seqEval()

    def __repr__(self):
        from hwt.serializer.hwt.serializer import HwtSerializer
        ctx = HwtSerializer.getBaseContext()
        return HwtSerializer.IfContainer(self, ctx)


from itertools import islice

from hwt.hdl.assignment import Assignment
from hwt.hdl.operator import Operator
from hwt.hdl.statements import IncompatibleStructure, HdlStatement
from hwt.hdl.value import Value
from hwt.pyUtils.arrayQuery import areSetsIntersets, groupedby
from hwt.serializer.utils import maxStmId
from hwt.hdl.process import HWProcess
from hwt.doc_markers import internal

@internal
def removeUnconnectedSignals(netlist):
    """
    If signal is not driving anything remove it
    """

    toDelete = set()
    toSearch = netlist.signals

    while toSearch:
        _toSearch = set()

        for sig in toSearch:
            if not sig.endpoints:
                try:
                    if sig._interface is not None:
                        # skip interfaces before we want to check them,
                        # they should not be optimized out from design
                        continue
                except AttributeError:
                    pass

                for e in sig.drivers:
                    # drivers of this signal are useless rm them
                    if isinstance(e, Operator):
                        inputs = e.operands
                        if e.result is sig:
                            e.result = None
                    else:
                        inputs = e._inputs
                        netlist.statements.discard(e)

                    for op in inputs:
                        if not isinstance(op, Value):
                            try:
                                op.endpoints.remove(e)
                            except KeyError:
                                # this operator has 2x+ same operand
                                continue

                            _toSearch.add(op)

                toDelete.add(sig)

        if toDelete:
            for sig in toDelete:
                if sig.ctx == netlist:
                    netlist.signals.remove(sig)
                _toSearch.discard(sig)
            toDelete = set()
        toSearch = _toSearch

@internal
def checkIfIsTooSimple(proc):
    """check if process is just unconditional assignments
       and it is useless to merge them"""
    try:
        a, = proc.statements
        if isinstance(a, Assignment):
            return True
    except ValueError:
        pass
    return False

@internal
def tryToMerge(procA: HWProcess, procB: HWProcess):
    """
    Try merge procB into procA

    :raise IncompatibleStructure: if merge is not possible
    :attention: procA is now result if merge has succeed
    :return: procA which is now result of merge
    """
    if (checkIfIsTooSimple(procA) or
            checkIfIsTooSimple(procB) or
            areSetsIntersets(procA.outputs, procB.sensitivityList) or
            areSetsIntersets(procB.outputs, procA.sensitivityList) or
            not HdlStatement._is_mergable_statement_list(procA.statements, procB.statements)):
        raise IncompatibleStructure()

    procA.statements = HdlStatement._merge_statement_lists(
        procA.statements, procB.statements)

    procA.outputs.extend(procB.outputs)
    procA.inputs.extend(procB.inputs)
    procA.sensitivityList.extend(procB.sensitivityList)

    return procA

@internal
def reduceProcesses(processes):
    """
    Try to merge processes as much is possible

    :param processes: list of processes instances
    """
    # sort to make order of merging same deterministic
    processes.sort(key=lambda x: (x.name, maxStmId(x)), reverse=True)
    # now try to reduce processes with nearly same structure of statements into one
    # to minimize number of processes
    for _, procs in groupedby(processes, lambda p: p.rank):
        for iA, pA in enumerate(procs):
            if pA is None:
                continue
            for iB, pB in enumerate(islice(procs, iA + 1, None)):
                if pB is None:
                    continue

                try:
                    pA = tryToMerge(pA, pB)
                except IncompatibleStructure:
                    continue
                procs[iA + 1 + iB] = None
                # procs[iA] = pA

        for p in procs:
            if p is not None:
                yield p


from collections import deque

from hwt.hdl.constants import READ, WRITE, NOP
from hwt.simulator.agentBase import SyncAgentBase
from hwt.simulator.shortcuts import oscilate

class BramPort_withoutClkAgent(SyncAgentBase):
    """
    :ivar requests: list of tuples (request type, address, [write data])
        - used for driver
    :ivar data: list of data in memory, used for monitor
    :ivar mem: if agent is in monitor mode (= is slave) all reads and writes
        are performed on mem object
    """

    def __init__(self, intf):
        super().__init__(intf, allowNoReset=True)

        self.requests = deque()
        self.readPending = False
        self.readed = deque()

        self.mem = {}
        self.requireInit = True

    def doReq(self, sim, req):
        rw = req[0]
        addr = req[1]

        if rw == READ:
            rw = 0
            wdata = None
            self.readPending = True
            if self._debugOutput is not None:
                self._debugOutput.write("%s, after %r read_req: %d\n" % (
                                        self.intf._getFullName(),
                                        sim.now, addr))
        elif rw == WRITE:
            wdata = req[2]
            rw = 1
            if self._debugOutput is not None:
                self._debugOutput.write("%s, after %r write: %d:%d\n" % (
                                        self.intf._getFullName(), sim.now,
                                        addr, wdata))

        else:
            raise NotImplementedError(rw)

        intf = self.intf
        w = sim.write
        w(rw, intf.we)
        w(addr, intf.addr)
        w(wdata, intf.din)

    def onReadReq(self, sim, addr):
        """
        on readReqRecieved in monitor mode
        """
        self.requests.append((READ, addr))

    def onWriteReq(self, sim, addr, data):
        """
        on writeReqRecieved in monitor mode
        """
        self.requests.append((WRITE, addr, data))

    def monitor(self, sim):
        intf = self.intf

        yield sim.waitOnCombUpdate()
        # now we are after clk edge
        if self.notReset(sim):
            en = sim.read(intf.en)
            assert en.vldMask
            if en.val:
                we = sim.read(intf.we)
                assert we.vldMask

                addr = sim.read(intf.addr)
                if we.val:
                    data = sim.read(intf.din)
                    self.onWriteReq(sim, addr, data)
                else:
                    self.onReadReq(sim, addr)

        if self.requests:
            req = self.requests.popleft()
            t = req[0]
            addr = req[1]
            assert addr._isFullVld(), sim.now
            if t == READ:
                sim.write(self.mem[addr.val], intf.dout)
            else:
                assert t == WRITE
                sim.write(None, intf.dout)
                self.mem[addr.val] = req[2]

    def driver(self, sim):
        intf = self.intf
        w = sim.write
        if self.requireInit:
            w(0, intf.en)
            w(0, intf.we)
            self.requireInit = False

#        yield s.wait(2000)
        readPending = self.readPending
        if self.requests and self.notReset(sim):
            req = self.requests.popleft()
            if req is NOP:
                w(0, intf.en)
                w(0, intf.we)
                self.readPending = False
            else:
                self.doReq(sim, req)
                w(1, intf.en)
        else:
            w(0, intf.en)
            w(0, intf.we)
            self.readPending = False

        if readPending:
            yield sim.waitOnCombUpdate()
            # now we are after clk edge
            d = sim.read(intf.dout)
            self.readed.append(d)
            if self._debugOutput is not None:
                self._debugOutput.write("%s, on %r read_data: %d\n" % (
                                        self.intf._getFullName(),
                                        sim.now, d.val))

class BramPortAgent(BramPort_withoutClkAgent):

    def getDrivers(self):
        drivers = super(BramPortAgent, self).getDrivers()
        drivers.append(oscilate(self.intf.clk))
        return drivers


from jinja2.environment import Environment
from jinja2.loaders import PackageLoader

from hwt.hdl.architecture import Architecture
from hwt.hdl.assignment import Assignment
from hwt.hdl.constants import SENSITIVITY, DIRECTION
from hwt.hdl.entity import Entity
from hwt.hdl.ifContainter import IfContainer
from hwt.hdl.operator import Operator
from hwt.hdl.operatorDefs import AllOps
from hwt.hdl.process import HWProcess
from hwt.hdl.switchContainer import SwitchContainer
from hwt.hdl.types.enum import HEnum
from hwt.hdl.types.enumVal import HEnumVal
from hwt.hdl.value import Value
from hwt.serializer.exceptions import SerializerException
from hwt.serializer.generic.constCache import ConstCache
from hwt.serializer.generic.indent import getIndent
from hwt.serializer.generic.nameScope import LangueKeyword
from hwt.serializer.generic.serializer import GenericSerializer
from hwt.serializer.hwt.keywords import HWT_KEYWORDS
from hwt.serializer.hwt.ops import HwtSerializer_ops
from hwt.serializer.hwt.types import HwtSerializer_types
from hwt.serializer.hwt.value import HwtSerializer_value
from hwt.serializer.utils import maxStmId
from hwt.synthesizer.param import evalParam
from hwt.synthesizer.rtlLevel.mainBases import RtlSignalBase
from hwt.serializer.hwt.context import HwtSerializerCtx

env = Environment(loader=PackageLoader('hwt', 'serializer/hwt/templates'))
unitHeadTmpl = env.get_template('unit_head.py.template')
unitBodyTmpl = env.get_template('unit_body.py.template')
processTmpl = env.get_template('process.py.template')
ifTmpl = env.get_template("if.py.template")

class HwtSerializer(HwtSerializer_value, HwtSerializer_ops,
                    HwtSerializer_types, GenericSerializer):
    """
    Serializer which converts Hwt objects back to Hwt code
    for debugging purposes
    """
    _keywords_dict = {kw: LangueKeyword() for kw in HWT_KEYWORDS}
    fileExtension = '.py'

    @classmethod
    def serializationDecision(cls, obj, serializedClasses,
                              serializedConfiguredUnits):
        return True

    @classmethod
    def getBaseContext(cls):
        return HwtSerializerCtx(cls.getBaseNameScope(), 0, None, None)

    @classmethod
    def asHdl(cls, obj, ctx: HwtSerializerCtx):
        """
        Convert object to HDL string

        :param obj: object to serialize
        :param ctx: HwtSerializerCtx instance
        """
        if isinstance(obj, RtlSignalBase):
            return cls.SignalItem(obj, ctx)
        elif isinstance(obj, Value):
            return cls.Value(obj, ctx)
        else:
            try:
                serFn = obj.asHwt
            except AttributeError:
                serFn = None
            if serFn is not None:
                return serFn(cls, ctx)

            try:
                serFn = getattr(cls, obj.__class__.__name__)
            except AttributeError:
                serFn = None

            if serFn is not None:
                return serFn(obj, ctx)

            raise SerializerException("Not implemented for %r" % (obj))

    @classmethod
    def stmAsHdl(cls, obj, ctx: HwtSerializerCtx):
        try:
            serFn = getattr(cls, obj.__class__.__name__)
        except AttributeError:
            raise NotImplementedError("Not implemented for %s" % (repr(obj)))
        return serFn(obj, ctx)

    @classmethod
    def Entity(cls, ent: Entity, ctx: HwtSerializerCtx):
        """
        Entity is just forward declaration of Architecture, it is not used
        in most HDL languages as there is no recursion in hierarchy
        """

        cls.Entity_prepare(ent, ctx, serialize=False)
        ent.name = ctx.scope.checkedName(ent.name, ent, isGlobal=True)
        ports = list(
            map(lambda p: (p.name, cls.HdlType(p._dtype, ctx)),
                ent.ports))
        return unitHeadTmpl.render(
            name=ent.name,
            ports=ports,
        )

    @classmethod
    def Architecture(cls, arch: Architecture, ctx: HwtSerializerCtx):
        variables = []
        procs = []
        extraTypes = set()
        extraTypes_serialized = []
        arch.variables.sort(key=lambda x: (x.name, x._instId))
        arch.processes.sort(key=lambda x: (x.name, maxStmId(x)))
        arch.componentInstances.sort(key=lambda x: x._name)

        for v in arch.variables:
            t = v._dtype
            # if type requires extra definition
            if isinstance(t, HEnum) and t not in extraTypes:
                extraTypes.add(v._dtype)
                extraTypes_serialized.append(
                    cls.HdlType(t, ctx, declaration=True))

            v.name = ctx.scope.checkedName(v.name, v)
            variables.append(v)

        childCtx = ctx.withIndent(2)
        childCtx.constCache = ConstCache(ctx.scope.checkedName)

        def serializeVar(v):
            dv = evalParam(v.defVal)
            if isinstance(dv, HEnumVal):
                dv = "%s.%s" % (dv._dtype.name, dv.val)
            else:
                dv = cls.Value(dv, childCtx)

            return v.name, cls.HdlType(v._dtype, childCtx), dv

        for p in arch.processes:
            procs.append(cls.HWProcess(p, childCtx))

        constants = []
        const_cache = childCtx.constCache
        childCtx.constCache = None
        for cVal, cName in sorted(const_cache._cache.items(),
                                  key=lambda x: x[1],
                                  reverse=True):
            constants.append((cName, cls.Value(cVal, childCtx)))

        portNames = [p.name for p in arch.entity.ports]
        portToLocalsRow = "%s = %s" % (
            ", ".join(portNames),
            ", ".join(["self." + n for n in portNames]))

        return unitBodyTmpl.render(
            DIRECTION_IN=DIRECTION.IN,
            name=arch.getEntityName(),
            portToLocalsRow=portToLocalsRow,
            constants=constants,
            signals=list(map(serializeVar, variables)),
            extraTypes=extraTypes_serialized,
            processes=procs,
            componentInstances=arch.componentInstances,
        )

    @classmethod
    def Assignment(cls, a: Assignment, ctx: HwtSerializerCtx):
        dst = a.dst
        indentStr = getIndent(ctx.indent)
        srcStr = "%s" % cls.Value(a.src, ctx)
        indexes = a.indexes
        if indexes is None:
            indexes = []

        return "%s%s%s(%s)" % (
            indentStr, dst.name,
            "".join(map(lambda x: "[%s]" % cls.asHdl(x, ctx),
                        indexes)),
            srcStr
        )

    @classmethod
    def comment(cls, coment: str):
        return "#" + coment.replace("\n", "\n#")

    @classmethod
    def IfContainer(cls, ifc: IfContainer, ctx: HwtSerializerCtx):
        cond = cls.condAsHdl(ifc.cond, ctx)
        ifTrue = ifc.ifTrue
        ifFalse = ifc.ifFalse

        if ifFalse is None:
            ifFalse = []

        childCtx = ctx.withIndent()

        def serialize_statements(statements):
            return [cls.stmAsHdl(obj, childCtx)
                    for obj in statements]

        def serialize_elif(elifCase):
            cond, statements = elifCase
            return (cls.condAsHdl(cond, ctx),
                    serialize_statements(statements))

        return ifTmpl.render(
            indent=getIndent(ctx.indent),
            indentNum=ctx.indent,
            cond=cond,
            ifTrue=serialize_statements(ifTrue),
            elIfs=[serialize_elif(elIf) for elIf in ifc.elIfs],
            ifFalse=serialize_statements(ifFalse)
        )

    @classmethod
    def SwitchContainer(cls, sw: SwitchContainer,
                        ctx: HwtSerializerCtx):
        switchOn = sw.switchOn
        if not sw.cases:
            # this should be usually reduced, but can appear while debugging
            if sw.default:
                return "\n".join([cls.asHdl(obj, ctx) for obj in sw.default])
            else:
                return cls.comment(" fully reduced switch on %s"
                                   % cls.asHdl(switchOn, ctx))

        def mkCond(c):
            return switchOn._eq(c)

        elIfs = []

        for key, statements in sw.cases[1:]:
            elIfs.append((mkCond(key), statements))
        ifFalse = sw.default

        topCond = mkCond(sw.cases[0][0])
        topIf = IfContainer(topCond,
                            sw.cases[0][1],
                            ifFalse,
                            elIfs)

        return cls.IfContainer(topIf, ctx)

    @classmethod
    def sensitivityListItem(cls, item):
        if isinstance(item, Operator):
            op = item.operator
            if op == AllOps.RISING_EDGE:
                sens = SENSITIVITY.RISING
            elif op == AllOps.FALLING_EDGE:
                sens = SENSITIVITY.FALLING
            else:
                raise TypeError("This is not an event sensitivity", op)

            return "(%s, %s)" % (str(sens), item.operands[0].name)
        else:
            return item.name

    @classmethod
    def HWProcess(cls, proc: HWProcess, ctx: HwtSerializerCtx):
        body = proc.statements
        assert body
        proc.name = ctx.scope.checkedName(proc.name, proc)
        sensitivityList = sorted(
            map(cls.sensitivityListItem, proc.sensitivityList))

        _body = "\n".join([cls.stmAsHdl(stm, ctx)
                           for stm in body])

        return processTmpl.render(
            indent=getIndent(ctx.indent),
            name=proc.name,
            sensitivityList=sensitivityList,
            stmLines=[_body]
        )