import math
import statistics
from itertools import groupby
from random import randint
from typing import Dict, Tuple, Counter

import pandas as pd

from Src.BioAnalyzer.Analysis.GenePrioritization.Steps.DataIntegration.IntermediateRepresentation.Generators import \
    IntermediateRepresentationGeneratorBase
from Src.BioAnalyzer.Analysis.GenePrioritization.Steps.DataIntegration.IntermediateRepresentation.Transformers.SampleTransformerBase import \
    SampleTransformerBase
from Src.BioDataManagement.CrossCutting.DTOs.ExpressionLevelStatusDto import ExpressionLevelStatusDto


class MicroRnaToGeneTransformer(SampleTransformerBase):
    """    
    """
    def __init__(self,
                 intermediateRepresentationGenerator: IntermediateRepresentationGeneratorBase,
                 get_global_diff_values_action,
                 get_mirna_gene_target_action):
        super().__init__(intermediateRepresentationGenerator)
        self.__get_mirna_gene_target_action = get_mirna_gene_target_action
        self.__get_global_diff_values_action = get_global_diff_values_action

    def transform(self, from_sample_matrix: pd.DataFrame, is_highly_significant: bool) -> Tuple[pd.DataFrame, Dict[int, ExpressionLevelStatusDto]]:
        mirna_gene_targets = {mirna.lower(): g for mirna, g in
                              self.__get_mirna_gene_targets(from_sample_matrix.columns.tolist()).items()}

        mirna_samples = self.__get_mirna_samples(from_sample_matrix, mirna_gene_targets)

        id_entrez_list = list(set([id_entrez for mirna_symbol, id_entrez_list in mirna_gene_targets.items()
                                   for id_entrez in id_entrez_list]))

        measure_matrix = dict([(g, []) for g in id_entrez_list])
        key_func = lambda gene: gene[0]

        for patient_id, exp_values in mirna_samples.items():
            gene_values = [(id_entrez,
                            exp_value) for mirna_symbol, exp_value in exp_values.items()
                                       for id_entrez in mirna_gene_targets[mirna_symbol]]

            gene_values = sorted(gene_values, key=key_func)

            for id_entrez, measures in groupby(gene_values, key_func):
                measures = [measure for id_entrez, measure in list(measures) if not math.isnan(measure)]
                measure_matrix[id_entrez].append(float('NaN') if not measures else statistics.mean(measures))

        gene_matrix = pd.DataFrame.from_dict(measure_matrix).dropna(axis=1,how='all')
        gene_matrix = self.intermediateRepresentationGenerator.generate(gene_matrix).dropna(axis=1,how='all')

        return gene_matrix, \
               self.__get_gene_status(mirna_gene_targets, gene_matrix.columns.tolist(), is_highly_significant)

    def __get_mirna_gene_targets(self, mirnas):
        gene_targets = {}
        fe_target = self.__get_mirna_gene_target_action(mirnas)

        gene_targets.update(dict([(t.microrna_symbol, list(set(gene_targets[t.microrna_symbol] + t.id_entrez_genes)))
                                      if t.microrna_symbol in gene_targets
                                      else (t.microrna_symbol, t.id_entrez_genes) for t in fe_target.result_list]))

        return gene_targets

    def __get_mirna_samples(self, from_sample_matrix, mirna_gene_targets):
        from_sample_matrix = from_sample_matrix[list(mirna_gene_targets.keys()) + ['patient_id']]
        from_sample_matrix.set_index("patient_id", drop=True, inplace=True)
        return from_sample_matrix.to_dict(orient="index")

    def __get_gene_status(self, mirna_gene_targets, genes, is_highly_significant):
        diff_mirna = [diff for diff in self.__get_global_diff_values_action(is_highly_significant).result.values
                      if diff.element_id in mirna_gene_targets]

        genes_status = [(g, diff.status) for diff in diff_mirna
                                      for g in mirna_gene_targets[diff.element_id] if g in genes]

        key_func = lambda gene: gene[0]
        genes_status = sorted(genes_status, key=key_func)

        genes_status_dict = {}
        for id_entrez, status in groupby(genes_status, key_func):
            status = list(status)
            status_counter = Counter(status)
            status = [k for k, v in status_counter.most_common()]
            len_status = len(status) - 1

            genes_status_dict[id_entrez] = status[0] if len_status == 1 else status[randint(0, len_status)]

        return dict([(entrez_id, status[1]) for entrez_id, status in genes_status_dict.items()])