from collections import namedtuple
import io
import itertools
import os
import os.path
import subprocess


def gprbuild(project='default.gpr'):
    """Compile the given project using GPRbuild."""
    subprocess.check_call(['gprbuild',
        '-q', '-m', '-p', '-g', '-we',
        '-P{0}'.format(project)])


class Chdir(object):
    """
    Context manager used to temporarily change the current directory.
    """

    def __init__(self, dirname):
        """
        Create a Chdir context manager.

        When entered, this context manager will change the current directory to
        DIRNAME. The previous current directory is restored when leaving this
        context manager.
        """
        self.requested_dir = dirname

    def __enter__(self):
        self.old_dir = os.getcwd()
        os.chdir(self.requested_dir)

    def __exit__(self, exc_type, exc_value, traceback):
        os.chdir(self.old_dir)


#
# Actions to be done by the MultipleRegions' test driver.
#

# Command use to create a mapped region. "offset" is relative to the beginning
# of the file, starting at 0.
_Map = namedtuple('Map', 'offset length mutable')
def Map(offset, length, mutable=False):
    return _Map(offset, length, mutable)

# Likewise, but remap the last created mapped region.
_Remap = namedtuple('Remap', 'offset length mutable')
def Remap(offset, length, mutable=False):
    return _Remap(offset, length, mutable)

# Fill the last mapped region with "filler" repeated as need. "offset" is
# relative to the begging of the mapped region, starting at 1. This pecularity
# is used to reflect the underlying "Str_Access" bounds.
# Note that this can be used with "mutable" regions on reading files.
Fill = namedtuple('Fill', 'offset length filler')


# Expected attributes of a mapped region.
Region = namedtuple('Region', 'offset last size mutable content')


def interpret_actions(filename, mode, method, is_mmap_available, actions):
    """
    Interpet ACTIONS on the given FILENAME, return the resulting regions and
    the content of the resulting file.
    """

    result = []
    f = io.BytesIO(open(filename, 'r').read())

    write = mode == 'write'
    mmap = method == 'mmap'

    def map_region(offset, length, mutable):
        f.seek(offset)
        content = f.read(length)
        size = len(content)
        last = max(0, offset + size)
        mutable = mutable or write or not mmap or not is_mmap_available
        return Region(offset, last, size, mutable, content)

    for action in actions:
        if isinstance(action, _Map):
            result.append(map_region(
                action.offset, action.length,
                action.mutable))

        elif isinstance(action, _Remap):
            result[-1] = map_region(
                action.offset, action.length,
                action.mutable)

        elif isinstance(action, Fill):
            region = result[-1]

            # Writing overflow is not catched by GNATCOLL.Mmap: this is a real
            # error.
            if action.offset + action.length - 1 > region.size:
                raise ValueError('{0} goes out of {1}'.format(action, region))

            f.seek(region.offset + action.offset - 1)
            bounded_filler = ''.join(itertools.islice(
                    itertools.cycle(action.filler),
                    0, action.length
            ))
            f.write(bounded_filler)

            # The only attribute that must have changed is the content.
            result[-1] = map_region(region.offset, region.size, region.mutable)

        else:
            raise ValueError('Invalid action: {0}'.format(action))

    return result, f.getvalue()


def format_region(i, filename, region):
    result = [
        '== {0} - {1}: region from {2.offset} to {2.last} ==',
        '\nSize: {2.size}',
    ]
    if region.size:
        result.append('\nMutable: {3}')
    result.append('\n{2.content}\n\n')

    return ''.join(result).format(
        i, filename, region,
        str(region.mutable).upper()
    )

def format_regions(filename, regions):
    return ''.join(
        format_region(i, filename, region)
        for i, region in enumerate(regions, 1)
    )


def actions_to_args(actions):
    """
    Convert a list of actions to arguments (a list of strings) to be provided
    to the test driver's command line.
    """

    def helper(action):
        return [type(action).__name__.lower()] + [
            str(field) for field in action
        ]

    return sum(map(helper, actions), [])
