# Encapsulation: Control Over Your Data
# private attributes, getters, setters, validation inside the object
# redhorndev.com

# ─────────────────────────────────────────────
# The problem — no control
# ─────────────────────────────────────────────

class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age  = age

lassie = Animal("Lassie", 4)
lassie.age    = -5          # accepted — no validation
lassie.status = "missing"   # accepted — doesn't even exist as an attribute
print(lassie.age)           # -5 — wrong, but Python doesn't care

# ─────────────────────────────────────────────
# Single underscore — private by convention
# ─────────────────────────────────────────────

class Animal:
    def __init__(self, name, age):
        self.name = name
        self._age = age     # convention: treat as private, don't access directly

lassie = Animal("Lassie", 4)
print(lassie._age)          # 4 — still accessible, but signals "don't touch"

# ─────────────────────────────────────────────
# Double underscore — name mangling
# ─────────────────────────────────────────────

class Animal:
    def __init__(self, name, age):
        self.name  = name
        self.__age = age    # name-mangled by Python

lassie = Animal("Lassie", 4)
# print(lassie.__age)       # AttributeError
print(lassie._Animal__age)  # 4 — still accessible, but clearly not intended

# ─────────────────────────────────────────────
# Getters and setters — validation built in
# ─────────────────────────────────────────────

class Animal:
    VALID_STATUSES = ["available", "adopted"]
    VALID_HEALTH   = ["healthy", "ill"]

    def __init__(self, name, species, age):
        self.name    = name
        self.species = species
        self._age    = age
        self._status = "available"
        self._health = "healthy"

    # ── age ──────────────────────────────────
    def get_age(self):
        return self._age

    def set_age(self, age):
        if isinstance(age, int) and age > 0:
            self._age = age
        else:
            print("Invalid age. Must be a positive integer.")

    # ── status ───────────────────────────────
    def get_status(self):
        return self._status

    def set_status(self, status):
        if status in self.VALID_STATUSES:
            self._status = status
        else:
            print(f"Invalid status. Choose from: {self.VALID_STATUSES}")

    # ── health ───────────────────────────────
    def get_health(self):
        return self._health

    def set_health(self, health):
        if health in self.VALID_HEALTH:
            self._health = health
        else:
            print(f"Invalid health. Choose from: {self.VALID_HEALTH}")

    def describe(self):
        print(f"{self.name} | {self.species} | age: {self._age} | "
              f"health: {self._health} | status: {self._status}")

# ─────────────────────────────────────────────
# Using getters and setters
# ─────────────────────────────────────────────

lassie = Animal("Lassie", "dog", 4)
lassie.describe()
# Lassie | dog | age: 4 | health: healthy | status: available

lassie.set_age(5)
print(lassie.get_age())         # 5

lassie.set_age(-3)              # Invalid age. Must be a positive integer.
print(lassie.get_age())         # 5 — unchanged

lassie.set_status("adopted")
print(lassie.get_status())      # adopted

lassie.set_status("missing")    # Invalid status. Choose from: ['available', 'adopted']
print(lassie.get_status())      # adopted — unchanged

lassie.set_health("ill")
lassie.describe()
# Lassie | dog | age: 5 | health: ill | status: adopted

# ─────────────────────────────────────────────
# Quick reference
# ─────────────────────────────────────────────

# _attr          — private by convention (single underscore)
# __attr         — name-mangled (double underscore)
# get_attr()     — getter: returns the private attribute
# set_attr(val)  — setter: validates then sets the attribute
#
# Python has no true private attributes
# convention is the protection — single underscore = "please don't touch"
# getters/setters enforce rules regardless of where the object is used
