#!/usr/bin/env python3
"""
windreport.py - the single owner of the gust records.

Reads the station gust sensor and keeps two records, using the same
confirmation rule the page used to apply on its own:

    gustspeed.txt  (station gust sensor, read-only)
        -> gusttoday.txt / gusttime.txt   highest confirmed gust today + time
                                          (reset at midnight)
        -> maxgustspeed.txt / maxgustdate.txt   all-time record + date

weather.html only displays these files, so every device shows the same
figures whether or not it was open when the gust happened.

A new high only counts if a second, similar reading follows shortly after,
so one corrupted or half-written reading can never become a record.

It does not touch gustspeed.txt, windspeed, averagespeed.txt or any
beaufort*.txt file.
"""

import os
import time
from datetime import datetime

# ---------------------------------------------------------------- settings
GUST_FILE = "gustspeed.txt"          # station gust sensor - never written here
TODAY_SPEED_FILE = "gusttoday.txt"
TODAY_TIME_FILE = "gusttime.txt"
MAX_GUST_SPEED_FILE = "maxgustspeed.txt"
MAX_GUST_DATE_FILE = "maxgustdate.txt"
DATE_FORMAT = "%d %b %Y"             # e.g. "03 Oct 2026" - shown on the page as-is

POLL_SECONDS = 2                     # same as the page
STATUS_SECONDS = 60                  # how often to print a status line

# Confirmation rule - copied from recordGust() in the original weather.html:
CONFIRM_TOLERANCE = 2                # second reading within this many mph (< 2)
CONFIRM_WINDOW = 15                  # ...and within this many seconds


# ----------------------------------------------------------------- helpers
def read_number(name):
    """Read a number from a file, or return None if missing/invalid."""
    try:
        with open(name) as f:
            return float(f.read().strip().split()[0])
    except (OSError, ValueError, IndexError):
        return None


def read_text(name, default=""):
    try:
        with open(name) as f:
            return f.read().strip()
    except OSError:
        return default


def write_file(name, value):
    # Write to a temp file then swap it in, so the page never reads a
    # half-written file.
    try:
        tmp = name + ".tmp"
        with open(tmp, "w") as f:
            f.write(str(value) + "\n")
        os.replace(tmp, name)
    except OSError as e:
        print(f"Could not write {name}: {e}")


def file_is_from_today(name):
    try:
        return datetime.fromtimestamp(os.path.getmtime(name)).date() == datetime.now().date()
    except OSError:
        return False


def check_gust(reading, record, pending, now):
    """
    Same logic as recordGust() on the page.

    reading  - latest gustspeed.txt value
    record   - the value to beat (today's high, or the all-time record)
    pending  - (speed, time) of an unconfirmed candidate, or None
    now      - monotonic seconds

    Returns (new_record_or_None, pending).
    """
    if reading > record:
        if (pending
                and pending[0] <= reading
                and abs(pending[0] - reading) < CONFIRM_TOLERANCE
                and now - pending[1] < CONFIRM_WINDOW):
            return reading, None          # confirmed: new record
        return None, (reading, now)       # candidate - wait for a second reading
    return None, None                     # not above the record: clear candidate


def reset_today():
    write_file(TODAY_SPEED_FILE, 0)
    write_file(TODAY_TIME_FILE, "")


# -------------------------------------------------------------------- main
def main():
    # All-time record starts at 0 if there is none
    if read_number(MAX_GUST_SPEED_FILE) is None:
        write_file(MAX_GUST_SPEED_FILE, 0)

    # Today's high: resume if written today, otherwise start fresh
    if read_number(TODAY_SPEED_FILE) is None or not file_is_from_today(TODAY_SPEED_FILE):
        reset_today()

    current_day = datetime.now().date()
    pending_today = None
    pending_max = None
    last_status = 0.0

    print(f"windreport started (PID {os.getpid()}) - checking {GUST_FILE} "
          f"every {POLL_SECONDS}s")
    print(f"Working folder (all files are read/written here): {os.getcwd()}")

    while True:
        now = time.monotonic()
        wall = datetime.now()
        stamp = f"{wall:%Y-%m-%d %H:%M:%S}"

        # Midnight: reset today's high (the all-time record is never reset)
        if wall.date() != current_day:
            current_day = wall.date()
            pending_today = None
            print(f"{stamp}  NEW DAY - highest gust today was "
                  f"{read_number(TODAY_SPEED_FILE) or 0:g} mph, reset to 0")
            reset_today()

        reading = read_number(GUST_FILE)
        today_high = read_number(TODAY_SPEED_FILE) or 0.0
        max_gust = read_number(MAX_GUST_SPEED_FILE) or 0.0

        if reading is not None:
            # ---- highest gust today
            before = pending_today
            rec, pending_today = check_gust(reading, today_high, pending_today, now)
            if rec is not None:
                today_high = rec
                write_file(TODAY_SPEED_FILE, rec)
                write_file(TODAY_TIME_FILE, wall.strftime("%H:%M:%S"))
                print(f"{stamp}  NEW HIGH TODAY: {rec:g} mph (confirmed)")
            elif pending_today and pending_today is not before:
                print(f"{stamp}  Possible new high today {reading:g} mph "
                      f"- waiting for a confirming reading")

            # ---- all-time record
            before = pending_max
            rec, pending_max = check_gust(reading, max_gust, pending_max, now)
            if rec is not None:
                max_gust = rec
                write_file(MAX_GUST_SPEED_FILE, rec)
                write_file(MAX_GUST_DATE_FILE, wall.strftime(DATE_FORMAT))
                print(f"{stamp}  NEW MAX GUST: {rec:g} mph (confirmed)")

        if now - last_status >= STATUS_SECONDS:
            last_status = now
            shown = "missing" if reading is None else f"{reading:g} mph"
            print(f"{stamp}  {GUST_FILE}: {shown}  |  "
                  f"Highest today: {today_high:g} mph at "
                  f"{read_text(TODAY_TIME_FILE) or '-'}  |  "
                  f"Max gust: {max_gust:g} mph on {read_text(MAX_GUST_DATE_FILE, 'n/a')}")

        time.sleep(POLL_SECONDS)


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\nwindreport stopped.")
