class UtilityFunctionBuilder(object):
    """
    Builds plot of utility function for a user's utility function 
    for a game where they can win or lose a fixed amount of money with
    a fixed probability (1/2).

    _left_margin - maximum amount of money the user can lose
    _right_margin - maximum amount of money the user can win
    _delta - precision of plot building
    """                                             
    def __init__(self):
        super(UtilityFunctionBuilder, self).__init__()
        self._left_margin = None
        self._right_margin = None
        self._delta = None
        self._game = FortuneGame()

    def set_initial_values(self, l_margin, r_margin, delta):
        """Sets initial values of fields."""              
        self._left_margin = l_margin
        self._right_margin = r_margin
        self._delta = delta

    def interactively_build_plot(self):
        """Starting function that reads values and builds plot."""
        self._read_initial_values()
        self._build_plot_from_results_list(self._find_plot_points())

    def semi_interactively_build_plot(self):
        """Starting function that reads values and builds plot semi-auto."""
        self._read_initial_values()
        self._build_plot_from_results_list(self._find_plot_points(True))

    def _read_initial_values(self):
        """Reads initial values of fields from keyboard."""
        l_margin = safely_read_float_value(
                u"Input the sum you can lose: "
                )
        if l_margin > 0:
            l_margin *= -1
        r_margin = safely_read_float_value(
                u"Input the sum you can win: "
                )
        delta = safely_read_float_value(
                u"Input accuracy: "
                )
        self.set_initial_values(l_margin, r_margin, delta)

    def _find_plot_points(self, semi_auto=False):
        """
        Finds points and utility values for plot of utility function.

        semi_auto - whether the all values must be read from user or only \
                first four
        """
        if semi_auto:
            result = self._find_plot_points_on_int(
                self._left_margin, self._right_margin, 0, 1, self._delta,
                True
                )
        else:
            result = self._find_plot_points_on_int(
                    self._left_margin, self._right_margin, 0, 1, self._delta
                    )  
        result.append((self._right_margin, 1))
        result.insert(0, (self._left_margin, 0))  
        return result

    def _find_plot_points_on_int(self, lose_a, win_a, lose_u, win_u, delta,
            semi_auto=False):
        """
        Finds points and utility values for plot on specified interval.

        lose_a - left margin of interval
        win_a - right margin of interval
        lose_u - utility in left margin
        win_u - utility in right margin
        delta - precision
        semi_auto - whether the all values must be read from user or only \
                first four
        """
        result = []
        
        while True:
            x1, ux1 = self._find_middle_point_and_utility(
                    lose_a, win_a, lose_u, win_u
                    )
            result.append((x1, ux1))

            x2, ux2 = self._find_middle_point_and_utility(
                    lose_a, x1, lose_u, ux1
                    )
            result.append((x2, ux2))

            x3, ux3 = self._find_middle_point_and_utility(
                    x1, win_a, ux1, win_u
                    )
            result.append((x3, ux3))

            x4, ux4 = self._find_middle_point_and_utility(
                    x2, x3, ux2, ux3
                    )
            #result.append((x4, ux4))

            if fabs(x1 - x4) <= delta:
                # result with starting points
                data_for_recursion = list(result)
                data_for_recursion.append((win_a, win_u))
                data_for_recursion.insert(0, (lose_a, lose_u))
                data_for_recursion = self._sort_results_list(
                        self._remove_duplicates_from_results_list(
                            data_for_recursion
                            )
                        )
                if semi_auto:
                    proportions = self._find_proportions(
                            result, lose_a, win_a
                            )
                for i in range(len(data_for_recursion) - 1):
                    if fabs(data_for_recursion[i][0] - \
                            data_for_recursion[i+1][0]) >= delta:
                        if semi_auto:
                            result.extend(self._propagate_results(
                                data_for_recursion[i][0], 
                                data_for_recursion[i+1][0],
                                data_for_recursion[i][1],
                                data_for_recursion[i+1][1],
                                delta,
                                proportions
                                ))
                        else:
                            result.extend(self._find_plot_points_on_int(
                                data_for_recursion[i][0], 
                                data_for_recursion[i+1][0],
                                data_for_recursion[i][1],
                                data_for_recursion[i+1][1],
                                delta
                                ))

                return self._sort_results_list(
                        self._remove_duplicates_from_results_list(result))
            else:
                print u"\nControversial input data - x4 doesn't coincide " \
                        + "with x1. Please correct your choices.\n"
                self._delta *= 1.5

    def _propagate_results(self, lose_a, win_a, lose_u, win_u, delta, \
            proportions):
        """
        Automatically finds points and utility values for plot on \
                specified interval.

        lose_a - left margin of interval
        win_a - right margin of interval
        lose_u - utility in left margin
        win_u - utility in right margin
        delta - precision   
        proportions - list of proportions of x's
        """
        result = []

        x1, ux1 = self._auto_find_middle_point_and_utility(
                lose_a, win_a, lose_u, win_u, proportions[0]
                )
        result.append((x1, ux1))

        x2, ux2 = self._auto_find_middle_point_and_utility(
                lose_a, x1, lose_u, ux1, proportions[1]
                )
        result.append((x2, ux2))

        x3, ux3 = self._auto_find_middle_point_and_utility(
                x1, win_a, ux1, win_u, proportions[2]
                )
        result.append((x3, ux3))

        x4, ux4 = self._auto_find_middle_point_and_utility(
                x2, x3, ux2, ux3, proportions[3]
                )
        #result.append((x4, ux4))

        # result with starting points
        data_for_recursion = list(result)
        data_for_recursion.append((win_a, win_u))
        data_for_recursion.insert(0, (lose_a, lose_u))
        data_for_recursion = self._sort_results_list(
                self._remove_duplicates_from_results_list(
                    data_for_recursion
                    )
                )
        
        for i in range(len(data_for_recursion) - 1):
            if fabs(data_for_recursion[i][0] - \
                    data_for_recursion[i+1][0]) >= delta:
                result.extend(self._propagate_results(
                            data_for_recursion[i][0], 
                            data_for_recursion[i+1][0],
                            data_for_recursion[i][1],
                            data_for_recursion[i+1][1],
                            delta,
                            proportions
                            )) 

        return self._sort_results_list(
                self._remove_duplicates_from_results_list(result))

    def _find_proportions(self, list_of_x, l_border, r_border):
        """
        Finds on which percents of interval found x's are located.
        Returns list of percents.

        list_of_x - x's found on interval
        l_border - left margin of interval
        r_border - right margin of interval
        """
        distance = r_border - l_border
        return [(x[0] - l_border) / distance for x in list_of_x]

    def _find_middle_point_and_utility(self, l_a, w_a, l_u, w_u):
        """
        Finds point and its utility which is equivalent to utility
        of interval.

        l_a - left margin of interval.
        w_a - right margin of interval.
        l_u - utility in left margin.
        w_u - utility in right margin.
        """
        print u"\nFinding value which utility is equivalent to utility of" \
                " game (" + unicode(l_a) + u", " + unicode(w_a) \
                + u") with probabilities (1/2, 1/2)"
        x = self._game.play(l_a, w_a)
        u = 0.5 * (l_u + w_u)
        return x, u

    def _auto_find_middle_point_and_utility(self, lose_a, win_a, lose_u, \
            win_u, proportion):
        """
        Automatically finds point and its utility which is equivalent to 
        utility of interval.

        l_a - left margin of interval.
        w_a - right margin of interval.
        l_u - utility in left margin.
        w_u - utility in right margin. 
        proportion - where the point is on inetrval.
        """
        x = lose_a + proportion * (win_a - lose_a)  
        u = 0.5 * (lose_u + win_u)
        return x, u

    def _sort_results_list(self, results_list):
        """Sorts results list (each element is tuple (point, utility))"""
        return sorted(results_list, key=lambda element: element[0])

    def _remove_duplicates_from_results_list(self, results_list):
        """Removes duplicated values from results list."""
        found = set()
        result = []
        for item in results_list:
            if item[0] not in found:
                result.append(item)
                found.add(item[0])
        return result

    def _build_plot_from_results_list(self, results_list):
        """Actually draws the plot using data from results_list."""
        print results_list
        # Preparing plot information
        plot_x_values = [element[0] for element in results_list]
        plot_y_values = [element[1] for element in results_list]
        plt.title(u"Utility function")
        plt.plot(plot_x_values, plot_y_values)
        plt.xlabel(u"Gainings")
        plt.ylabel(u"Utility")
        plt.show()
 def __init__(self):
     super(UtilityFunctionBuilder, self).__init__()
     self._left_margin = None
     self._right_margin = None
     self._delta = None
     self._game = FortuneGame()