class Command(object, metaclass=CommandMeta):
    def __init__(self, *args, **kwargs) -> None:
        cls_name = self.__class__.__name__
        params = self.__class__.parameters  # type: FrozenSet[Parameter]

        # were any positional arguments passed to the constructor?
        if args:
            msg = "constructor [{}] accepts no positional arguments but {} {} given"  # noqa: pycodestyle
            msg = msg.format(cls_name,
                             "was" if len(args) == 1 else "were",
                             len(args))
            raise TypeError(msg)

        # set values for each variable
        for p in params:
            try:
                val = kwargs[p.name]
            except KeyError:
                msg = "missing keyword argument [{}] to constructor [{}]"
                msg = msg.format(p.name, cls_name)
                raise TypeError(msg)

            # TODO perform run-time type checking?
            setattr(self, p._field, val)

        # did we pass any unexpected keyword arguments?
        if len(kwargs) > len(params):
            actual_args = set(n for n in kwargs)
            expected_args = set(p.name for p in params)
            unexpected_arguments = list(actual_args - expected_args)
            msg = "unexpected keyword arguments [{}] supplied to constructor [{}]"  # noqa: pycodestyle
            msg = msg.format('; '.join(unexpected_arguments), cls_name)
            raise TypeError(msg)

    @classmethod
    def get_next_allowed(cls, system: Type['System']) -> List[Type['Command']]:
        if not cls.next_allowed:
            return []
        if cls.next_allowed[0] == '*':  # all allowed
            return [v for k, v in system.commands.items()
                    if k not in cls.next_allowed[1:]]
        allowed = [system.commands[n] for n in cls.next_allowed]
        return allowed

    def __eq__(self, other: 'Command') -> bool:
        if type(self) != type(other):
            msg = "illegal comparison of commands: [{}] vs. [{}]"
            msg = msg.format(self.uid, other.uid)
            raise Exception(msg)  # FIXME use HoustonException
        for param in self.__class__.parameters:
            if self[param.name] != other[param.name]:
                return False
        return True

    def __getitem__(self, name: str) -> Any:
        # FIXME use frozendict
        try:
            params = self.__class__.parameters
            param = next(p for p in params if p.name == name)
        except StopIteration:
            msg = "no parameter [{}] in command [{}]"
            msg.format(name, self.__class__.__name__)
            raise KeyError(msg)
        return getattr(self, param._field)

    def __hash__(self) -> int:
        params = (self.uid,)
        params += tuple(self[p.name] for p in self.__class__.parameters)
        return hash(params)

    @property
    def uid(self) -> str:
        """
        The UID of the type of this command.
        """
        return self.__class__.uid

    @property
    def name(self) -> str:
        """
        The name of the type of this command.
        """
        return self.__class__.__name__

    @staticmethod
    def from_dict(d: Dict[str, Any]) -> 'Command':
        name_typ = d['type']
        typ = _UID_TO_COMMAND_TYPE[name_typ]  # type: Type[Command]
        params = d['parameters']
        return typ(**params)

    def to_dict(self) -> Dict[str, Any]:
        fields = {
            'type': self.uid,
            'parameters': {}
        }  # type: Dict[str, Any]
        for param in self.__class__.parameters:
            fields['parameters'][param.name] = getattr(self, param._field)
        return fields

    def __repr__(self) -> str:
        fields = self.to_dict()['parameters']
        for (name, val) in fields.items():
            if isinstance(val, float):
                s = "{:.3f}".format(val)
            else:
                s = str(val)
            fields[name] = val
        s = '; '.join(["{}: {}".format(k, v) for (k, v) in fields.items()])
        s = "{}({})".format(self.__class__.__name__, s)
        return s

    def dispatch(self,
                 sandbox: 'Sandbox',
                 state: State,
                 environment: Environment,
                 configuration: Configuration
                 ) -> None:
        """
        Responsible for invoking this command.

        Parameters:
            sandbox: the sandbox for the system under test.
            state: the state of the system immediately prior to the
                call to this method
            environment: a description of the environment in which the
                command is being performed
            configuration: the configuration of the system under test.
        """
        raise NotImplementedError

    def timeout(self,
                state: State,
                environment: Environment,
                config: Configuration
                ) -> float:
        """
        Responsible for calculating the maximum time that this command
        should take to finish its execution.

        Parameters:
            state: the state of the system prior to the execution of the
                command.
            environment: the state of the environment prior to the execution
                of the command.
            configuration: the configuration of the system under test.

        Returns:
            Maximum length of time (in seconds) that the command may take to
            complete its execution.
        """
        spec = self.resolve(state, environment, config)
        return spec.timeout(self, state, environment, config)

    def resolve(self,
                state: State,
                environment: Environment,
                config: Configuration
                ) -> Specification:
        """
        Returns the specification that the system is expected to satisfy when
        completing this command in a given state, environment, and
        configuration.
        """
        for spec in self.__class__.specifications:
            if spec.precondition.is_satisfied(self,
                                              state,
                                              None,
                                              environment,
                                              config):
                return spec
        raise Exception("failed to resolve specification")

    def to_message(self) -> Message:
        """
        Transforms this command into a message that can be sent to the system
        under test.
        """
        raise NotImplementedError

    def __iter__(self) -> Iterator[Parameter]:
        yield from self.__class__.parameters

    @classmethod
    def generate(cls, rng: random.Random) -> 'Command':
        params = {p.name: p.generate(rng) for p in cls.parameters}
        command = cls(**params)
        return command class Environment(object):
    """
    Provides a static description of the state of the environment.
    """
    @staticmethod
    def from_file(fn: str) -> 'Environment':
        with open(fn, "r") as f:
            jsn = json.load(f)
        return Environment.from_json(jsn)

    @staticmethod
    def from_json(jsn: Dict[str, Any]) -> 'Environment':
        return Environment(jsn['constants'])

    def __init__(self, values: Dict[str, Any]) -> None:
        """
        Constructs a description of a mission environment.

        Parameters:
            values: a dictionary of environment constant values, indexed by
                the name of those constants.
        """
        self.__values = values.copy()

    def __getitem__(self, name: str):
        """
        Retrieves the value of an environmental variable with a given name.
        """
        return self.__values[name]

    def __eq__(self, other: 'Environment') -> bool:
        return self.to_json() == other.to_json()

    def __hash__(self) -> int:
        vals = tuple(self.__values.items())
        vals = sorted(vals, key=lambda x: x[0])
        return hash(tuple(vals))

    def to_json(self) -> Dict[str, Any]:
        return {'constants': self.__values.copy()} class RandomMissionGenerator(MissionGenerator):
    def __init__(self,
                 system: Type[System],
                 initial_state: State,
                 env: Environment,
                 config: Configuration,
                 threads: int = 1,
                 command_generators: Optional[Dict[str, Callable]] = None,
                 max_num_commands: int = 10
                 ) -> None:
        super().__init__(system, threads, command_generators, max_num_commands)
        self.__initial_state = initial_state
        self.__env = env
        self.__configuration = config

    @property
    def initial_state(self):
        """
        The initial state used by all missions produced by this generator.
        """
        return self.__initial_state

    @property
    def env(self):
        """
        The environment used by all missions produced by this generator.
        """
        return self.__env

    def generate_command(self, command_class: Type[Command]) -> Command:
        generator = self.command_generator(command_class)
        if generator is None:
            return command_class.generate(self.rng)
        # g = generator.generate_action_without_state
        # return g(self.system, self.__env, self.rng)
        return g(self.rng)

    def generate_mission(self):
        command_classes = list(self.system.commands.values())
        takeoff = None
        for c in command_classes:
            if "MAV_CMD_NAV_TAKEOFF" in c.uid:
                takeoff = c
                break
        if not takeoff:
            raise Exception("No TAKEOFF command found")
        commands = [self.generate_command(takeoff)]
        cmds_len = self.max_num_commands
        for i in range(1, cmds_len):
            next_allowed = commands[i - 1].__class__.get_next_allowed(self.system)  # noqa: pycodestyle
            if next_allowed:
                command_class = self.rng.choice(next_allowed)
                commands.append(self.generate_command(command_class))
            else:
                break
        return Mission(self.__configuration,
                       self.__env,
                       self.__initial_state,
                       commands,
                       self.system) class ResourceLimits(object):
    """
    Used to specify limits on the resources that can be used during a given
    test-generation trial.
    """
    @staticmethod
    def from_json(jsn):
        num_missions_selected = jsn['num_missions_selected']
        return ResourceLimits(num_missions=jsn['num_missions'],
                              running_time=jsn['running_time'],
                              num_missions_selected=num_missions_selected)

    def __init__(self,
                 num_missions=None,
                 running_time=None,
                 num_missions_selected=None):
        self.__num_missions = num_missions
        self.__running_time = running_time
        self.__num_missions_selected = num_missions_selected

    def reached(self, usage):
        """
        Determines whether a given resource usage has reached the limits
        defined by this object.
        """
        if self.reached_mission_limit(usage.num_missions):
            return True
        if self.reached_time_limit(usage.running_time):
            return True
        return False

    @property
    def num_missions_selected(self):
        """
        Number of missions that should be selected from all generated missions.
        """
        return self.__num_missions_selected

    @property
    def num_missions(self):
        """
        The maximum number of missions that may be generated, or None, if
        no such limit exists.
        """
        return self.__num_missions

    @property
    def running_time(self):
        """
        The maximum length of time that the test-generation process may take,
        or None, if there is no such limit.
        """
        return self.__running_time

    def reached_mission_limit(self, num_missions):
        """
        Determines whether a given number of missions exceeds the maximum
        number of missions that can be generated (as specified by this object).
        """
        return self.__num_missions is not None and \
            num_missions >= self.__num_missions

    def reached_time_limit(self, running_time):
        """
        Determines whether a given running time meets or exceeds the limit.
        """
        return self.__running_time is not None and \
            running_time >= self.__running_time

    def to_json(self):
        """
        Returns a JSON description of these limits.
        """
        return {
            'num_missions': self.__num_missions,
            'running_time': self.__running_time,
            'num_missions_selected': self.__num_missions_selected
        } class Mission(object):
    """
    A mission is represented as a sequence of commands that are carried out in
    a given environment and initial state.
    """
    configuration = attr.ib(type=Configuration)
    environment = attr.ib(type=Environment)
    initial_state = attr.ib(type=State)
    commands = attr.ib(type=Tuple[Command], converter=tuple)
    system = attr.ib(type=Type[System])

    @staticmethod
    def from_dict(jsn: Dict[str, Any]) -> 'Mission':
        system = System.get_by_name(jsn['system'])
        env = Environment.from_json(jsn['environment'])
        config = system.configuration.from_dict(jsn['configuration'])
        initial_state = system.state.from_dict(jsn['initial_state'])
        cmds = tuple(Command.from_dict(c) for c in jsn['commands'])
        return Mission(config, env, initial_state, cmds, system)

    def is_empty(self) -> bool:
        """
        Returns True if this mission contains no commands.
        """
        return self.commands == ()

    def __iter__(self) -> Iterator[Command]:
        """
        Returns an iterator over the commands contained in this mission.
        """
        yield from self.commands

    @property
    def size(self) -> int:
        """
        The number of commands in this mission.
        """
        return len(self.commands)

    def extended(self, cmd: Command) -> 'Mission':
        """
        Returns a variant of this mission with a given command added onto the
        end.
        """
        cmds = self.commands + (cmd,)
        return Mission(self.environment, self.initial_state, cmds, self.system)

    def to_dict(self) -> Dict[str, Any]:
        return {
            'configuration': self.configuration.to_dict(),
            'environment': self.environment.to_json(),
            'initial_state': self.initial_state.to_dict(),
            'commands': [c.to_dict() for c in self.commands],
            'system': self.system.name}

    def run(self,
            bz: BugZooClient,
            snapshot_or_name: Union[str, Snapshot]
            ) -> 'MissionOutcome':
        """
        Creates a sandbox and runs the commands and returns the outcome.
        """
        with self.system.sandbox.for_snapshot(bz,
                                              snapshot_or_name,
                                              self.initial_state,
                                              self.environment,
                                              self.configuration) as sandbox:
            outcome = sandbox.run(self.commands)
            return outcome class MissionRunnerPool(object):
    """
    Mission runner pools are used to distribute the execution of a stream
    of missions across a given number of workers, each running on a separate
    thread.
    """
    def __init__(self,
                 bz: BugZooClient,
                 snapshot_name: str,
                 system: 'System',
                 size: int,
                 source,  # FIXME
                 callback,  # FIXMe
                 with_coverage=False,
                 record=False):
        assert callable(callback)
        assert size > 0

        # if a list is provided, use an iterator for that list
        if isinstance(source, list):
            source = iter(source)

        self.__system = system
        self.__source = source
        self.__callback = callback
        self.__index = -1
        self._lock = threading.Lock()

        # provision desired number of runners
        self.__runners = \
            [MissionRunner(self, bz, snapshot_name, with_coverage, record)
                for _ in range(size)]

    def run(self) -> None:
        """

        """
        try:
            for runner in self.__runners:
                runner.start()

            # soft block until all runners have finished
            # (unlike join, we allow exceptions to be thrown to the parent
            #  thread)
            while True:
                if not any(runner.is_alive() for runner in self.__runners):
                    break
                time.sleep(0.1)

        finally:
            self.shutdown()

    def shutdown(self) -> None:
        """
        Kills all runners that belong to this pool.
        """
        if self.__runners == []:
            return

        for runner in self.__runners:
            if runner is not None:
                runner.shutdown()
        self.__runners = []

    @property
    def system(self) -> 'System':
        """
        The system under test.
        """
        return self.__system

    @property
    def size(self) -> int:
        """
        The number of independent threads being used by the pool to run
        tests.
        """
        return self.__runners.length()

    def report(self, mission, outcome, coverage=None) -> None:
        """
        Used to report the outcome of a mission.

        WARNING: It is the responsibility of the callback to guarantee
            thread safety (if necessary).
        """
        self.__callback(mission, outcome, coverage)

    def fetch(self) -> Tuple[int, Optional[Mission]]:
        """
        Returns the next mission from the (lazily-generated) queue, or None if
        there are no missions left to run.

        This method is considered to be thread safe (no concurrent reads from
        the source of the pool are allowed).
        """
        # acquire fetch lock
        self._lock.acquire()
        try:
            self.__index += 1
            return self.__index, self.__source.__next__()

        except StopIteration:
            return self.__index, None

        finally:
            self._lock.release() class DeltaDebugging(RootCauseFinder):
    """
    Conducts delta debugging on a failing mission to narrow
    it down to main problem.
    """
    def __init__(self,
                 system: Type[System],
                 initial_state: State,
                 environment: Environment,
                 config: Configuration,
                 initial_failing_missions: List[Mission],
                 bz: BugZooClient,
                 snapshot: str
                 ) -> None:
        self.__domain = MissionDomain.from_initial_mission(
            initial_failing_missions[0], discrete_params=True)
        self.__bz = bz
        self.__snapshot = snapshot

        super(DeltaDebugging, self).__init__(system, initial_state,
                                             environment, config,
                                             initial_failing_missions)

    @property
    def domain(self) -> MissionDomain:
        """
        The domain where the failure happens.
        """
        return self.__domain

    def find_root_cause(self, time_limit: float = 0.0) -> MissionDomain:
        empty_domain = MissionDomain(self.system)
        final_domain = self._dd2(self.domain, empty_domain)
        print("FINISHED: {}".format(str(final_domain)))

        return final_domain

    def _dd2(self, c: MissionDomain, r: MissionDomain) -> MissionDomain:

        print("****** C: {}\n****** R: {}".format(str(c), str(r)))

        if c.command_size == 1:
            return c

        c1, c2 = DeltaDebugging._divide(c)

        m1 = DeltaDebugging._union(c1, r)
        res = self._run(m1)
        if not res:
            return self._dd2(c1, r)

        m2 = DeltaDebugging._union(c2, r)
        res = self._run(m2)
        if not res:
            return self._dd2(c2, r)

        final_domain = DeltaDebugging._union(self._dd2(c1, m2),
                                             self._dd2(c2, m1))
        return final_domain

    def _run(self, mission_domain: MissionDomain) -> bool:
        """
        runs a mission using sandbox and returns whether the mission passed.
        """
        mission = mission_domain.generate_mission(self.environment,
                                                  self.initial_state,
                                                  self.configuration,
                                                  self.rng)
        res = mission.run(self.__bz, self.__snapshot)

        return res.passed

    @staticmethod
    def _divide(c: MissionDomain) -> Tuple[MissionDomain, MissionDomain]:
        """
        Divides a domain into two almost equal domains.
        """
        mid = int(c.command_size / 2)
        c1 = MissionDomain(c.system, c.domain[:mid])
        c2 = MissionDomain(c.system, c.domain[mid:])
        return c1, c2

    @staticmethod
    def _union(c1: MissionDomain, c2: MissionDomain) -> MissionDomain:
        """
        Returns the union of two domains.
        """
        command_list = []
        i1, i2 = 0, 0
        while i1 < c1.command_size or i2 < c2.command_size:
            if i1 >= c1.command_size or \
               (i2 < c2.command_size and c2.domain[i2][0] < c1.domain[i1][0]):
                command_list.append(c2.domain[i2])
                i2 += 1
            else:
                command_list.append(c1.domain[i1])
                i1 += 1
        c = MissionDomain(c1.system, command_list)
        return c class SymbolicExecution(object):

    def __init__(self,
                 system: Type[System],
                 initial_state: State,
                 environment: Environment,
                 configuration: Configuration
                 ) -> None:
        self.__system = system
        self.__initial_state = initial_state
        self.__environment = environment
        self.__configuration = configuration

    @property
    def system(self) -> System:
        return self.__system

    @property
    def initial_state(self) -> State:
        return self.__initial_state

    @property
    def environment(self) -> Environment:
        return self.__environment

    @property
    def configuration(self) -> Configuration:
        return self.__configuration

    def execute_symbolically(self, mission: Mission) -> List[Mission]:
        """
        Having the sequense of actions in `mission` this function
        will generate parameters for those actions in order to
        explore all possible action branches.
        """

        rng = random.Random(1000)
        commands = mission.commands
        all_paths = []  # type List[List[Specification]]
        all_missions = []
        self._dfs(commands, 0, [], all_paths)

        for bp in all_paths:
            logger.info("BP: " + str(bp))
            logger.info("CMD: " + str(commands))
            ctx = z3.Context()
            solver = z3.Optimize(ctx=ctx)
            smts = []
            soft_smts = []
            seq_id = 0
            mappings = {}
            for b in bp:
                smt, decls = b.get_constraint(ctx,
                                              commands[seq_id],
                                              self.initial_state,
                                              "__{}".format(seq_id))

                for pb in commands[seq_id].specifications:
                    if pb.name == b.name:
                        break
                    smt.append(z3.Not(pb.precondition.get_expression(
                        decls, self.initial_state,
                        "__{}".format(seq_id))[0]))

                logger.debug("SSS {}".format(smt))
                mappings[seq_id] = decls
                smts.extend(smt)
                soft_smts.extend(Expression.values_to_smt("$",
                                                          commands[seq_id],
                                                          decls))
                seq_id += 1

            smts.extend(self._connect_pre_and_post(seq_id - 1, mappings))
            logger.debug("Final " + str(smts))
