# -*- coding: utf-8 -*-
##############################################################################
#
#    GNU Health: The Free Health and Hospital Information System
#    Copyright (C) 2008-2022 Luis Falcon <lfalcon@gnusolidario.org>
#    Copyright (C) 2011-2022 GNU Solidario <health@gnusolidario.org>
#
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from trytond.model import ModelView, ModelSQL, fields
from datetime import datetime
from dateutil.relativedelta import relativedelta
from trytond.transaction import Transaction
from trytond.pyson import Eval, Not, Bool, Equal
from .exceptions import PatientAlreadyInICU
from trytond.i18n import gettext


__all__ = ['InpatientRegistration', 'InpatientIcu', 'Glasgow', 'ApacheII',
            'MechanicalVentilation', 'ChestDrainageAssessment',
            'PatientRounding']


class InpatientRegistration(ModelSQL, ModelView):
    'Patient admission History'
    __name__ = 'gnuhealth.inpatient.registration'
    icu = fields.Boolean('ICU', help='Shows if patient was admitted to'
        ' the Intensive Care Unit during the hospitalization period')
    icu_admissions = fields.One2Many('gnuhealth.inpatient.icu',
        'name', "ICU Admissions")


class InpatientIcu(ModelSQL, ModelView):
    'Patient ICU Information'
    __name__ = 'gnuhealth.inpatient.icu'

    def icu_duration(self, name):
        if self.discharged_from_icu:
            end = self.icu_discharge_date
        else:
            end = datetime.now()
        return end.date() - self.icu_admission_date.date()

    name = fields.Many2One('gnuhealth.inpatient.registration',
        'Registration Code', required=True)

    admitted = fields.Boolean('Admitted', help="Will be set when the patient \
        is currently admitted at ICU")

    icu_admission_date = fields.DateTime('ICU Admission',
        help="ICU Admission Date", required=True)
    discharged_from_icu = fields.Boolean('Discharged')
    icu_discharge_date = fields.DateTime('Discharge', states={
            'invisible': Not(Bool(Eval('discharged_from_icu'))),
            'required': Bool(Eval('discharged_from_icu')),
            },
        depends=['discharged_from_icu'])
    icu_stay = fields.Function(fields.TimeDelta('Duration'), 'icu_duration')

    mv_history = fields.One2Many('gnuhealth.icu.ventilation',
        'name', "Mechanical Ventilation History")

    @classmethod
    def validate(cls, inpatients):
        super(InpatientIcu, cls).validate(inpatients)
        for inpatient in inpatients:
            inpatient.check_patient_admitted_at_icu()

    def check_patient_admitted_at_icu(self):
        # Verify that the patient is not at ICU already
        cursor = Transaction().connection.cursor()
        cursor.execute("SELECT count(name) "
            "FROM " + self._table + "  \
            WHERE (name = %s AND admitted)",
            (str(self.name.id),))
        if cursor.fetchone()[0] > 1:
            raise PatientAlreadyInICU(
                gettext('health_icu.msg_patient_already_in_icu'))

    @staticmethod
    def default_admitted():
        return True

    @fields.depends('discharged_from_icu')
    def on_change_with_admitted(self):
        # Reset the admission flag when the patient is discharged from ICU
        if self.discharged_from_icu:
            res = False
        else:
            res = True
        return res


