# SPDX-FileCopyrightText: 2008-2023 Luis Falcón <falcon@gnuhealth.org>
# SPDX-FileCopyrightText: 2011-2023 GNU Solidario <health@gnusolidario.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later

#########################################################################
#   Hospital Management Information System (HMIS) component of the      #
#                       GNU Health project                              #
#                   https://www.gnuhealth.org                           #
#########################################################################
#                       HEALTH GENETICS package                         #
#                  health_genetics.py: main module                      #
#########################################################################
from trytond.model import ModelView, ModelSQL, fields, Unique
from trytond.pyson import Eval
from trytond.pool import Pool
from uuid import uuid4
from trytond.modules.health.core import get_institution

__all__ = ['DiseaseGene', 'ProteinDisease', 'GeneVariant',
           'GeneVariantPhenotype',
           'PatientGeneticRisk', 'FamilyDiseases', 'GnuHealthPatient']


class DiseaseGene(ModelSQL, ModelView):
    'Disease Genes'
    __name__ = 'gnuhealth.disease.gene'

    name = fields.Char('Gene Name', required=True, select=True)
    protein_name = fields.Char('Protein Code',
                               help="Encoding Protein Code, \
                               such as UniProt protein name",
                               select=True)
    long_name = fields.Char('Official Long Name', translate=True)
    gene_id = fields.Char('Gene ID',
                          help="default code from NCBI Entrez database.",
                          select=True)
    chromosome = fields.Char('Chromosome',
                             help="Name of the affected chromosome",
                             select=True)
    location = fields.Char('Location', help="Locus of the chromosome")

    info = fields.Text('Information', help="Extra Information")
    variants = fields.One2Many('gnuhealth.gene.variant', 'name',
                               'Variants')

    protein_uri = fields.Function(fields.Char("Protein URI"),
                                  'get_protein_uri')

    def get_protein_uri(self, name):
        ret_url = ''
        if (self.protein_name):
            ret_url = 'http://www.uniprot.org/uniprot/' + \
                str(self.protein_name)
        return ret_url

    @classmethod
    def __setup__(cls):
        super(DiseaseGene, cls).__setup__()

        t = cls.__table__()
        cls._sql_constraints = [
            ('name_unique', Unique(t, t.name),
                'The Official Symbol name must be unique'),
            ]

    def get_rec_name(self, name):
        protein = ''
        if self.protein_name:
            protein = ' (' + self.protein_name + ') '
        return self.name + protein + ':' + self.long_name

    @classmethod
    def search_rec_name(cls, name, clause):
        if clause[1].startswith('!') or clause[1].startswith('not '):
            bool_op = 'AND'
        else:
            bool_op = 'OR'
        return [bool_op,
                ('name',) + tuple(clause[1:]),
                ('long_name',) + tuple(clause[1:]),
                ]

    """
    #Obsoleted. Old (3.2) migration
    @classmethod
    # Update to version 3.2
    def __register__(cls, module_name):
        super(DiseaseGene, cls).__register__(module_name)

        TableHandler = backend.get('TableHandler')
        table = TableHandler(cls, module_name)
        # Insert the current "specialty" associated to the HP in the
        # table that keeps the specialties associated to different health
        # professionals, gnuhealth.hp_specialty

        if table.column_exist('dominance'):
            # Drop old dominance column
            # which is now part of the gene variant phenotype
            table.drop_column('dominance')
    """