#            solver.from_string(smt)
            solver.add(smts)
            for s in soft_smts:
                solver.add_soft(s)

            if not solver.check() == z3.sat:
                logger.info("UNSAT")
                continue

            logger.info("SAT")

            model = solver.model()
            logger.debug("Model: {}".format(model))
            commands_list = []
            seq_id = 0
            for b in bp:
                parameters = {}
                for p in commands[seq_id].parameters:
                    val = model[mappings[seq_id]["${}".format(p.name)]]
                    parameters[p.name] = eval(str(val))
                    if parameters[p.name] is None:
                        v = p.generate(rng)
                        logger.debug("PP {} {}".format(p.name, v))
                        parameters[p.name] = v
                logger.debug("Parameters: {}".format(parameters))
                commands_list.append(commands[seq_id].__class__(**parameters))
                seq_id += 1

            logger.debug("Added: {}".format(commands_list))
            all_missions.append(Mission(self.configuration,
                                        self.environment,
                                        self.initial_state,
                                        commands_list,
                                        self.system))

        return all_missions

    def _connect_pre_and_post(self,
                              number: int,
                              mappings: Dict[int, Dict[str, Any]]
                              ) -> List[z3.ExprRef]:
        assert(number >= 0)
        s = []
        for v in self.initial_state:
            for i in range(0, number):
                m1 = mappings[i]['__{}'.format(v.name)]
                m2 = mappings[i + 1]['_{}'.format(v.name)]
                s.append(m1 == m2)
        s.extend(Expression.values_to_smt('_',
                                          self.initial_state,
                                          mappings[0]))
        return s

    def _dfs(self,
             commands: List[Command],
             start_index: int,
             path: List[Specification],
             all_paths: List[List[Specification]]
             ) -> None:

        if start_index == len(commands):
            all_paths.append(path)
            return
        for s in commands[start_index].specifications:
            new_path = copy.deepcopy(path)
            new_path.append(s)
            self._dfs(commands, start_index + 1, new_path, all_paths) class State(BaseState):
    home_latitude = var(float,
                        lambda c: c.conn.home_location.lat,
                        noise=0.0005)
    home_longitude = var(float,
                         lambda c: c.conn.home_location.lon,
                         noise=0.0005)
    altitude = var(float,
                   lambda c: c.conn.location.global_relative_frame.alt,  # noqa: pycodestyle
                   noise=0.5)
    latitude = var(float,
                   lambda c: c.conn.location.global_relative_frame.lat,  # noqa: pycodestyle
                   noise=0.0005)
    longitude = var(float,
                    lambda c: c.conn.location.global_relative_frame.lon,  # noqa: pycodestyle
                    noise=0.0005)
    armable = var(bool, lambda c: c.conn.is_armable)
    armed = var(bool, lambda c: c.conn.armed)
    mode = var(str, lambda c: c.conn.mode.name)
    vx = var(float, lambda c: c.conn.velocity[0], noise=0.3)
    vy = var(float, lambda c: c.conn.velocity[1], noise=0.3)
    vz = var(float, lambda c: c.conn.velocity[2], noise=0.3)
    pitch = var(float, lambda c: c.conn.attitude.pitch, noise=0.2)
    yaw = var(float, lambda c: c.conn.attitude.yaw, noise=0.2)
    roll = var(float, lambda c: c.conn.attitude.roll, noise=0.2)
    heading = var(float, lambda c: c.conn.heading, noise=15)  # FIXME high?
    airspeed = var(float, lambda c: c.conn.airspeed, noise=1.0)
    groundspeed = var(float, lambda c: c.conn.groundspeed, noise=1.0)
    ekf_ok = var(bool, lambda c: c.conn.ekf_ok)