class Glasgow(ModelSQL, ModelView):
    'Glasgow Coma Scale'
    __name__ = 'gnuhealth.icu.glasgow'

    name = fields.Many2One('gnuhealth.inpatient.registration',
        'Registration Code', required=True)

    evaluation_date = fields.DateTime('Date', help="Date / Time",
        required=True)

    glasgow = fields.Integer('Glasgow',
        help='Level of Consciousness - on Glasgow Coma Scale :  < 9 severe -'
        ' 9-12 Moderate, > 13 minor')
    glasgow_eyes = fields.Selection([
        ('1', '1 : Does not Open Eyes'),
        ('2', '2 : Opens eyes in response to painful stimuli'),
        ('3', '3 : Opens eyes in response to voice'),
        ('4', '4 : Opens eyes spontaneously'),
        ], 'Eyes', sort=False)
    glasgow_verbal = fields.Selection([
        ('1', '1 : Makes no sounds'),
        ('2', '2 : Incomprehensible sounds'),
        ('3', '3 : Utters inappropriate words'),
        ('4', '4 : Confused, disoriented'),
        ('5', '5 : Oriented, converses normally'),
        ], 'Verbal', sort=False)
    glasgow_motor = fields.Selection([
        ('1', '1 : Makes no movement'),
        ('2', '2 : Extension to painful stimuli - decerebrate response -'),
        ('3', '3 : Abnormal flexion to painful stimuli \
            (decorticate response)'),
        ('4', '4 : Flexion / Withdrawal to painful stimuli'),
        ('5', '5 : localizes painful stimuli'),
        ('6', '6 : Obeys commands'),
        ], 'Motor', sort=False)

    @staticmethod
    def default_glasgow_eyes():
        return '4'

    @staticmethod
    def default_glasgow_verbal():
        return '5'

    @staticmethod
    def default_glasgow_motor():
        return '6'

    @staticmethod
    def default_glasgow():
        return 15

    # Default evaluation date
    @staticmethod
    def default_evaluation_date():
        return datetime.now()

    @fields.depends('glasgow_verbal', 'glasgow_motor', 'glasgow_eyes')
    def on_change_with_glasgow(self):
        return int(self.glasgow_motor) + int(self.glasgow_eyes) + \
            int(self.glasgow_verbal)

    # Return the Glasgow Score with each component
    def get_rec_name(self, name):
        if self.name:
            res = str(self.glasgow) + ': ' + 'E' + self.glasgow_eyes + ' V' + \
                self.glasgow_verbal + ' M' + self.glasgow_motor
        return res


