<?php
// -*- coding: utf-8 -*-
/**
 * TRS to Latitude/Longitude Lookup
 * trs2latlon.php
 * Version: 1.05
 *
 * Queries a local flat-file database first; falls back to BLM PLSS GIS
 * API on cache miss and stores the result locally for future lookups.
 * Also writes the most recent result to latest.txt.
 */

ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/my_debug.log');
error_reporting(E_ALL);

// ======================================================================
// USER CONFIGURATION
// ======================================================================

$OUTPUT_FILE      = 'latest.txt';       // most-recent result; set to '' to disable
$DB_FILE          = 'trs2latlon.txt';   // local TRS database
$DEFAULT_STATE    = 'OR';
$DEFAULT_MERIDIAN = '33';               // 33 = Willamette (OR and WA)
$SHOW_STATUS      = False;               // true = show source and saved-to messages; false = hide them

// ======================================================================
// END USER CONFIGURATION
// ======================================================================

// ----------------------------------------------------------------------
// Helper: HTTP GET via curl
// ----------------------------------------------------------------------
function http_get($url) {
    $ch = curl_init();
    curl_setopt_array($ch, array(
        CURLOPT_URL            => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_TIMEOUT        => 15,
        CURLOPT_USERAGENT      => 'TRS2LatLon/1.05 (contact@example.com)',
        CURLOPT_HTTPHEADER     => array('Accept: application/json'),
    ));
    $body     = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $error    = curl_error($ch);
    curl_close($ch);
    return array('body' => $body, 'http_code' => $httpCode, 'error' => $error);
}

