import logging
from numpy import nan
from collections import Counter
from protmapper import uniprot_client, ProtMapper
from indra.statements import *
from indra.databases import hgnc_client
from indra.statements.validate import validate_id


logger = logging.getLogger(__name__)


# Map of HPRD PTM types to INDRA Statement types
_ptm_map = {
    'ADP-Ribosylation': None,
    'Acetylation': Acetylation,
    'Alkylation': None,
    'Amidation': None,
    'Carboxylation': None,
    'Deacetylation': Deacetylation,
    'Deacylation': None,
    'Deglycosylation': Deglycosylation,
    'Demethylation': Demethylation,
    'Dephosphorylation': Dephosphorylation,
    'Disulfide Bridge': None,
    'Farnesylation': Farnesylation,
    'Glucuronosyl transfer': None,
    'Glutathionylation': None,
    'Glycation': None,
    'Glycosyl phosphatidyl inositol GPI': None,
    'Glycosylation': Glycosylation,
    'Hydroxylation': Hydroxylation,
    'Methylation': Methylation,
    'Myristoylation': Myristoylation,
    'Neddylation': None,
    'Nitration': None,
    'Palmitoylation': Palmitoylation,
    'Phosphorylation': Phosphorylation,
    'Prenylation': None,
    'Proteolytic Cleavage': None,
    'S-Nitrosylation': None,
    'Sulfation': None,
    'Sumoylation': Sumoylation,
    'Transglutamination': None,
    'Ubiquitination': Ubiquitination,
}


class HprdProcessor(object):
    """Get INDRA Statements from HPRD data.

    See documentation for `indra.sources.hprd.api.process_flat_files.`

    Parameters
    ----------
    id_df : pandas.DataFrame
        DataFrame loaded from the HPRD_ID_MAPPINGS.txt file.
    cplx_df :  pandas.DataFrame
        DataFrame  loaded from the PROTEIN_COMPLEXES.txt file.
    ptm_df : pandas.DataFrame
        DataFrame loaded from the POST_TRANSLATIONAL_MODIFICATIONS.txt file.
    ppi_df : pandas.DataFrame
        DataFrame loaded from the BINARY_PROTEIN_PROTEIN_INTERACTIONS.txt file.
    seq_dict : dict
        Dictionary mapping RefSeq IDs to protein sequences, loaded from the
        PROTEIN_SEQUENCES.txt file.
    motif_window : int
        Number of flanking amino acids to include on each side of the
        PTM target residue in the 'site_motif' annotations field of the
        Evidence for Modification Statements. Default is 7.

    Attributes
    ----------
    statements : list of INDRA Statements
        INDRA Statements (Modifications and Complexes) produced from the
        HPRD content.
    id_df : pandas.DataFrame
        DataFrame loaded from HPRD_ID_MAPPINGS.txt file.
    seq_dict : 
        Dictionary mapping RefSeq IDs to protein sequences, loaded from the
        PROTEIN_SEQUENCES.txt file.
    no_hgnc_for_egid: collections.Counter
        Counter listing Entrez gene IDs reference in the HPRD content that
        could not be mapped to a current HGNC ID, along with their frequency.
    no_up_for_hgnc : collections.Counter
        Counter with tuples of form (entrez_id, hgnc_symbol, hgnc_id) where
        the HGNC ID could not be mapped to a Uniprot ID, along with their
        frequency.
    no_up_for_refseq : collections.Counter
        Counter of RefSeq protein IDs that could not be mapped to any Uniprot
        ID, along with frequency.
    many_ups_for_refseq : collections.Counter
        Counter of RefSeq protein IDs that yielded more than one matching
        Uniprot ID. Note that in these cases, the Uniprot ID obtained from
        HGNC is used.
    invalid_site_pos : list of tuples
        List of tuples of form (refseq_id, residue, position) indicating sites
        of post translational modifications where the protein sequences
        provided by HPRD did not contain the given residue at the given
        position.
    off_by_one : list of tuples
        The subset of sites contained in `invalid_site_pos` where the given
        residue can be found at position+1 in the HPRD protein sequence,
        suggesting an off-by-one error due to numbering based on the
        protein with initial methionine cleaved. Note that no mapping is
        performed by the processor.
    motif_window : int
        Number of flanking amino acids to include on each side of the
        PTM target residue in the 'site_motif' annotations field of the
        Evidence for Modification Statements. Default is 7.
    """
    def __init__(self, id_df, cplx_df=None, ptm_df=None, ppi_df=None,
                 seq_dict=None, motif_window=7):
        if cplx_df is None and ptm_df is None and ppi_df is None:
            raise ValueError('At least one of cplx_df, ptm_df, or ppi_df must '
                             'be specified.')
        if ptm_df is not None and not seq_dict:
            raise ValueError('If ptm_df is given, seq_dict must also be given.')

        self.statements = []
        self.id_df = id_df
        self.seq_dict = seq_dict
        self.motif_window = motif_window

        # Keep track of the ID mapping issues encountered
        self.no_hgnc_for_egid = Counter()
        self.no_up_for_hgnc = Counter()
        self.no_up_for_refseq = Counter()
        self.many_ups_for_refseq = Counter()
        self.invalid_site_pos = []
        self.off_by_one = []

        # Do the actual processing
        if cplx_df is not None:
            self.get_c
