from typing import Dict
from typing import Any
from typing import Tuple
from typing import List


def is_valid(data: Dict[str, Any]) -> Tuple[bool, List[str]]:
    """
    Verify that the `data` (which should be obtained from a YAML file using
    `load_data()` contains a well-formed List of disjunct memory regions ordered
    by increasing addresses.

    Returns a tuple of a `bool` and a list of strings.  The list is empty if
    there were no problems, and describes one validation issue per element
    otherwise.

    >>> is_valid(None)
    (False, ['no data in file'])
    >>> is_valid({'devices': [{'end': 9699328, 'start': 9437184}]})
    (False, ['no description of memory in file (no "memory" key)'])
    >>> is_valid({'memory': 1})
    (False, ['bad description of memory in file ("memory" is not a list)'])
    >>> is_valid({'memory': []})
    (False, ['memory described as empty in file (list is zero-length)'])
    >>> is_valid({'memory': [{'end': 1342177280, 'start': 268435456}]})
    (True, [])
    >>> is_valid({'memory': [{'end': 1342177280}]})
    (False, ['region 0 is missing its start bound'])
    >>> is_valid({'memory': [{'start': 268435456}]})
    (False, ['region 0 is missing its end bound'])
    >>> is_valid({'memory': [{'junk': 'foo'}]})
    (False, ['region 0 is missing its start bound', 'region 0 is missing its end bound'])
    >>> is_valid({'memory': [{'start': 'foo'}]})
    (False, ['region start "foo" is not an integer', 'region 0 is missing its end bound'])
    >>> is_valid({'memory': [{'start': 'foo', 'end': 'bar'}]})
    (False, ['region start "foo" is not an integer', 'region end "bar" is not an integer'])
    >>> is_valid({'memory': [{'start': 2048, 'end': 1024}]})
    (False, ['region bounds are not in strictly increasing order (1024 not > 2048)'])
    >>> is_valid({'memory': [{'end': 4095, 'start': 0}, {'end': 65535, 'start': 32768}, {'end': 1342177280, 'start': 268435456}]})
    (True, [])
    >>> is_valid({'memory': [{'end': 4095, 'start': 0}, {'end': 65535, 'start': 32768}, {'end': 1342177280, 'start': 268435456}, {'end': 16384, 'start': 32768}]})
    (False, ['region bounds are not in strictly increasing order (32768 not > 1342177280)', 'region bounds are not in strictly increasing order (16384 not > 1342177280)'])
    """
    problems = []

    if data is None:
        problems.append('no data in file')
    elif 'memory' not in data:
        problems.append('no description of memory in file (no "memory" key)')
    elif not isinstance(data['memory'], list):
        problems.append('bad description of memory in file'
                        ' ("memory" is not a list)')
    elif len(data['memory']) == 0:
        problems.append('memory described as empty in file'
                        ' (list is zero-length)')
    else:
        # The initialization of last_seen_bound works with the "increasing
        # bounds" comparison below to require that all addresses be nonnegative.
        last_seen_bound = -1
        region_counter = 0

        for region in data['memory']:
            for bound in ('start', 'end'):
                if bound not in region:
                    problems.append('region {n} is missing its {name} bound'
                                    .format(n=region_counter, name=bound))
                elif not isinstance(region[bound], int):
                    problems.append('region {name} "{value}" is not an integer'
                                    .format(name=bound, value=region[bound]))
                elif not region[bound] > last_seen_bound:
                    problems.append('region bounds are not in strictly'
                                    ' increasing order ({this} not > {last})'
                                    .format(this=region[bound],
                                            last=last_seen_bound))
                else:
                    last_seen_bound = region[bound]

            region_counter += 1

    if problems:
        return (False, problems)

    return (True, [])