"""Settings (weights, tire size, ...) and the sensor list, saved as JSON files on the Pi."""
import json
import os
import threading

DATA_DIR = os.environ.get("DYNO_DATA", os.path.expanduser("~/minibike-dyno-data"))
RUNS_DIR = os.path.join(DATA_DIR, "runs")
SETTINGS_FILE = os.path.join(DATA_DIR, "settings.json")
SENSORS_FILE = os.path.join(DATA_DIR, "sensors.json")

# Everything the horsepower math needs. Units are the ones shown in the app.
DEFAULT_SETTINGS = {
    "bike_weight_lb": 120.0,      # mini bike with fuel
    "rider_weight_lb": 150.0,     # rider with helmet and gear
    "weights_set": False,         # the app nags until the real weights are entered
    "tire_diameter_in": 13.0,     # rear tire, outside diameter (for wheel-speed sensors)
    "drivetrain": "clutch",       # "clutch" (chain + centrifugal clutch) or "torque_converter"
    "efficiency_clutch": 0.85,    # share of engine power that reaches the tire
    "efficiency_torque_converter": 0.70,
    "cda_m2": 0.55,               # air drag area of bike + upright rider
    "crr": 0.020,                 # tire rolling resistance on pavement
    "rotating_pct": 5.0,          # extra "weight" from spinning wheels, clutch, chain
    "slope_pct": 0.0,             # driveway slope in the riding direction (+ = uphill)
    "smoothing": 7,               # GPS samples used to smooth speed (odd number, 5-15)
    "start_mph": 3.0,             # a run starts when you go faster than this
    "stop_mph": 2.0,              # and ends after you slow below this
    "min_top_mph": 5.0,           # shorter/slower "runs" are thrown away
    "speed_source": "auto",       # "auto" (wheel sensor if there is one), "gps" or "wheel"
}

_lock = threading.Lock()


def _ensure_dirs():
    os.makedirs(RUNS_DIR, exist_ok=True)


def _read_json(path, fallback):
    try:
        with open(path) as f:
            return json.load(f)
    except (OSError, ValueError):
        return fallback


def _write_json(path, data):
    _ensure_dirs()
    tmp = path + ".tmp"
    with open(tmp, "w") as f:
        json.dump(data, f, indent=2)
    os.replace(tmp, path)


def load_settings():
    with _lock:
        saved = _read_json(SETTINGS_FILE, {})
    settings = dict(DEFAULT_SETTINGS)
    settings.update({k: v for k, v in saved.items() if k in DEFAULT_SETTINGS})
    return settings


def save_settings(changes):
    """Merge changes (from the app) into the saved settings, keeping types sane."""
    settings = load_settings()
    for key, value in changes.items():
        if key not in DEFAULT_SETTINGS:
            continue
        default = DEFAULT_SETTINGS[key]
        try:
            if isinstance(default, bool):
                value = bool(value)
            elif isinstance(default, int):
                value = int(value)
            elif isinstance(default, float):
                value = float(value)
            else:
                value = str(value)
        except (TypeError, ValueError):
            continue
        settings[key] = value
    if "bike_weight_lb" in changes or "rider_weight_lb" in changes:
        settings["weights_set"] = True
    if settings["smoothing"] % 2 == 0:
        settings["smoothing"] += 1
    settings["smoothing"] = max(3, min(21, settings["smoothing"]))
    with _lock:
        _write_json(SETTINGS_FILE, settings)
    return settings


# The GPS is the only sensor to start with. New sensors are added from the app's
# Sensors page; each entry is {"id", "type", "name", "enabled", plus its own options}.
DEFAULT_SENSORS = [
    {"id": "gps", "type": "ublox_gps", "name": "GPS (HGLRC M100)", "enabled": True,
     "port": "/dev/serial0", "rate_hz": 10},
    {"id": "practice", "type": "sim_gps", "name": "Practice GPS (fake rides)", "enabled": False,
     "engine_hp": 6.5, "every_s": 40},
]


def load_sensors():
    with _lock:
        sensors = _read_json(SENSORS_FILE, None)
    if not isinstance(sensors, list):
        sensors = [dict(s) for s in DEFAULT_SENSORS]
    return sensors


def save_sensors(sensors):
    with _lock:
        _write_json(SENSORS_FILE, sensors)