// ----------------------------------------------------------------------
// Local database helpers
// Format of each line:  T5S R8E Section 10, 44.3503, -120.0654
// Key used for lookup:  "T5S R8E SECTION 10" (case-insensitive)
// ----------------------------------------------------------------------
function db_lookup($db_path, $trs_label) {
    if (!file_exists($db_path)) return null;
    $key   = strtoupper($trs_label);
    $lines = file($db_path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    foreach ($lines as $line) {
        $parts = explode(',', $line, 3);
        if (count($parts) < 3) continue;
        if (strtoupper(trim($parts[0])) === $key) {
            return array('lat' => trim($parts[1]), 'lon' => trim($parts[2]));
        }
    }
    return null;
}

function db_append($db_path, $result_line) {
    file_put_contents($db_path, $result_line . "\n", FILE_APPEND | LOCK_EX);
}

// ----------------------------------------------------------------------
// HTML page renderer
// ----------------------------------------------------------------------
function show_page($title, $body_html) {
    $css = '
    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
    body {
      background: #0C0F1A;
      color: #e0e0e0;
      font-family: \'Barlow\', sans-serif;
      min-height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    .card {
      background: #141824;
      border: 1px solid #2a3050;
      border-radius: 8px;
      padding: 36px 40px 32px;
      width: 360px;
      display: flex;
      flex-direction: column;
      align-items: center;
    }
    h2 {
      color: #00E5FF;
      font-size: 17px;
      font-weight: bold;
      letter-spacing: 3px;
      text-transform: uppercase;
      text-align: center;
      margin-bottom: 28px;
    }
    .form-grid { width: 100%; }
    .row { display: flex; align-items: baseline; margin-bottom: 14px; }
    label {
      color: #aaa;
      font-size: 12px;
      letter-spacing: 1px;
      text-transform: uppercase;
      width: 150px;
      flex-shrink: 0;
    }
    input, select {
      background: #1a1e2e;
      border: none;
      border-bottom: 1px solid #00E5FF;
      color: #ffffff;
      font-family: "Courier New", Courier, monospace;
      font-size: 14px;
      padding: 4px 8px;
      width: 90px;
      outline: none;
    }
    input:focus, select:focus { border-bottom: 1px solid #ffffff; background: #222840; }
    select option { background: #1a1e2e; }
    .btn-row { margin-top: 24px; width: 100%; display: flex; justify-content: center; }
	button {
	  background: #1a1e2e;
	  color: #ffffff;
	  border: 1px solid #4a5070;
	  border-radius: 6px;
	  font-family: \'Barlow\', sans-serif;
	  font-size: 14px;
	  font-weight: 600;
	  letter-spacing: 0.5px;
	  text-transform: none;
	  padding: 8px 20px;
	  cursor: pointer;
	  width: 100%;
	}
	button:hover { background: #2a3050; border-color: #00E5FF; color: #00E5FF; }
    .result-box { text-align: center; width: 100%; }
    .result-trs { color: #00E5FF; font-size: 16px; letter-spacing: 2px; text-transform: uppercase; margin-bottom: 14px; }
    .result-coords { color: #00E5FF; font-size: 20px; font-weight: bold; margin-bottom: 6px; }
    .result-saved { color: #666; font-size: 11px; margin-bottom: 12px; }
    .source-tag {
      display: inline-block;
      font-size: 11px;
      letter-spacing: 1px;
      text-transform: uppercase;
      padding: 3px 10px;
      border-radius: 10px;
      margin-bottom: 18px;
    }
    .source-local { background: #1a2e1a; color: #44cc66; border: 1px solid #44cc66; }
    .source-api   { background: #1a1e2e; color: #aaaaff; border: 1px solid #aaaaff; }
    .divider { width: 100%; border: none; border-top: 1px solid #2a3050; margin: 16px 0; }
    .error-msg { color: #ff5555; font-size: 13px; text-align: center; margin-bottom: 20px; line-height: 1.6; }
    .back-link {
      color: #aaa; font-size: 12px; text-decoration: none;
      letter-spacing: 1px; text-transform: uppercase; display: inline-block;
    }
    .back-link:hover { color: #00E5FF; }
    .close-btn {
      background: #1a1e2e; color: #ffffff; border: 2px solid #ffffff;
      border-radius: 20px; font-family: "Courier New", Courier, monospace;
      font-size: 13px; font-weight: bold; letter-spacing: 2px;
      text-transform: uppercase; padding: 8px 28px; cursor: pointer; margin-top: 12px;
    }
	
    .new-lookup-btn {
	  display: inline-block;
	  background: #1a1e2e;
	  color: #ffffff;
	  border: 1px solid #4a5070;
	  border-radius: 6px;
	  font-family: \'Barlow\', sans-serif;
	  font-size: 14px;
	  font-weight: 600;
	  letter-spacing: 0.5px;
	  text-decoration: none;
	  padding: 8px 20px;
	  cursor: pointer;
	  width: 100%;
	  text-align: center;
	  margin-top: 8px;
	  box-sizing: border-box;
	}
	.new-lookup-btn:hover { background: #2a3050; border-color: #00E5FF; color: #00E5FF; }
    .new-lookup-btn:hover { background: #2a3050; border-color: #00E5FF; color: #00E5FF; }
    .close-btn:hover { background: #2a3050; border-color: #00E5FF; color: #00E5FF; }
    ';

    echo '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">';
	echo '<link rel="preconnect" href="https://fonts.googleapis.com">';
    echo '<link href="https://fonts.googleapis.com/css2?family=Barlow:wght@400;600&display=swap" rel="stylesheet">';
    echo '<title>' . htmlspecialchars($title) . '</title>';
    echo '<style>' . $css . '</style></head><body>';
    echo '<div class="card">' . $body_html . '</div>';
    echo '</body></html>';
}

// ======================================================================
// GET: show input form
// ======================================================================
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    $sel_or = ($DEFAULT_STATE === 'OR') ? ' selected' : '';
    $sel_wa = ($DEFAULT_STATE === 'WA') ? ' selected' : '';

    $html  = '<h2>TRS to<br>Lat / Lon</h2>';
    $html .= '<form method="post" class="form-grid">';
    $html .= '<div class="row"><label>State:</label>';
    $html .= '<select name="state">';
    $html .= '<option value="OR"' . $sel_or . '>OR</option>';
    $html .= '<option value="WA"' . $sel_wa . '>WA</option>';
    $html .= '</select></div>';
    $html .= '<div class="row"><label>Township (ex: 5S):</label><input name="township" maxlength="6" autocomplete="off"></div>';
    $html .= '<div class="row"><label>Range (ex: 8E):</label><input name="range" maxlength="6" autocomplete="off"></div>';
    $html .= '<div class="row"><label>Section (1-36):</label><input name="section" maxlength="2" autocomplete="off"></div>';
    $html .= '<div class="btn-row"><button type="submit">&#9658; Look Up</button></div>';
    $html .= '</form>';

    show_page('TRS to Lat/Lon', $html);
    exit;
}

// ======================================================================
// POST: validate inputs
// ======================================================================
$state    = strtoupper(trim(isset($_POST['state'])    ? $_POST['state']    : 'OR'));
$township = strtoupper(trim(isset($_POST['township']) ? $_POST['township'] : ''));
$range    = strtoupper(trim(isset($_POST['range'])    ? $_POST['range']    : ''));
$section  = trim(isset($_POST['section']) ? $_POST['section'] : '');

$errors = array();
if (!in_array($state, array('OR', 'WA'), true))
    $errors[] = 'State must be OR or WA.';
if ($township === '')
    $errors[] = 'Township is required (example: 5S).';
if ($range === '')
    $errors[] = 'Range is required (example: 8E).';
if ($section === '' || !ctype_digit($section) || (int)$section < 1 || (int)$section > 36)
    $errors[] = 'Section must be a number from 1 to 36.';

if (!empty($errors)) {
    $err_html = implode('<br>', array_map('htmlspecialchars', $errors));
    $html  = '<h2>TRS to<br>Lat / Lon</h2>';
    $html .= '<div class="error-msg">' . $err_html . '</div>';
    $html .= '<a class="back-link" href="' . htmlspecialchars($_SERVER['PHP_SELF']) . '">&larr; Try Again</a>';
    show_page('TRS to Lat/Lon - Error', $html);
    exit;
}

$township_val = 'T' . ltrim($township, 'Tt');
$range_val    = 'R' . ltrim($range,    'Rr');
$trs_label    = $township_val . ' ' . $range_val . ' Section ' . $section;

// ======================================================================
// Check local database first
// ======================================================================
$db_path   = __DIR__ . DIRECTORY_SEPARATOR . $DB_FILE;
$db_record = db_lookup($db_path, $trs_label);
$source    = '';

if ($db_record !== null) {
    // Format to 4 decimal places regardless of how the value is stored in the DB
    $lat_disp = number_format((float)$db_record['lat'], 4);
    $lon_disp = number_format((float)$db_record['lon'], 4);
    $source   = 'local';
} else {
    // Cache miss - query BLM API
    $trs_param = implode('+', array($state, $DEFAULT_MERIDIAN, $township_val, $range_val, 'SEC', $section));
    $blm_url   = 'https://gis.blm.gov/arcgis/rest/services/Cadastral/BLM_Natl_PLSS_CadNSDI'
               . '/MapServer/exts/CadastralSpecialServices/GetLatLon'
               . '?trs=' . $trs_param . '&f=pjson';

    $resp = http_get($blm_url);

    if ($resp['error'] || !$resp['body']) {
        $msg = 'BLM GIS service could not be reached.' . ($resp['error'] ? ' (' . $resp['error'] . ')' : '');
        $html  = '<h2>TRS to<br>Lat / Lon</h2>';
        $html .= '<div class="error-msg">ERROR: ' . htmlspecialchars($msg) . '</div>';
        $html .= '<a class="back-link" href="' . htmlspecialchars($_SERVER['PHP_SELF']) . '">&larr; Try Again</a>';
        show_page('TRS to Lat/Lon - Error', $html);
        exit;
    }

    $blm_data = json_decode($resp['body'], true);

    if (json_last_error() !== JSON_ERROR_NONE) {
        $html  = '<h2>TRS to<br>Lat / Lon</h2>';
        $html .= '<div class="error-msg">ERROR: BLM returned an unexpected response (not valid JSON).</div>';
        $html .= '<a class="back-link" href="' . htmlspecialchars($_SERVER['PHP_SELF']) . '">&larr; Try Again</a>';
        show_page('TRS to Lat/Lon - Error', $html);
        exit;
    }

    $latitude  = null;
    $longitude = null;

    if (!empty($blm_data['coordinates'][0])) {
        $coord     = $blm_data['coordinates'][0];
        $latitude  = isset($coord['lat'])  ? $coord['lat']  : (isset($coord['latitude'])  ? $coord['latitude']  : (isset($coord['y']) ? $coord['y'] : null));
        $longitude = isset($coord['lon'])  ? $coord['lon']  : (isset($coord['longitude']) ? $coord['longitude'] : (isset($coord['x']) ? $coord['x'] : null));
    }
    if ($latitude === null && isset($blm_data['lat'])) {
        $latitude  = $blm_data['lat'];
        $longitude = isset($blm_data['lon']) ? $blm_data['lon'] : (isset($blm_data['lng']) ? $blm_data['lng'] : null);
    }

    if ($latitude === null || $longitude === null) {
        $html  = '<h2>TRS to<br>Lat / Lon</h2>';
        $html .= '<div class="error-msg">ERROR: No coordinates returned by BLM for that location.<br>Please check your Township, Range, and Section values.</div>';
        $html .= '<a class="back-link" href="' . htmlspecialchars($_SERVER['PHP_SELF']) . '">&larr; Try Again</a>';
        show_page('TRS to Lat/Lon - Error', $html);
        exit;
    }

    $lat_disp = number_format((float)$latitude,  4);
    $lon_disp = number_format((float)$longitude, 4);
    $source   = 'api';

    db_append($db_path, $trs_label . ', ' . $lat_disp . ', ' . $lon_disp);
}

// ======================================================================
// Write latest.txt
// ======================================================================
$result_line = $trs_label . ', ' . $lat_disp . ', ' . $lon_disp;
$file_note   = '';

if ($OUTPUT_FILE !== '') {
    $write_path = __DIR__ . DIRECTORY_SEPARATOR . $OUTPUT_FILE;
    $bytes = file_put_contents($write_path, $result_line . "\n");
    if ($bytes === false) {
        $file_note = '<div class="error-msg" style="font-size:11px;margin-top:8px;">Note: Could not write latest.txt</div>';
    }
}

// ======================================================================
// Display result
// ======================================================================
$html  = '<h2>TRS to<br>Lat / Lon</h2>';
$html .= '<div class="result-box">';
$html .= '<div class="result-trs">' . htmlspecialchars($trs_label) . '</div>';
$html .= '<div class="result-coords">' . htmlspecialchars($lat_disp) . ', ' . htmlspecialchars($lon_disp) . '</div>';

if ($SHOW_STATUS) {
    if ($source === 'local') {
        $html .= '<span class="source-tag source-local">&#10003; From local database</span>';
    } else {
        $html .= '<span class="source-tag source-api">&#8635; From BLM API</span>';
    }
    $html .= '<hr class="divider">';
    $html .= '<div class="result-saved">Saved to latest.txt:<br>' . htmlspecialchars($result_line) . '</div>';
    $html .= $file_note;
} else {
    $html .= '<hr class="divider">';
}

$html .= '<a class="new-lookup-btn" href="' . htmlspecialchars($_SERVER['PHP_SELF']) . '">New Lookup</a>';
$html .= '</div>';

show_page('TRS to Lat/Lon - Result', $html);

// --- END OF FILE: trs2latlon.php Version 1.05 ---