#    throttle_channel = var(float, lambda c: c.conn.channels['3'])
#    roll_channel = var(float, lambda c: c.conn.channels['1'])

    def evolve(self,
               message: MAVLinkMessage,
               time_offset: float,
               connection: MAVLinkConnection
               ) -> 'State':
        values = {name: v.read(connection)
                  for (name, v) in self.variables.items()}
        values['time_offset'] = time_offset
        state_new = self.__class__(**values)
        return state_new class State(BaseState):
    home_latitude = var(float, lambda c: -35.362938)  # TODO: fixed
    home_longitude = var(float, lambda c: 149.165085)  # TODO: fixed
    altitude = var(float,
                   lambda c: c.connection.location.global_relative_frame.alt,  # noqa: pycodestyle
                   noise=1.0)
    latitude = var(float,
                   lambda c: c.connection.location.global_relative_frame.lat,  # noqa: pycodestyle
                   noise=0.0005)
    longitude = var(float,
                    lambda c: c.connection.location.global_relative_frame.lon,  # noqa: pycodestyle
                    noise=0.0005)
    armable = var(bool, lambda c: c.connection.is_armable)
    armed = var(bool, lambda c: c.connection.armed)
    mode = var(str, lambda c: c.connection.mode.name)
    pitch = var(float, lambda c: c.connection.attitude.pitch, noise=0.05)
    yaw = var(float, lambda c: c.connection.attitude.yaw, noise=0.05)
    roll = var(float, lambda c: c.connection.attitude.roll, noise=0.05)
    heading = var(float, lambda c: c.connection.heading, noise=2)
    groundspeed = var(float, lambda c: c.connection.groundspeed, noise=0.05)
    ekf_ok = var(bool, lambda c: c.connection.ekf_ok)
    throttle_channel = var(float, lambda c: c.connection.channels['3'])
    roll_channel = var(float, lambda c: c.connection.channels['1']) class Configuration(BaseConfiguration):
    speedup = option(int)
    min_parachute_alt = option(float)
    constant_timeout_offset = option(int)
    # FIXME this should be computed based on acceleration and speed
    time_per_metre_travelled = option(float) class GoTo(Command):
    uid = 'ardu:copter:goto'
    name = 'goto'
    parameters = [
        Parameter('latitude', ContinuousValueRange(-90.0, 90.0, True)),
        Parameter('longitude', ContinuousValueRange(-180.0, 180.0, True)),
        Parameter('altitude', ContinuousValueRange(0.3, 100.0))
    ]
    specifications = [
        GotoNormally,
        GotoLoiter,
        Idle
    ]

    @classmethod
    def generate(cls, rng: random.Random) -> 'GoTo':
        (lat, lon) = (-35.3632607, 149.1652351)  # FIXME
        heading = rng.uniform(0.0, 360.0)
        dist = rng.uniform(0.0, 2.0)  # FIXME
        params = {}

        origin = geopy.Point(latitude=lat, longitude=lon)
        dist = geopy.distance.distance(meters=dist)
        destination = dist.destination(origin, heading)
        params['latitude'] = destination.latitude
        params['longitude'] = destination.longitude
        params['altitude'] = 5.0  # FIXME

        command = cls(**params)
        return command

    def to_message(self) -> Message:
        return CommandLong(target_system=0,
                           target_component=0,
                           cmd_id=mavlink.MAV_CMD_NAV_WAYPOINT,
                           param_5=self.latitude,
                           param_6=self.longitude,
                           param_7=self.altitude)

    def dispatch(self,
                 sandbox: 'Sandbox',
                 state: State,
                 environment: Environment,
                 config: Configuration
                 ) -> None:
        loc = dronekit.LocationGlobalRelative(self.latitude,
                                              self.longitude,
                                              self.altitude)
        sandbox.connection.simple_goto(loc) class Takeoff(Command):
    uid = 'ardu:copter:takeoff'
    name = 'takeoff'
    parameters = [
        Parameter('altitude', ContinuousValueRange(0.3, 100.0))
    ]
    specifications = [
        TakeoffNormally,
        Idle
    ]

    def dispatch(self,
                 sandbox: 'Sandbox',
                 state: State,
                 environment: Environment,
                 config: Configuration
                 ) -> None:
        vehicle = sandbox.connection
        msg = vehicle.message_factory.command_long_encode(
            0, 0,
            mavutil.mavlink.MAV_CMD_NAV_TAKEOFF,
            0, 1, 0, 0, 0, 0, 0, self.altitude)
        vehicle.send_mavlink(msg)

    def to_message(self) -> CommandLong:
        msg = CommandLong(
            target_system=0,
            target_component=0,
            cmd_id=mavutil.mavlink.MAV_CMD_NAV_TAKEOFF,
            param_1=0,
            param_7=self.altitude)
        return msg class SetMode(Command):
    uid = 'ardu:copter:set-mode'
    name = 'set-mode'
    parameters = [
        Parameter('mode',
                  DiscreteValueRange(['GUIDED', 'LOITER', 'RTL', 'LAND']))
    ]
    specifications = [
        SetModeGuided,
        SetModeLoiter,
        SetModeRTL,
        SetModeLand,
        Idle
    ]

    def dispatch(self,
                 sandbox: 'Sandbox',
                 state: State,
                 environment: Environment,
                 config: Configuration
                 ) -> None:
        sandbox.connection.mode = dronekit.VehicleMode(self.mode)

    def to_message(self) -> CommandLong:
        raise NotImplementedError class ArmDisarm(Command):
    """
    Behaviours:
        Normal: if the robot is armable and is in either its 'GUIDED' or
            'LOITER' modes, the robot will become armed.
        Idle: if the conditions above cannot be met, the robot will ignore the
            command.
    """
    name = 'arm'
    parameters = [
        Parameter('arm', DiscreteValueRange([True, False]))
    ]
    specifications = [
        ArmNormally,
        DisarmNormally,
        Idle
    ]

    def dispatch(self,
                 sandbox: Sandbox,
                 state: State,
                 environment: Environment,
                 configuration: Configuration
                 ) -> None:
        vehicle = sandbox.connection
        arm_flag = 1 if self.arm else 0
        msg = vehicle.message_factory.command_long_encode(
            0, 0,
            mavutil.mavlink.MAV_CMD_COMPONENT_ARM_DISARM,
            0, arm_flag, 0, 0, 0, 0, 0, 0)
        vehicle.send_mavlink(msg)

    def to_message(self) -> CommandLong:
        msg = CommandLong(
            0, 0,
            mavutil.mavlink.MAV_CMD_COMPONENT_ARM_DISARM,
            0, 1 if self.arm else 0, 0, 0, 0, 0, 0, 0)
        return msg