class ProteinDisease(ModelSQL, ModelView):
    'Protein related disorders'
    __name__ = 'gnuhealth.protein.disease'

    name = fields.Char('Disease', required=True, select=True,
                       help="Uniprot Disease Code")

    disease_name = fields.Char('Disease name', translate=True)
    acronym = fields.Char('Acronym', required=True, select=True,
                          help="Disease acronym / mnemonics")

    disease_uri = fields.Function(fields.Char("Disease URI"),
                                  'get_disease_uri')

    mim_reference = fields.Char('MIM',
                                help="MIM - "
                                "Mendelian Inheritance in Man- DB reference")

    gene_variant = fields.One2Many('gnuhealth.gene.variant.phenotype',
                                   'phenotype',
                                   'Natural Variant',
                                   help="Protein sequence variant(s) "
                                        "involved in this condition")

    dominance = fields.Selection([
        (None, ''),
        ('d', 'dominant'),
        ('r', 'recessive'),
        ('c', 'codominance'),
        ], 'Dominance', sort=False, select=True)

    description = fields.Text('Description')

    active = fields.Boolean('Active', help="Whether this code is current."
                            "If you deactivate it, the code will "
                            "no longer show in the"
                            " protein-related diseases")

    @staticmethod
    def default_active():
        return True

    def get_disease_uri(self, name):
        ret_url = ''
        if (self.name):
            ret_url = 'http://www.uniprot.org/diseases/' + \
                str(self.name)
        return ret_url

    @classmethod
    def __setup__(cls):
        super(ProteinDisease, cls).__setup__()

        t = cls.__table__()
        cls._sql_constraints = [
            ('name_unique', Unique(t, t.name),
                'The Disease Code  name must be unique'),
            ]

    def get_rec_name(self, name):
        return self.name + ':' + self.disease_name

    @classmethod
    def search_rec_name(cls, name, clause):
        if clause[1].startswith('!') or clause[1].startswith('not '):
            bool_op = 'AND'
        else:
            bool_op = 'OR'
        return [bool_op,
                ('name',) + tuple(clause[1:]),
                ('disease_name',) + tuple(clause[1:]),
                ]


class GeneVariant(ModelSQL, ModelView):
    'Natural Variant'
    __name__ = 'gnuhealth.gene.variant'

    name = fields.Many2One('gnuhealth.disease.gene', 'Gene and Protein',
                           required=True,
                           help="Gene and expressing protein (in parenthesis)")
    variant = fields.Char("Protein Variant", required=True, select=True)
    aa_change = fields.Char('Change', help="Resulting amino acid change")
    phenotypes = fields.One2Many('gnuhealth.gene.variant.phenotype', 'variant',
                                 'Phenotypes')

    @classmethod
    def __setup__(cls):
        super(GeneVariant, cls).__setup__()

        t = cls.__table__()
        cls._sql_constraints = [
            ('variant_unique', Unique(t, t.variant),
                'The variant ID must be unique'),
            ('aa_unique', Unique(t, t.variant, t.aa_change),
                'The resulting AA change for this protein already exists'),
            ]

    def get_rec_name(self, name):
        return ' : '.join([self.variant, self.aa_change])

    # Allow to search by gene and variant or amino acid change
    @classmethod
    def search_rec_name(cls, name, clause):
        if clause[1].startswith('!') or clause[1].startswith('not '):
            bool_op = 'AND'
        else:
            bool_op = 'OR'
        return [bool_op,
                ('name',) + tuple(clause[1:]),
                ('variant',) + tuple(clause[1:]),
                ('aa_change',) + tuple(clause[1:]),
                ]


class GeneVariantPhenotype(ModelSQL, ModelView):
    'Variant Phenotypes'
    __name__ = 'gnuhealth.gene.variant.phenotype'

    name = fields.Char('Code', required=True)
    variant = fields.Many2One('gnuhealth.gene.variant', 'Variant',
                              required=True)

    gene = fields.Function(fields.Many2One(
        'gnuhealth.disease.gene', 'Gene & Protein',
        depends=['variant'],
        help="Gene and expressing protein (in parenthesis)"),
        'get_gene',
        searcher='search_gene')

    phenotype = fields.Many2One('gnuhealth.protein.disease', 'Phenotype',
                                required=True)

    def get_gene(self, name):
        if (self.variant):
            return self.variant.name.id

    def get_rec_name(self, name):
        if self.phenotype:
            return self.phenotype.rec_name

    @classmethod
    def search_gene(cls, name, clause):
        res = []
        value = clause[2]
        res.append(('variant.name', clause[1], value))
        return res

    # Allow to search by gene, variant or phenotype
    @classmethod
    def search_rec_name(cls, name, clause):
        if clause[1].startswith('!') or clause[1].startswith('not '):
            bool_op = 'AND'
        else:
            bool_op = 'OR'
        return [bool_op,
                ('variant',) + tuple(clause[1:]),
                ('phenotype',) + tuple(clause[1:]),
                ('gene',) + tuple(clause[1:]),
                ]

    @classmethod
    def __setup__(cls):
        super(GeneVariantPhenotype, cls).__setup__()

        t = cls.__table__()
        cls._sql_constraints = [
            ('code', Unique(t, t.name),
                'This code already exists'),
                ]