omplexes(cplx_df)
        if ptm_df is not None:
            self.get_ptms(ptm_df)
        if ppi_df is not None:
            self.get_ppis(ppi_df)

        # Tabulate IDs causing issues
        self.no_hgnc_for_egid = Counter(s
elf.no_hgnc_for_egid)
        self.no_up_for_hgnc = Counter(self.no_up_for_hgnc)
        self.no_up_for_refseq = Counter(self.no_up_for_refseq)
        self.many_ups_for_refseq = Counter(self.many_ups_for_refseq)

        logger.info('For information on problematic entries encountered while '
                    'processing, see HprdProcessor attributes: '
                    'no_hgnc_for_egid, no_up_for_hgnc, no_up_for_refseq, '
                    'many_ups_for_refseq, invalid_site_pos, and off_by_one.')

    def get_complexes(self, cplx_df):
        """Generate Complex Statements from the HPRD protein complexes data.

        Parameters
        ----------
        cplx_df :  pandas.DataFrame
            DataFrame  loaded from the PROTEIN_COMPLEXES.txt file.
        """
        # Group the agents for the complex
        logger.info('Processing complexes...')
        for cplx_id, this_cplx in cplx_df.groupby('CPLX_ID'):
            agents = []
            for hprd_id in this_cplx.HPRD_ID:
                ag = self._make_agent(hprd_id)
                if ag is not None:
                    agents.append(ag)
            # Make sure we got some agents!
            if not agents:
                continue
            # Get evidence info from first member of complex
            row0 = this_cplx.iloc[0]
            isoform_id = '%s_1' % row0.HPRD_ID
            ev_list = self._get_evidence(row0.HPRD_ID, isoform_id, row0.PMIDS,
                                         row0.EVIDENCE, 'interactions')
            stmt = Complex(agents, evidence=ev_list)
            self.statements.append(stmt)

    def get_ptms(self, ptm_df):
        """Generate Modification statements from the HPRD PTM data.

        Parameters
        ----------
        ptm_df : pandas.DataFrame
            DataFrame loaded from the POST_TRANSLATIONAL_MODIFICATIONS.txt file.
        """
        logger.info('Processing PTMs...')
        # Iterate over the rows of the dataframe
        for ix, row in ptm_df.iterrows():
            # Check the modification type; if we can't make an INDRA statement
            # for it, then skip it
            ptm_class = _ptm_map[row['MOD_TYPE']]
            if ptm_class is None:
                continue
            # Use the Refseq protein ID for the substrate to make sure that
            # we get the right Uniprot ID for the isoform
            sub_ag = self._make_agent(row['HPRD_ID'],
                                      refseq_id=row['REFSEQ_PROTEIN'])

            # If we couldn't get the substrate, skip the statement
            if sub_ag is None:
                continue
            enz_id = _nan_to_none(row['ENZ_HPRD_ID'])
            enz_