class Simulator(object):

    def __init__(self, params, scene, initial_pos):
        self.scene = scene
        self.params = params
        self.initial_pos = initial_pos
        self.status = "none"

        self.camera = CameraModel(scene, initial_pos,
            simulate_backlash=self.params.backlash,
            simulate_noise=self.params.noise)

        if params.perfect_classification is None:
            self.perfect_classification = None
        else:
            self.perfect_classification = \
                params.perfect_classification[scene.filename]

    def _do_local_search(self, direction, rev_direction):
        """Perform a local search (incremental hillclimbing in a 
        given direction). The hillclimbing has a tolerance of two steps.
        i.e., Up to two steps that don't increase the focus value can be taken
        before we stop climbing."""
        while not self.camera.will_hit_edge(direction):
            prev_fmeasure = self.camera.last_fmeasure()
            self.camera.move_fine(direction)

            if self.camera.last_fmeasure() < prev_fmeasure:
                if (two_step_tolerance and 
                    not self.camera.will_hit_edge(direction)):
                    # We've seen a decrease. Consider moving one extra step.
                    prev_fmeasure = self.camera.last_fmeasure()
                    self.camera.move_fine(direction)

                    if (self.camera.last_fmeasure() < prev_fmeasure):
                        # Seen a decrease again, backtrack and stop.
                        self.camera.move_fine(rev_direction, 2)
                        break
                else:
                    # Backtrack and stop.
                    self.camera.move_fine(rev_direction)
                    break

    def _go_to_max(self):
        """Return to the location of the largest focus value seen so far and
        perform a local search to find the exact location of the peak."""
        current_pos = self.camera.last_position()
        maximum_pos = max(self.camera.visited_positions,
            key=(lambda pos : self.camera.get_fvalue(pos)))

        if maximum_pos < current_pos:
            direction = Direction("left")
        elif maximum_pos > current_pos:
            direction = Direction("right")
        elif current_pos < self.camera.visited_positions[-2]:
            direction = Direction("left")
        else:
            direction = Direction("right")
        rev_direction = direction.reverse()

        # Take as many coarse steps as needed to go back to the maximum
        # without going over it.
        distance = abs(current_pos - maximum_pos)
        coarse_steps = distance / 8

        self.camera.move_coarse(direction, coarse_steps)

        # Keep going in fine steps to see if we can find a higher position.
        start_pos = self.camera.last_position()
        self._do_local_search(direction, rev_direction)

        # If we didn't move further, we might want to look in the other
        # direction too.
        if start_pos == self.camera.last_position():
            self._do_local_search(rev_direction, direction)

        self.status = "foundmax"

    def _get_first_direction(self):
        """Direction in which we should start sweeping initially."""
        first, second, third = self.camera.get_fvalues(
            self.camera.visited_positions[-3:])
        norm_lens_pos = float(self.initial_pos) / (self.scene.step_count - 1)

        evaluator = featuresfirststep.firststep_feature_evaluator(
            first, second, third, norm_lens_pos)
        return Direction(evaluatetree.evaluate_tree(
            self.params.left_right_tree, evaluator))

    def _sweep(self, direction):
        """Sweep the lens in one direction and return a
        tuple (success state, number of steps taken) along the way.
        """
        initial_position = self.camera.last_position()
        sweep_fvalues = [ self.camera.last_fmeasure() ]

        while not self.camera.will_hit_edge(direction):
            # Move the lens forward.
            self.camera.move_coarse(direction)
            sweep_fvalues.append(self.camera.last_fmeasure())

            # Take at least two steps before we allow turning back.
            if len(sweep_fvalues) < 3:
                continue
       
            if self.perfect_classification is None:
                # Obtain the ML classification at the new lens position.
                evaluator = featuresturn.action_feature_evaluator(
                    sweep_fvalues, self.scene.step_count)
                classification = evaluatetree.evaluate_tree(
                    self.params.action_tree, evaluator)
            else:
                key = featuresturn.make_key(str(direction), initial_position, 
                                            self.camera.last_position())
                classification = self.perfect_classification[key]

            if classification != "continue":
                assert (classification == "turn_peak" or
                        classification == "backtrack")
                return classification, len(sweep_fvalues) - 1

        # We've reached an edge, but the decision tree still does not want
        # to turn back, so what do we do now?
        # After thinking a lot about it, I think the best thing to do is to
        # introduce a condition manually. It's a bit ad-hoc, but we really need
        # to be able to handle this case robustly, as there are lot of cases
        # (i.e., landscape shots) where peaks will be at the edge.
        min_val = min(self.camera.get_fvalues(self.camera.visited_positions))
        max_val = max(self.camera.get_fvalues(self.camera.visited_positions))
        if float(min_val) / max_val > 0.8:
            return "backtrack", len(sweep_fvalues) - 1
        else:
            return "turn_peak", len(sweep_fvalues) - 1


    def _backtrack(self, previous_direction, step_count):
        """From the current lens position, go back to the lens position we
        were at before and look on the other side."""

        new_direction = previous_direction.reverse()

        # Go back to where we started.
        self.camera.move_coarse(new_direction, step_count)

        # Sweep again the other way.
        result, step_count = self._sweep(new_direction)

        if result == "turn_peak":
            self._go_to_max()
        elif result == "backtrack":
            # If we need to backtrack a second time, we failed.
            self.status = "failed"
        else:
            assert False

    def evaluate(self):
        """For every scene and every lens position, run a simulation and
        store the statistics."""

        # Take the first two steps, as to get three focus measures with which
        # to decide which direction to sweep.
        self.camera.move_fine(Direction("right"), 2)

        # Decide initial direction in which to look.
        direction = self._get_first_direction()
            
        # Search in that direction.
        result, step_count = self._sweep(direction)

        if result == "turn_peak":
            self._go_to_max()
        elif result == "backtrack":
            self._backtrack(direction, step_count)
        else:
            assert False

    def is_true_positive(self):
        """Whether a peak was found and the peak is close to a real peak."""
        return (self.status == "foundmax" and 
                self.scene.distance_to_closest_peak(
                    self.camera.last_position()) <= 1)

    def is_false_positive(self):
        """Whether a peak was found and the peak not close to a real peak."""
        return (self.status == "foundmax" and 
                self.scene.distance_to_closest_peak(
                    self.camera.last_position()) > 1)

    def is_true_negative(self):
        """Whether we failed to find a peak and we didn't come 
        close to a real peak."""
        return (self.status == "failed" and 
                all(self.scene.distance_to_closest_peak(pos) > 1
                    for pos in self.camera.visited_positions))

    def is_false_negative(self):
        """Whether we failed to find a peak but we did come 
        close to a real peak."""
        return (self.status == "failed" and 
                any(self.scene.distance_to_closest_peak(pos) <= 1
                    for pos in self.camera.visited_positions))

    def get_evaluation(self):
        """Return whether a simulation for this scene starting at the given
        lens position gave a true/false positive/negative.
        """
        if self.is_true_positive():
            return "true positive"
        if self.is_false_positive():
            return "false positive"
        if self.is_true_negative():
            return "true negative"
        if self.is_false_negative():
            return "false negative"