class ApacheII(ModelSQL, ModelView):
    'Apache II scoring'
    __name__ = 'gnuhealth.icu.apache2'

    name = fields.Many2One('gnuhealth.inpatient.registration',
        'Registration Code', required=True)
    score_date = fields.DateTime('Date', help="Date of the score",
        required=True)

    age = fields.Integer('Age', help='Patient age in years')
    temperature = fields.Float('Temperature', help='Rectal temperature')
    mean_ap = fields.Integer('MAP', help='Mean Arterial Pressure')
    heart_rate = fields.Integer('Heart Rate')
    respiratory_rate = fields.Integer('Respiratory Rate')
    fio2 = fields.Float('FiO2')
    pao2 = fields.Integer('PaO2')
    paco2 = fields.Integer('PaCO2')
    aado2 = fields.Integer('A-a DO2')

    ph = fields.Float('pH')
    serum_sodium = fields.Integer('Sodium')
    serum_potassium = fields.Float('Potassium')
    serum_creatinine = fields.Float('Creatinine')
    arf = fields.Boolean('ARF', help='Acute Renal Failure')
    wbc = fields.Float('WBC', help="White blood cells x 1000 - if you"
        " want to input 4500 wbc / ml, type in 4.5")
    hematocrit = fields.Float('Hematocrit')
    gcs = fields.Integer('GSC', help='Last Glasgow Coma Scale'
        ' You can use the GSC calculator from the Patient Evaluation Form.')
    chronic_condition = fields.Boolean('Chronic condition',
        help='Organ Failure or immunocompromised patient')
    hospital_admission_type = fields.Selection([
        (None, ''),
        ('me', 'Medical or emergency postoperative'),
        ('el', 'elective postoperative')],
        'Hospital Admission Type', states={
            'invisible': Not(Bool(Eval('chronic_condition'))),
            'required': Bool(Eval('chronic_condition'))}, sort=False)

    apache_score = fields.Integer('Score')

    #Default FiO2 PaO2 and PaCO2 so we do the A-a gradient
    #calculation with non-null values

    @fields.depends('fio2', 'pao2', 'paco2')
    def on_change_with_aado2(self):
    # Calculates the Alveolar-arterial difference
    # based on FiO2, PaCO2 and PaO2 values
        if (self.fio2 and self.paco2 and self.pao2):
            return (713 * self.fio2) - (self.paco2 / 0.8) - self.pao2

    @fields.depends('age', 'temperature', 'mean_ap', 'heart_rate',
        'respiratory_rate', 'fio2', 'pao2', 'aado2', 'ph', 'serum_sodium',
        'serum_potassium', 'serum_creatinine', 'arf', 'wbc', 'hematocrit',
        'gcs', 'chronic_condition', 'hospital_admission_type')
    def on_change_with_apache_score(self):
    # Calculate the APACHE SCORE from the variables in the

        total = 0
        # Age
        if (self.age):
            if (self.age > 44 and self.age < 55):
                total = total + 2
            elif (self.age > 54 and self.age < 65):
                total = total + 3
            elif (self.age > 64 and self.age < 75):
                total = total + 5
            elif (self.age > 74):
                total = total + 6

        # Temperature
        if (self.temperature):
            if ((self.temperature >= 38.5 and self.temperature < 39) or
                (self.temperature >= 34 and self.temperature < 36)):
                    total = total + 1
            elif (self.temperature >= 32 and self.temperature < 34):
                total = total + 2
            elif ((self.temperature >= 30 and self.temperature < 32) or
                (self.temperature >= 39 and self.temperature < 41)):
                total = total + 3
            elif (self.temperature >= 41 or self.temperature < 30):
                total = total + 4

        # Mean Arterial Pressure (MAP)
        if (self.mean_ap):
            if ((self.mean_ap >= 110 and self.mean_ap < 130) or
                (self.mean_ap >= 50 and self.mean_ap < 70)):
                    total = total + 2
            elif (self.mean_ap >= 130 and self.mean_ap < 160):
                total = total + 3
            elif (self.mean_ap >= 160 or self.mean_ap < 50):
                total = total + 4

        # Heart Rate
        if (self.heart_rate):
            if ((self.heart_rate >= 55 and self.heart_rate < 70) or
                (self.heart_rate >= 110 and self.heart_rate < 140)):
                    total = total + 2
            elif ((self.heart_rate >= 40 and self.heart_rate < 55) or
                (self.heart_rate >= 140 and self.heart_rate < 180)):
                    total = total + 3
            elif (self.heart_rate >= 180 or self.heart_rate < 40):
                total = total + 4

        # Respiratory Rate
        if (self.respiratory_rate):
            if ((self.respiratory_rate >= 10 and self.respiratory_rate < 12) or
                (self.respiratory_rate >= 25 and self.respiratory_rate < 35)):
                    total = total + 1
            elif (self.respiratory_rate >= 6 and self.respiratory_rate < 10):
                    total = total + 2
            elif (self.respiratory_rate >= 35 and self.respiratory_rate < 50):
                    total = total + 3
            elif (self.respiratory_rate >= 50 or self.respiratory_rate < 6):
                total = total + 4

        # FIO2
        if (self.fio2):
            # If Fi02 is greater than 0.5, we measure the AaDO2 gradient
            # Otherwise, we take into account the Pa02 value

            if (self.fio2 >= 0.5):
                if (self.aado2 >= 200 and self.aado2 < 350):
                    total = total + 2

                elif (self.aado2 >= 350 and self.aado2 < 500):
                    total = total + 3

                elif (self.aado2 >= 500):
                    total = total + 4

            else:
                if (self.pao2 >= 61 and self.pao2 < 71):
                    total = total + 1

                elif (self.pao2 >= 55 and self.pao2 < 61):
                    total = total + 3

                elif (self.pao2 < 55):
                    total = total + 4

        # Arterial pH
        if (self.ph):
            if (self.ph >= 7.5 and self.ph < 7.6):
                    total = total + 1
            elif (self.ph >= 7.25 and self.ph < 7.33):
                    total = total + 2
            elif ((self.ph >= 7.15 and self.ph < 7.25) or
                (self.ph >= 7.6 and self.ph < 7.7)):
                    total = total + 3
            elif (self.ph >= 7.7 or self.ph < 7.15):
                total = total + 4

        # Serum Sodium
        if (self.serum_sodium):
            if (self.serum_sodium >= 150 and self.serum_sodium < 155):
                    total = total + 1
            elif ((self.serum_sodium >= 155 and self.serum_sodium < 160) or
                (self.serum_sodium >= 120 and self.serum_sodium < 130)):
                    total = total + 2
            elif ((self.serum_sodium >= 160 and self.serum_sodium < 180) or
                (self.serum_sodium >= 111 and self.serum_sodium < 120)):
                    total = total + 3
            elif (self.serum_sodium >= 180 or self.serum_sodium < 111):
                total = total + 4

        # Serum Potassium
        if (self.serum_potassium):
            if ((self.serum_potassium >= 3 and self.serum_potassium < 3.5) or
                (self.serum_potassium >= 5.5 and self.serum_potassium < 6)):
                    total = total + 1
            elif (self.serum_potassium >= 2.5 and self.serum_potassium < 3):
                total = total + 2
            elif (self.serum_potassium >= 6 and self.serum_potassium < 7):
                total = total + 3
            elif (self.serum_potassium >= 7 or self.serum_potassium < 2.5):
                total = total + 4

        # Serum Creatinine
        if (self.serum_creatinine):
            arf_factor = 1
            if (self.arf):
            # We multiply by 2 the score if there is concomitant ARF
                arf_factor = 2
            if ((self.serum_creatinine < 0.6) or
                (self.serum_creatinine >= 1.5 and self.serum_creatinine < 2)):
                    total = total + 2 * arf_factor
            elif (self.serum_creatinine >= 2 and self.serum_creatinine < 3.5):
                total = total + 3 * arf_factor
            elif (self.serum_creatinine >= 3.5):
                total = total + 4 * arf_factor

        # Hematocrit
        if (self.hematocrit):
            if (self.hematocrit >= 46 and self.hematocrit < 50):
                total = total + 1
            elif ((self.hematocrit >= 50 and self.hematocrit < 60) or
                (self.hematocrit >= 20 and self.hematocrit < 30)):
                    total = total + 2
            elif (self.hematocrit >= 60 or self.hematocrit < 20):
                total = total + 4

        # WBC ( x 1000 )
        if (self.wbc):
            if (self.wbc >= 15 and self.wbc < 20):
                total = total + 1
            elif ((self.wbc >= 20 and self.wbc < 40) or
                (self.wbc >= 1 and self.wbc < 3)):
                    total = total + 2
            elif (self.wbc >= 40 or self.wbc < 1):
                total = total + 4

        # Immnunocompromised or severe organ failure
        if (self.chronic_condition):
            if (self.hospital_admission_type == 'me'):
                total = total + 5
            else:
                total = total + 2

        return total