import copy
import json
import logging
import bugzoo
import houston
import copy
from bugzoo import BugZoo
from houston.command import Command
from houston.environment import Environment
from houston.generator.rand import RandomMissionGenerator
from houston.generator.resources import ResourceLimits
from houston.mission import Mission
from houston.runner import MissionRunnerPool
from houston.root_cause.delta_debugging import DeltaDebugging
from houston.root_cause.symex import SymbolicExecution
from houston.ardu.copter.state import State as CopterState
from houston.ardu.rover.state import State as RoverState
from houston.ardu.configuration import Configuration as ArduConfig
from houston.ardu.copter import Takeoff, GoTo, ArmDisarm, SetMode
        from bugzoo.core.coverage import TestSuiteCoverage
        from bugzoo.core.spectra import Spectra
        from bugzoo.localization import Localization
        from bugzoo.localization.suspiciousness import tarantula
    assert isinstance(missions, list)


    outcomes = {}
    coverages = {}
    def record_outcome(mission, outcome, coverage=None):
        outcomes[mission] = outcome
        coverages[mission] = coverage

    runner_pool = MissionRunnerPool(bz, snapshot_name, sut, 4, missions, record_outcome, coverage, record)
    print("Started running")
    runner_pool.run()
    print("Done running")
    print(outcomes)

    with open("example/failed.json", "w") as f:
        for m in outcomes:
            if not outcomes[m].passed:
                f.write(str(m.to_dict()))
                f.write("\n")

    if coverage:
        test_suite_coverage_dict = {}
        counter = 0
        for m in coverages:
            test = {
                    'test': 't{}'.format(counter),
                    'outcome': outcomes[m].to_test_outcome_json(0),
                    'coverage': coverages[m].to_dict()
            }
            test_suite_coverage_dict['t{}'.format(counter)] = test
            counter += 1
        spectra = Spectra.from_coverage(TestSuiteCoverage.from_dict(test_suite_coverage_dict))
        l = Localization.from_spectra(spectra, tarantula)
        print(str(l.get_most_suspicious_lines()))
        with open("example/localization.json", "w") as f:
            f.write(str(l))