class PatientGeneticRisk(ModelSQL, ModelView):
    'Patient Genetic Information'
    __name__ = 'gnuhealth.patient.genetic.risk'

    patient = fields.Many2One('gnuhealth.patient', 'Patient', select=True)
    disease_gene = fields.Many2One('gnuhealth.disease.gene',
                                   'Gene', required=True)
    natural_variant = fields.Many2One('gnuhealth.gene.variant', 'Variant',
                                      domain=[('name', '=',
                                              Eval('disease_gene'))],
                                      depends=['disease_gene'])

    variant_phenotype = fields.Many2One('gnuhealth.gene.variant.phenotype',
                                        'Phenotype',
                                        domain=[('variant', '=',
                                                Eval('natural_variant'))],
                                        depends=['natural_variant'])

    onset = fields.Integer('Onset', help="Age in years")

    notes = fields.Char("Notes")

    healthprof = fields.Many2One(
        'gnuhealth.healthprofessional', 'Health prof',
        help="Health professional")

    institution = fields.Many2One('gnuhealth.institution', 'Institution')

    @staticmethod
    def default_institution():
        return get_institution()

    @classmethod
    def create_genetics_pol(cls, genetic_info):
        """ Adds an entry in the person Page of Life
            related to this genetic finding
        """
        Pol = Pool().get('gnuhealth.pol')
        pol = []

        vals = {
            'page': str(uuid4()),
            'person': genetic_info.patient.name.id,
            'age': genetic_info.onset and str(genetic_info.onset) + 'y' or '',
            'federation_account': genetic_info.patient.name.federation_account,
            'page_type': 'medical',
            'medical_context': 'genetics',
            'relevance': 'important',
            'gene': genetic_info.disease_gene.rec_name,
            'natural_variant': genetic_info.natural_variant and
                               genetic_info.natural_variant.aa_change,
            'summary': genetic_info.notes,
            'author': genetic_info.healthprof and
            genetic_info.healthprof.name.rec_name,
            'node': genetic_info.institution and
            genetic_info.institution.name.rec_name
            }
        if (genetic_info.variant_phenotype):
            vals['health_condition_text'] = vals['health_condition_text'] = \
                genetic_info.variant_phenotype.phenotype.rec_name

        pol.append(vals)
        Pol.create(pol)

    @classmethod
    def create(cls, vlist):

        # Execute first the creation of PoL
        genetic_info = super(PatientGeneticRisk, cls).create(vlist)

        cls.create_genetics_pol(genetic_info[0])

        return genetic_info

    @classmethod
    def search_rec_name(cls, name, clause):
        if clause[1].startswith('!') or clause[1].startswith('not '):
            bool_op = 'AND'
        else:
            bool_op = 'OR'
        return [bool_op,
                ('patient',) + tuple(clause[1:]),
                ('disease_gene',) + tuple(clause[1:]),
                ('variant_phenotype',) + tuple(clause[1:]),
                ]


class FamilyDiseases(ModelSQL, ModelView):
    'Family History'
    __name__ = 'gnuhealth.patient.family.diseases'

    patient = fields.Many2One('gnuhealth.patient', 'Patient', select=True)
    name = fields.Many2One('gnuhealth.pathology', 'Condition', required=True)
    xory = fields.Selection([
        (None, ''),
        ('m', 'Maternal'),
        ('f', 'Paternal'),
        ('s', 'Sibling'),
        ], 'Maternal or Paternal', select=True)

    relative = fields.Selection([
        ('mother', 'Mother'),
        ('father', 'Father'),
        ('brother', 'Brother'),
        ('sister', 'Sister'),
        ('aunt', 'Aunt'),
        ('uncle', 'Uncle'),
        ('nephew', 'Nephew'),
        ('niece', 'Niece'),
        ('grandfather', 'Grandfather'),
        ('grandmother', 'Grandmother'),
        ('cousin', 'Cousin'),
        ], 'Relative',
        help='First degree = siblings, mother and father\n'
             'Second degree = Uncles, nephews and Nieces\n'
             'Third degree = Grandparents and cousins',
        required=True)


class GnuHealthPatient (ModelSQL, ModelView):
    """
    Add to the Medical patient_data class (gnuhealth.patient) the genetic
    and family risks"""
    __name__ = 'gnuhealth.patient'

    genetic_risks = fields.One2Many('gnuhealth.patient.genetic.risk',
                                    'patient', 'Genetic Information')
    family_history = fields.One2Many('gnuhealth.patient.family.diseases',
                                     'patient', 'Family History')