def search_standard(scenes, scene_to_print):
    print ("Perform a standard hill-climbing search, where coarse steps are\n"
           "taken until some stopping condition occurs, at which point the\n"
           "movement is reversed, at which point fine steps are taken to\n"
           "maximize the focus value. This is the method described in\n"
           "[He2003] and [Li2005].\n\n"
           "To visualize the steps taken for simulation of a specific scene,\n"
           "use the command-line argument --scene-to-print=something.txt")

    step_size = 8

    data_rows = [("filename", "success %", "steps")]

    # Redirect stdout to a file for printing R script.
    orig_stdout = sys.stdout
    file_to_print = open("comparison.R", "w+")
    sys.stdout = file_to_print

    total_success = 0

    for scene in scenes:
        success_count = 0
        total_step_count = 0

        initial_positions = range(0, scene.step_count - step_size)
        for initial_position in initial_positions:
            camera = CameraModel(scene, initial_position,
                simulate_backlash=simulate_backlash, 
                simulate_noise=simulate_noise)

            first_measure = camera.last_fmeasure()
            camera.move_coarse(Direction("right"))

            # Determine whether to start moving left or right.
            if camera.last_fmeasure() < first_measure:
                direction = Direction("left")
            else:
                direction = Direction("right")

            # If the first step decreases focus value, switch direction.
            # This is a simple backtracking, basically.
            first_measure = camera.last_fmeasure()
            camera.move_coarse(direction)
            if camera.last_fmeasure() < first_measure:
                direction = direction.reverse()

            # Sweep
            max_value = camera.last_fmeasure()
            while not camera.will_hit_edge(direction):
                camera.move_coarse(direction)
                max_value = max(max_value, camera.last_fmeasure())

                # Have we found a peak?
                if camera.last_fmeasure() < max_value * 0.9:
                    # Stop searching
                    break
                    
            # Hillclimb until we're back at the peak.
            while not camera.will_hit_edge(direction.reverse()):
                prev_measure = camera.last_fmeasure()
                camera.move_fine(direction.reverse())
                if prev_measure > camera.last_fmeasure():
                    camera.move_fine(direction)
                    break

            # Record if we succeeded.
            if scene.distance_to_closest_peak(camera.last_position()) <= 1:
                success_count += 1
                evaluation = "succeeded"
            else:
                evaluation = "failed"
            
            if scene.filename == scene_to_print:
                camera.print_script(evaluation)

            total_step_count += camera.steps_taken

        success = float(success_count) / len(initial_positions) * 100
        line = (scene.name, 
                "%.1f" % success, 
                "%.1f" % (float(total_step_count) / len(initial_positions)))
        data_rows.append(line)
        total_success += success

    # Restore original stdout
    sys.stdout = orig_stdout
    file_to_print.close()

    print_aligned_data_rows(data_rows)
    print "average success : %.1f" % (total_success / len(scenes))