# Reverse engineered from amazfit's app (also known as Mi Fit)
from body_scales import bodyScales
class bodyScore:

    def __init__(self, age, sex, height, weight, bmi, bodyfat, muscle, water, visceral_fat, bone, basal_metabolism, protein):
        self.age = age
        self.sex = sex
        self.height = height
        self.weight = weight
        self.bmi = bmi
        self.bodyfat = bodyfat
        self.muscle = muscle
        self.water = water
        self.visceral_fat = visceral_fat
        self.bone = bone
        self.basal_metabolism = basal_metabolism
        self.protein = protein
        self.scales = bodyScales(age, height, sex, weight)

    def getBodyScore(self):
        score = 100
        score -= self.getBmiDeductScore()
        score -= self.getBodyFatDeductScore()
        score -= self.getMuscleDeductScore()
        score -= self.getWaterDeductScore()
        score -= self.getVisceralFatDeductScore()
        score -= self.getBoneDeductScore()
        score -= self.getBasalMetabolismDeductScore()
        if self.protein:
            score -= self.getProteinDeductScore()
        return score

    def getMalus(self, data, min_data, max_data, max_malus, min_malus):
        result = ((data - max_data) / (min_data - max_data)) * float(max_malus - min_malus)
        if result >= 0.0:
            return result
        return 0.0

    def getBmiDeductScore(self):
        if not self.height >= 90:
            # "BMI is not reasonable
            return 0.0

        bmi_low = 15.0
        bmi_verylow = 14.0
        bmi_normal = 18.5
        bmi_overweight = 28.0
        bmi_obese = 32.0
        fat_scale = self.scales.getFatPercentageScale()

        # Perfect range (bmi >= 18.5 and bodyfat not high for adults, bmi >= 15.0 for kids
        if self.bmi >= 18.5 and self.age >= 18 and self.bodyfat < fat_scale[2]:
            return 0.0
        elif self.bmi >= bmi_verylow and self.age < 18 and self.bodyfat < fat_scale[2]:
            return 0.0

        # Extremely skinny (bmi < 14)
        elif self.bmi <= bmi_verylow:
            return 30.0
        # Too skinny (bmi between 14 and 15)
        elif self.bmi > bmi_verylow and self.bmi < bmi_low:
            return self.getMalus(self.bmi, bmi_verylow, bmi_low, 30, 15) + 15.0
        # Skinny (for adults, between 15 and 18.5)
        elif self.bmi >= bmi_low and self.bmi < bmi_normal and self.age >= 18:
            return self.getMalus(self.bmi, 15.0, 18.5, 15, 5) + 5.0

        # Normal or high bmi but too much bodyfat
        elif ((self.bmi >= bmi_low and self.age < 18) or (self.bmi >= bmi_normal and self.age >= 18)) and self.bodyfat >= fat_scale[2]:
            # Obese
            if self.bmi >= bmi_obese:
                return 10.0
            # Overweight
            if self.bmi > bmi_overweight:
                return self.getMalus(self.bmi, 28.0, 25.0, 5, 10) + 5.0
            else:
                return 0.0

    def getBodyFatDeductScore(self):
        scale = self.scales.getFatPercentageScale()

        if self.sex == 'male':
            best = scale[2] - 3.0
        elif self.sex == 'female':
            best = scale[2] - 2.0

        # Slighly low in fat or low part or normal fat
        if self.bodyfat >= scale[0] and self.bodyfat < best:
            return 0.0
        elif self.bodyfat >= scale[3]:
            return 20.0
        else:
            # Sightly high body fat
            if self.bodyfat < scale[3]:
                return self.getMalus(self.bodyfat, scale[3], scale[2], 20, 10) + 10.0

            # High part of normal fat
            elif self.bodyfat <= normal[2]:
                return self.getMalus(self.bodyfat, scale[2], best, 3, 9) + 3.0

            # Very low in fat
            elif self.bodyfat < normal[0]:
                return self.getMalus(self.bodyfat, 1.0, scale[0], 3, 10) + 3.0


    def getMuscleDeductScore(self):
        scale = self.scales.getMuscleMassScale()

        # For some reason, there's code to return self.calculate(muscle, normal[0], normal[0]+2.0, 3, 5) + 3.0
        # if your muscle is between normal[0] and normal[0] + 2.0, but it's overwritten with 0.0 before return
        if self.muscle >= scale[0]:
            return 0.0
        elif self.muscle < (scale[0] - 5.0):
            return 10.0
        else:
            return self.getMalus(self.muscle, scale[0] - 5.0, scale[0], 10, 5) + 5.0

    # No malus = normal or good; maximum malus (10.0) = less than normal-5.0;
    # malus = between 5 and 10, on your water being between normal-5.0 and normal
    def getWaterDeductScore(self):
        scale = self.scales.getWaterPercentageScale()

        if self.water >= scale[0]:
            return 0.0
        elif self.water <= (scale[0] - 5.0):
            return 10.0
        else:
            return self.getMalus(self.water, scale[0] - 5.0, scale[0], 10, 5) + 5.0

    # No malus = normal; maximum malus (15.0) = very high; malus = between 10 and 15
    # with your visceral fat in your high range
    def getVisceralFatDeductScore(self):
        scale = self.scales.getVisceralFatScale()

        if self.visceral_fat < scale[0]:
            # For some reason, the original app would try to
            # return 3.0 if vfat == 8 and 5.0 if vfat == 9
            # but i's overwritten with 0.0 anyway before return
            return 0.0
        elif self.visceral_fat >= scale[1]:
            return 15.0
        else:
            return self.getMalus(self.visceral_fat, scale[1], scale[0], 15, 10) + 10.0

    def getBoneDeductScore(self):
        scale = self.scales.getBoneMassScale()

        if self.bone >= scale[0]:
            return 0.0
        elif self.bone <= (scale[0] - 0.3):
            return 10.0
        else:
            return self.getMalus(self.bone, scale[0] - 0.3, scale[0], 10, 5) + 5.0

    def getBasalMetabolismDeductScore(self):
        # Get normal BMR
        normal = self.scales.getBMRScale()[0]

        if self.basal_metabolism >= normal:
            return 0.0
        elif self.basal_metabolism <= (normal - 300):
            return 6.0
        else:
            # It's really + 5.0 in the app, but it's probably a mistake, should be 3.0
            return self.getMalus(self.basal_metabolism, normal - 300, normal, 6, 3) + 5.0


    # Get protein percentage malus
    def getProteinDeductScore(self):
        # low: 10,16; normal: 16,17
        # Check limits
        if self.protein > 17.0:
            return 0.0
        elif self.protein < 10.0:
            return 10.0
        else:
            # Return values for low proteins or normal proteins
            if self.protein <= 16.0:
                return self.getMalus(self.protein, 10.0, 16.0, 10, 5) + 5.0
            elif self.protein <= 17.0:
                return self.getMalus(self.protein, 16.0, 17.0, 5, 3) + 3.0