class MechanicalVentilation(ModelSQL, ModelView):
    'Mechanical Ventilation History'
    __name__ = 'gnuhealth.icu.ventilation'

    def mv_duration(self, name):
        start = end = datetime.now()

        if self.mv_start:
            start = self.mv_start

        if self.mv_end:
            end = self.mv_end

        return end.date() - start.date()

    name = fields.Many2One('gnuhealth.inpatient.icu', 'Patient ICU Admission',
        required=True)

    ventilation = fields.Selection([
        (None, ''),
        ('none', 'None - Maintains Own'),
        ('nppv', 'Non-Invasive Positive Pressure'),
        ('ett', 'ETT'),
        ('tracheostomy', 'Tracheostomy')],
        'Type', help="NPPV = Non-Invasive Positive "
            "Pressure Ventilation, BiPAP-CPAP \n"
            "ETT - Endotracheal Tube", sort=False)

    ett_size = fields.Integer('ETT Size', states={
            'invisible': Not(Equal(Eval('ventilation'), 'ett'))})

    tracheostomy_size = fields.Integer('Tracheostomy size', states={
            'invisible': Not(Equal(Eval('ventilation'), 'tracheostomy'))})

    mv_start = fields.DateTime('From', help="Start of Mechanical Ventilation",
        required=True)
    mv_end = fields.DateTime('To', help="End of Mechanical Ventilation",
        states={
            'invisible': Bool(Eval('current_mv')),
            'required': Not(Bool(Eval('current_mv'))),
            },
        depends=['current_mv'])
    mv_period = fields.Function(fields.TimeDelta('Duration'), 'mv_duration')
    current_mv = fields.Boolean('Current')
    remarks = fields.Char('Remarks')

    @classmethod
    def validate(cls, inpatients):
        super(MechanicalVentilation, cls).validate(inpatients)
        for inpatient in inpatients:
            inpatient.check_patient_current_mv()

    def check_patient_current_mv(self):
        # Check for only one current mechanical ventilation on patient
        cursor = Transaction().connection.cursor()
        cursor.execute("SELECT count(name) "
            "FROM " + self._table + "  \
            WHERE (name = %s AND current_mv)",
            (str(self.name.id),))
        if cursor.fetchone()[0] > 1:
            raise PatientAlreadyOnMV(
                gettext('health_icu.msg_patient_already_on_mv'))

    @staticmethod
    def default_current_mv():
        return True


