from math import floor
from .body_scales import bodyScales


class bodyMetrics:
    def __init__(self, weight, height, age, gender, impedance):
        self.weight = weight
        self.height = height
        self.age = age
        self.gender = gender
        self.impedance = impedance
        self.scales = bodyScales(age, height, gender, weight)

        # Check for potential out of boundaries
        if self.height > 220:
            raise Exception("Height is too high (limit: >220cm)")
        elif weight < 10 or weight > 200:
            raise Exception(
                "Weight is either too low or too high (limits: <10kg and >200kg)"
            )
        elif age > 99:
            raise Exception("Age is too high (limit >99 years)")
        elif impedance > 3000:
            raise Exception("Impedance is too high (limit >3000ohm)")

    # Set the value to a boundary if it overflows
    def checkValueOverflow(self, value, minimum, maximum):
        if value < minimum:
            return minimum
        elif value > maximum:
            return maximum
        else:
            return value

    # Get LBM coefficient (with impedance)
    def getLBMCoefficient(self):
        lbm = (self.height * 9.058 / 100) * (self.height / 100)
        lbm += self.weight * 0.32 + 12.226
        lbm -= self.impedance * 0.0068
        lbm -= self.age * 0.0542
        return lbm

    # Get BMR
    def getBMR(self):
        if self.gender == "female":
            bmr = 864.6 + self.weight * 10.2036
            bmr -= self.height * 0.39336
            bmr -= self.age * 6.204
        else:
            bmr = 877.8 + self.weight * 14.916
            bmr -= self.height * 0.726
            bmr -= self.age * 8.976

        # Capping
        if self.gender == "female" and bmr > 2996:
            bmr = 5000
        elif self.gender == "male" and bmr > 2322:
            bmr = 5000
        return self.checkValueOverflow(bmr, 500, 10000)

    # Get fat percentage
    def getFatPercentage(self):
        # Set a constant to remove from LBM
        if self.gender == "female" and self.age <= 49:
            const = 9.25
        elif self.gender == "female" and self.age > 49:
            const = 7.25
        else:
            const = 0.8

        # Calculate body fat percentage
        LBM = self.getLBMCoefficient()

        if self.gender == "male" and self.weight < 61:
            coefficient = 0.98
        elif self.gender == "female" and self.weight > 60:
            coefficient = 0.96
            if self.height > 160:
                coefficient *= 1.03
        elif self.gender == "female" and self.weight < 50:
            coefficient = 1.02
            if self.height > 160:
                coefficient *= 1.03
        else:
            coefficient = 1.0
        fatPercentage = (1.0 - (((LBM - const) * coefficient) / self.weight)) * 100

        # Capping body fat percentage
        if fatPercentage > 63:
            fatPercentage = 75
        return self.checkValueOverflow(fatPercentage, 5, 75)

    # Get water percentage
    def getWaterPercentage(self):
        waterPercentage = (100 - self.getFatPercentage()) * 0.7

        if waterPercentage <= 50:
            coefficient = 1.02
        else:
            coefficient = 0.98

        # Capping water percentage
        if waterPercentage * coefficient >= 65:
            waterPercentage = 75
        return self.checkValueOverflow(waterPercentage * coefficient, 35, 75)

    # Get bone mass
    def getBoneMass(self):
        if self.gender == "female":
            base = 0.245691014
        else:
            base = 0.18016894

        boneMass = (base - (self.getLBMCoefficient() * 0.05158)) * -1

        if boneMass > 2.2:
            boneMass += 0.1
        else:
            boneMass -= 0.1

        # Capping boneMass
        if self.gender == "female" and boneMass > 5.1:
            boneMass = 8
        elif self.gender == "male" and boneMass > 5.2:
            boneMass = 8
        return self.checkValueOverflow(boneMass, 0.5, 8)

    # Get muscle mass
    def getMuscleMass(self):
        muscleMass = (
            self.weight
            - ((self.getFatPercentage() * 0.01) * self.weight)
            - self.getBoneMass()
        )

        # Capping muscle mass
        if self.gender == "female" and muscleMass >= 84:
            muscleMass = 120
        elif self.gender == "male" and muscleMass >= 93.5:
            muscleMass = 120

        return self.checkValueOverflow(muscleMass, 10, 120)

    # Get Visceral Fat
    def getVisceralFat(self):
        if self.gender == "female":
            if self.weight > (13 - (self.height * 0.5)) * -1:
                subsubcalc = (
                    (self.height * 1.45) + (self.height * 0.1158) * self.height
                ) - 120
                subcalc = self.weight * 500 / subsubcalc
                vfal = (subcalc - 6) + (self.age * 0.07)
            else:
                subcalc = 0.691 + (self.height * -0.0024) + (self.height * -0.0024)
                vfal = (
                    (((self.height * 0.027) - (subcalc * self.weight)) * -1)
                    + (self.age * 0.07)
                    - self.age
                )
        else:
            if self.height < self.weight * 1.6:
                subcalc = (
                    (self.height * 0.4) - (self.height * (self.height * 0.0826))
                ) * -1
                vfal = ((self.weight * 305) / (subcalc + 48)) - 2.9 + (self.age * 0.15)
            else:
                subcalc = 0.765 + self.height * -0.0015
                vfal = (
                    (((self.height * 0.143) - (self.weight * subcalc)) * -1)
                    + (self.age * 0.15)
                    - 5.0
                )

        return self.checkValueOverflow(vfal, 1, 50)

    # Get BMI
    def getBMI(self):
        return self.checkValueOverflow(
            self.weight / ((self.height / 100) * (self.height / 100)), 10, 90
        )

    # Get ideal weight (just doing a reverse BMI, should be something better)
    def getIdealWeight(self, orig=True):
        # Uses mi fit algorithm (or holtek's one)
        if orig and self.gender == "female":
            return (self.height - 70) * 0.6
        elif orig and self.gender == "male":
            return (self.height - 80) * 0.7
        else:
            return self.checkValueOverflow(
                (22 * self.height) * self.height / 10000, 5.5, 198
            )

    # Get fat mass to ideal (guessing mi fit formula)
    def getFatMassToIdeal(self):
        mass = (self.weight * (self.getFatPercentage() / 100)) - (
            self.weight * (self.scales.getFatPercentageScale()[2] / 100)
        )
        if mass < 0:
            return {"type": "to_gain", "mass": mass}
        else:
            return {"type": "to_lose", "mass": mass * -1}

    # Get protetin percentage (warn: guessed formula)
    def getProteinPercentage(self, orig=True):
        # Use original algorithm from mi fit (or legacy guess one)
        if orig:
            proteinPercentage = (self.getMuscleMass() / self.weight) * 100
            proteinPercentage -= self.getWaterPercentage()
        else:
            proteinPercentage = 100 - (floor(self.getFatPercentage() * 100) / 100)
            proteinPercentage -= floor(self.getWaterPercentage() * 100) / 100
            proteinPercentage -= (
                floor((self.getBoneMass() / self.weight * 100) * 100) / 100
            )

        return self.checkValueOverflow(proteinPercentage, 5, 32)

    # Get body type (out of nine possible)
    def getBodyType(self):
        if self.getFatPercentage() > self.scales.getFatPercentageScale()[2]:
            factor = 0
        elif self.getFatPercentage() < self.scales.getFatPercentageScale()[1]:
            factor = 2
        else:
            factor = 1

        if self.getMuscleMass() > self.scales.getMuscleMassScale()[1]:
            return 2 + (factor * 3)
        elif self.getMuscleMass() < self.scales.getMuscleMassScale()[0]:
            return factor * 3
        else:
            return 1 + (factor * 3)

    def getBmiLabel(self):
        bmi = self.getBMI()
        if bmi < 18.5:
            return "Underweight"
        elif bmi >= 18.5 and bmi < 25:
            return "Normal or Healthy Weight"
        elif bmi >= 25 and bmi < 27:
            return "Slight overweight"
        elif bmi >= 27 and bmi < 30:
            return "Overweight"
        elif bmi >= 30 and bmi < 35:
            return "Moderate obesity"
        elif bmi >= 35 and bmi < 40:
            return "Severe obesity"
        elif bmi >= 40:
            return "Massive obesity"

    # Get Metabolic Age
    def getMetabolicAge(self):
        if self.gender == "female":
            metabolicAge = (
                (self.height * -1.1165)
                + (self.weight * 1.5784)
                + (self.age * 0.4615)
                + (self.impedance * 0.0415)
                + 83.2548
            )
        else:
            metabolicAge = (
                (self.height * -0.7471)
                + (self.weight * 0.9161)
                + (self.age * 0.4184)
                + (self.impedance * 0.0517)
                + 54.2267
            )
        return self.checkValueOverflow(metabolicAge, 15, 80)