### Generate missions
def generate(sut, initial, environment, config, number_of_missions, max_num_commands=3):
    mission_generator = RandomMissionGenerator(sut, initial, environment, config, max_num_commands=max_num_commands) #action_generators=[CircleBasedGotoGenerator((-35.3632607, 149.1652351), 2.0)])
    resource_limits = ResourceLimits(number_of_missions, 1000)
    missions = mission_generator.generate(100, resource_limits)
    print("### {}".format(missions))
    with open("example/missions.json", "w") as f:
        mission_descriptions = list(map(Mission.to_dict, missions))
        print(str(mission_descriptions))
        json.dump(mission_descriptions, f)


### Generate and run missions
def generate_and_run(sut, initial, environment, number_of_missions):
    mission_generator = RandomMissionGenerator(sut, initial, environment)
    resource_limits = ResourceLimits(number_of_missions, 1000)
    mission_generator.generate_and_run(100, resource_limits)
    #mission_generator.prepare(100, resource_limits)
    #for i in range(number_of_missions):
    #    m = mission_generator.generate_mission()
    #    print("Mission {}: {}".format(i, m.to_json()))
    #    res = sandbox.run(m)
    print("DONE")


### Generate and run missions with mutation operator
def generate_and_run_mutation(sut, initial_state, environment, initial_mission, number_of_missions):
    mission_generator = MutationBasedMissionGenerator(sut, initial, environment, initial_mission, action_generators=[CircleBasedGotoGenerator((-35.3632607, 149.1652351), 2.0)])
    resource_limits = ResourceLimits(number_of_missions*5, 1000, number_of_missions)
    mission_generator.generate_and_run(100, resource_limits, with_coverage=True)
    print("DONE")
    with open("example/missions-mutation.json", "w") as f:
        mission_descriptions = list(map(Mission.to_json, mission_generator.history))
        print(str(mission_descriptions))
        json.dump(mission_descriptions, f)
        f.write("\n")
        mission_descriptions = list(map(Mission.to_json, mission_generator.most_fit_missions))
        print(str(mission_descriptions))
        json.dump(mission_descriptions, f)



