class FormulaSelector(object):
    def __init__(self, fingerprint, model):
        self._fp = fingerprint.reshape(1, -1)
        self._model = model
        pass

    def refine_annotation(self, spectrum):
        known_lost_formula = set()
        for peak in spectrum[::-1]:
            peak['remain'] = []
            peak['lost_directly'] = []
            peak['lost_indirectly'] = set()
            self._refine_peak_annotation(peak, known_lost_formula)
            self._set_peak_annotation(peak, known_lost_formula)
            peak['lost_indirectly'] = list(peak['lost_indirectly'])
            peak['lost_directly'] += peak['lost_indirectly']
            pass
        pass

    def _refine_peak_annotation(self, peak, all_known_loss):
        if len(peak['anno']) == 0:
            return {}

        if len(peak['anno']) > 1:
            max_possibility = -1
            max_item = []

            for cur_feature in peak['anno']:
                cur_possibility = self._calculate_feature_possibility(cur_feature, all_known_loss)

                if cur_possibility > max_possibility:
                    max_item = [cur_feature]
                    max_possibility = cur_possibility
                elif cur_possibility == max_possibility:
                    max_item.append(cur_feature)
                pass
            peak['anno'] = max_item
        else:
            self._calculate_feature_possibility(peak['anno'][0], all_known_loss)
            pass

        pass

    @classmethod
    def _set_peak_annotation(cls, peak, all_known_loss):
        for item in peak['anno']:
            peak['remain'].append((0, item[0]))
            if type(item[1]) is list:
                peak['lost_indirectly'].add((0, item[1][1]))
            else:
                all_known_loss.add(item[1])
                peak['lost_directly'].append((0, item[1]))
        pass

    def _calculate_feature_possibility(self, cur_feature, all_known_loss):
        cur_max_possibility = -1
        # Calculate remain and directly loss
        for feature_place, feature_type in [(0, 'remain'), (1, 'lost_directly')]:
            cur_possibility = self._model.score_feature(feature_type,
                                                        (0, cur_feature[feature_place]),
                                                        self._fp)[0]
            if cur_possibility > cur_max_possibility:
                cur_max_possibility = cur_possibility

        # Calculate indirectly loss
        cur_direct_loss = cur_feature[1]
        for known_loss in all_known_loss:
            cur_delta_loss = cur_direct_loss.sub_not_allow_zero(known_loss)
            if cur_delta_loss is not None:
                cur_possibility = self._model.score_feature('lost_directly',
                                                            (0, cur_delta_loss),
                                                            self._fp)[0]
                if cur_possibility > cur_max_possibility:
                    cur_max_possibility = cur_possibility
                    cur_feature[1] = [known_loss, cur_delta_loss]
            pass
        return cur_max_possibility

    pass