class ChestDrainageAssessment(ModelSQL, ModelView):
    'Chest Drainage Asessment'
    __name__ = 'gnuhealth.icu.chest_drainage'

    name = fields.Many2One('gnuhealth.patient.rounding', 'Rounding',
        required=True)
    location = fields.Selection([
        (None, ''),
        ('rl', 'Right Pleura'),
        ('ll', 'Left Pleura'),
        ('mediastinum', 'Mediastinum')],
        'Location', sort=False)
    fluid_aspect = fields.Selection([
        (None, ''),
        ('serous', 'Serous'),
        ('bloody', 'Bloody'),
        ('chylous', 'Chylous'),
        ('purulent', 'Purulent')],
        'Aspect', sort=False)
    suction = fields.Boolean('Suction')
    suction_pressure = fields.Integer('cm H2O', states={
            'invisible': Not(Bool(Eval('suction'))),
            'required': Bool(Eval('suction')),
            },
        depends=['suction'])
    oscillation = fields.Boolean('Oscillation')
    air_leak = fields.Boolean('Air Leak')
    fluid_volume = fields.Integer('Volume')
    remarks = fields.Char('Remarks')


class PatientRounding(ModelSQL, ModelView):
    # Nursing Rounding for ICU
    # Inherit and append to the existing model the new functionality for ICU

    __name__ = 'gnuhealth.patient.rounding'

    STATES = {'readonly': Eval('state') == 'done'}

    icu_patient = fields.Boolean('ICU', help='Check this box if this is'
    'an Intensive Care Unit rounding.', states=STATES)
    # Neurological assesment
    gcs = fields.Many2One('gnuhealth.icu.glasgow', 'GCS',
        domain=[('name', '=', Eval('name'))], depends=['name'],states=STATES)

    pupil_dilation = fields.Selection([
        ('normal', 'Normal'),
        ('miosis', 'Miosis'),
        ('mydriasis', 'Mydriasis')],
        'Pupil Dilation', sort=False, states=STATES)

    left_pupil = fields.Integer('L', help="size in mm of left pupil",
        states=STATES)
    right_pupil = fields.Integer('R', help="size in mm of right pupil",
        states=STATES)

    anisocoria = fields.Boolean('Anisocoria', states=STATES)

    pupillary_reactivity = fields.Selection([
        (None, ''),
        ('brisk', 'Brisk'),
        ('sluggish', 'Sluggish'),
        ('nonreactive', 'Nonreactive')],
        'Pupillary Reactivity', sort=False, states=STATES)

    pupil_consensual_resp = fields.Boolean('Consensual Response',
        help="Pupillary Consensual Response", states=STATES)

    # Respiratory assesment
    # Mechanical ventilation information is on the patient ICU general info

    respiration_type = fields.Selection([
        (None, ''),
        ('regular', 'Regular'),
        ('deep', 'Deep'),
        ('shallow', 'Shallow'),
        ('labored', 'Labored'),
        ('intercostal', 'Intercostal')],
        'Respiration', sort=False, states=STATES)

    oxygen_mask = fields.Boolean('Oxygen Mask', states=STATES)
    fio2 = fields.Integer('FiO2', states=STATES)

    peep = fields.Boolean('PEEP', states=STATES)

    peep_pressure = fields.Integer('cm H2O', help="Pressure", states={
            'invisible': Not(Bool(Eval('peep'))),
            'required': Bool(Eval('peep')),
            'readonly': Eval('state') == 'done',
            },
        depends=['peep'])

    sce = fields.Boolean('SCE', help="Subcutaneous Emphysema",states=STATES)
    lips_lesion = fields.Boolean('Lips lesion',states=STATES)
    oral_mucosa_lesion = fields.Boolean('Oral mucosa lesion', states=STATES)

    # Chest expansion characteristics
    chest_expansion = fields.Selection([
        (None, ''),
        ('symmetric', 'Symmetrical'),
        ('asymmetric', 'Asymmetrical')],
        'Expansion', sort=False, states=STATES)
    paradoxical_expansion = fields.Boolean('Paradoxical',
        help="Paradoxical Chest Expansion", states=STATES)
    tracheal_tug = fields.Boolean('Tracheal Tug', states=STATES)

    # Trachea position
    trachea_alignment = fields.Selection([
        (None, ''),
        ('midline', 'Midline'),
        ('right', 'Deviated right'),
        ('left', 'Deviated left')],
        'Tracheal alignment', sort=False, states=STATES)

    # Chest Drainages
    chest_drainages = fields.One2Many('gnuhealth.icu.chest_drainage',
        'name', "Drainages", states=STATES)

    # Chest X-Ray
    xray = fields.Binary('Xray', states=STATES)

    # Cardiovascular assessment

    ecg = fields.Many2One('gnuhealth.patient.ecg', 'Inpatient ECG',
        domain=[('inpatient_registration_code', '=', Eval('name'))],
        depends=['name'],states=STATES)

    venous_access = fields.Selection([
        (None, ''),
        ('none', 'None'),
        ('central', 'Central catheter'),
        ('peripheral', 'Peripheral')],
        'Venous Access', sort=False, states=STATES)

    swan_ganz = fields.Boolean('Swan Ganz',
        help="Pulmonary Artery Catheterization - PAC -", states=STATES)

    arterial_access = fields.Boolean('Arterial Access', states=STATES)

    dialysis = fields.Boolean('Dialysis', states=STATES)

    edema = fields.Selection([
        (None, ''),
        ('none', 'None'),
        ('peripheral', 'Peripheral'),
        ('anasarca', 'Anasarca')],
        'Edema', sort=False, states=STATES)

    # Blood & Skin
    bacteremia = fields.Boolean('Bacteremia', states=STATES)
    ssi = fields.Boolean('Surgery Site Infection', states=STATES)
    wound_dehiscence = fields.Boolean('Wound Dehiscence', states=STATES)
    cellulitis = fields.Boolean('Cellulitis', states=STATES)
    necrotizing_fasciitis = fields.Boolean('Necrotizing fasciitis',
        states=STATES)

    # Abdomen & Digestive

    vomiting = fields.Selection([
        (None, ''),
        ('none', 'None'),
        ('vomiting', 'Vomiting'),
        ('hematemesis', 'Hematemesis')],
        'Vomiting', sort=False, states=STATES)

    bowel_sounds = fields.Selection([
        (None, ''),
        ('normal', 'Normal'),
        ('increased', 'Increased'),
        ('decreased', 'Decreased'),
        ('absent', 'Absent')],
        'Bowel Sounds', sort=False, states=STATES)

    stools = fields.Selection([
        (None, ''),
        ('normal', 'Normal'),
        ('constipation', 'Constipation'),
        ('diarrhea', 'Diarrhea'),
        ('melena', 'Melena')],
        'Stools', sort=False, states=STATES)

    peritonitis = fields.Boolean('Peritonitis signs', states=STATES)

    @fields.depends('left_pupil', 'right_pupil')
    def on_change_with_anisocoria(self):
        if (self.left_pupil == self.right_pupil):
            return False
        else:
            return True

    @staticmethod
    def default_pupil_dilation():
        return 'normal'