### Generate and run missions with fault localization
def generate_and_run_with_fl(sut, initial, environment, number_of_missions):
    mission_generator = RandomMissionGenerator(sut, initial, environment, max_num_commands=3, action_generators=[CircleBasedGotoGenerator((-35.3632607, 149.1652351), 2.0)])
    resource_limits = ResourceLimits(number_of_missions, 1000)
    report = mission_generator.generate_and_run(100, resource_limits, with_coverage=True)
    print("DONE")
    print(report)
    print(mission_generator.coverage)
    print(mission_generator.report_fault_localization())
    with open("fl.json", "w") as f:
        f.write(str(mission_generator.report_fault_localization()))


### Generate missions with symbolic execution
def generate_with_se(sut, initial, environment, config, mission):
    se = SymbolicExecution(sut, initial, environment, config)
    missions = se.execute_symbolically(mission)
    with open("example/missions.json", "w") as f:
        mission_descriptions = list(map(Mission.to_dict, missions))
        print(str(mission_descriptions))
        json.dump(mission_descriptions, f)


if __name__ == "__main__":
    setup_logging()
    bz = BugZoo()
    snapshot = 'ardubugs:742cdf6b'
    #snapshot = bz.bugs['afrl:AIS-Scenario1']

    config = ArduConfig(
        speedup=1,
        time_per_metre_travelled=5.0,
        constant_timeout_offset=1.0,
        min_parachute_alt=10.0)

    sut = houston.ardu.copter.copter.ArduCopter

    # mission description
    cmds = (
        ArmDisarm(arm=False),

        ArmDisarm(arm=True),