-- server.lua
-- Simple HTTP server in Lua using LuaSocket
-- Install dependency: luarocks install luasocket

local socket = require("socket")

-- ─── Configuration ────────────────────────────────────────────────────────────
local HOST = "127.0.0.1"
local PORT = 8080

-- ─── In-memory data store ─────────────────────────────────────────────────────
local messages = {}  -- stores messages sent from the client

-- ─── Helper: parse the request body from raw HTTP request ─────────────────────
local function parse_body(raw)
    -- Body follows the blank line after headers
    local body = raw:match("\r\n\r\n(.*)")
    return body or ""
end

-- ─── Helper: URL-decode a string ──────────────────────────────────────────────
local function url_decode(str)
    str = str:gsub("+", " ")
    str = str:gsub("%%(%x%x)", function(h)
        return string.char(tonumber(h, 16))
    end)
    return str
end

-- ─── Helper: parse application/x-www-form-urlencoded body ────────────────────
local function parse_form(body)
    local params = {}
    for key, value in body:gmatch("([^&=]+)=([^&=]*)") do
        params[url_decode(key)] = url_decode(value)
    end
    return params
end

-- ─── Helper: encode a Lua table as a simple JSON object ───────────────────────
local function to_json(tbl)
    local parts = {}
    for k, v in pairs(tbl) do
        local val
        if type(v) == "string" then
            val = string.format('"%s"', v:gsub('"', '\\"'))
        elseif type(v) == "number" then
            val = tostring(v)
        elseif type(v) == "boolean" then
            val = tostring(v)
        else
            val = '"[unsupported]"'
        end
        table.insert(parts, string.format('"%s": %s', k, val))
    end
    return "{ " .. table.concat(parts, ", ") .. " }"
end

-- ─── Helper: encode a list of messages as a JSON array ────────────────────────
local function messages_to_json()
    local items = {}
    for _, msg in ipairs(messages) do
        table.insert(items, to_json(msg))
    end
    return "[" .. table.concat(items, ", ") .. "]"
end

-- ─── HTTP response builders ────────────────────────────────────────────────────
local function http_response(status, content_type, body)
    return string.format(
        "HTTP/1.1 %s\r\nContent-Type: %s\r\nContent-Length: %d\r\nAccess-Control-Allow-Origin: *\r\nConnection: close\r\n\r\n%s",
        status, content_type, #body, body
    )
end

local function ok_json(body)
    return http_response("200 OK", "application/json", body)
end

local function ok_html(body)
    return http_response("200 OK", "text/html", body)
end

local function not_found()
    return http_response("404 Not Found", "text/plain", "404 Not Found")
end

-- ─── HTML page served to the browser ─────────────────────────────────────────
local HTML = [[<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>Lua Server Demo</title>
  <style>
    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

    :root {
      --bg:      #0d1117;
      --surface: #161b22;
      --border:  #30363d;
      --accent:  #58a6ff;
      --green:   #3fb950;
      --text:    #e6edf3;
      --muted:   #8b949e;
      --radius:  10px;
    }

    body {
      font-family: 'Courier New', monospace;
      background: var(--bg);
      color: var(--text);
      min-height: 100vh;
      display: flex;
      flex-direction: column;
      align-items: center;
      padding: 40px 20px;
    }

    header {
      text-align: center;
      margin-bottom: 36px;
    }

    header h1 {
      font-size: 2rem;
      color: var(--accent);
      letter-spacing: 2px;
    }

    header p {
      color: var(--muted);
      margin-top: 6px;
      font-size: 0.85rem;
    }

    .card {
      background: var(--surface);
      border: 1px solid var(--border);
      border-radius: var(--radius);
      padding: 24px;
      width: 100%;
      max-width: 640px;
      margin-bottom: 24px;
    }

    .card h2 {
      font-size: 0.9rem;
      text-transform: uppercase;
      letter-spacing: 1.5px;
      color: var(--muted);
      margin-bottom: 16px;
    }

    input[type="text"] {
      width: 100%;
      background: var(--bg);
      border: 1px solid var(--border);
      border-radius: var(--radius);
      color: var(--text);
      padding: 10px 14px;
      font-family: inherit;
      font-size: 0.95rem;
      outline: none;
      transition: border-color 0.2s;
      margin-bottom: 10px;
    }

    input[type="text"]:focus { border-color: var(--accent); }

    button {
      background: var(--accent);
      color: #0d1117;
      border: none;
      border-radius: var(--radius);
      padding: 10px 20px;
      font-family: inherit;
      font-size: 0.9rem;
      font-weight: bold;
      cursor: pointer;
      transition: opacity 0.2s;
    }

    button:hover { opacity: 0.85; }

    #status {
      margin-top: 10px;
      font-size: 0.8rem;
      color: var(--green);
      min-height: 18px;
    }

    #message-list {
      list-style: none;
      display: flex;
      flex-direction: column;
      gap: 10px;
    }

    #message-list li {
      background: var(--bg);
      border: 1px solid var(--border);
      border-radius: var(--radius);
      padding: 10px 14px;
      font-size: 0.88rem;
    }

    #message-list li span.sender {
      color: var(--accent);
      font-weight: bold;
      margin-right: 8px;
    }

    #message-list li span.time {
      float: right;
      color: var(--muted);
      font-size: 0.78rem;
    }

    #server-info {
      font-size: 0.82rem;
      color: var(--muted);
      line-height: 1.8;
    }

    #server-info b { color: var(--text); }
  </style>
</head>
<body>
  <header>
    <h1>&#x25B6; Lua HTTP Server</h1>
    <p>Bidirectional client &harr; server communication demo</p>
  </header>

  <!-- Send a message -->
  <div class="card">
    <h2>Send a Message</h2>
    <input type="text" id="nameInput"    placeholder="Your name"    value="Alice" />
    <input type="text" id="messageInput" placeholder="Your message" value="Hello from the browser!" />
    <button onclick="sendMessage()">Send to Server</button>
    <div id="status"></div>
  </div>

  <!-- Live message feed -->
  <div class="card">
    <h2>Server Message Log</h2>
    <ul id="message-list"><li style="color:var(--muted)">Loading…</li></ul>
  </div>

  <!-- Server info -->
  <div class="card">
    <h2>Server Info</h2>
    <div id="server-info">Fetching…</div>
  </div>

  <script>
    const BASE = "";   // same origin

    // ── Send a message to the server ─────────────────────────────────────────
    async function sendMessage() {
      const name    = document.getElementById("nameInput").value.trim();
      const message = document.getElementById("messageInput").value.trim();
      if (!name || !message) return;

      const body = new URLSearchParams({ name, message });

      const res  = await fetch(BASE + "/send", { method: "POST", body });
      const data = await res.json();

      document.getElementById("status").textContent =
        data.ok ? "✓ Message received by server!" : "✗ Error: " + data.error;

      document.getElementById("messageInput").value = "";
      fetchMessages();
    }

    // ── Fetch all messages from the server ───────────────────────────────────
    async function fetchMessages() {
      const res  = await fetch(BASE + "/messages");
      const list = await res.json();

      const ul = document.getElementById("message-list");
      if (list.length === 0) {
        ul.innerHTML = '<li style="color:var(--muted)">No messages yet.</li>';
        return;
      }

      ul.innerHTML = list.reverse().map(m => `
        <li>
          <span class="sender">${esc(m.name)}</span>${esc(m.text)}
          <span class="time">${esc(m.time)}</span>
        </li>
      `).join("");
    }

    // ── Fetch server metadata ────────────────────────────────────────────────
    async function fetchInfo() {
      const res  = await fetch(BASE + "/info");
      const data = await res.json();
      document.getElementById("server-info").innerHTML =
        `<b>Host:</b> ${esc(data.host)} &nbsp;|&nbsp; <b>Port:</b> ${esc(data.port)}<br>` +
        `<b>Uptime:</b> ${esc(data.uptime)}s &nbsp;|&nbsp; <b>Requests served:</b> ${esc(data.requests)}<br>` +
        `<b>Lua version:</b> ${esc(data.lua_version)}`;
    }

    // ── Simple HTML escape ───────────────────────────────────────────────────
    function esc(s) {
      return String(s)
        .replace(/&/g,"&amp;").replace(/</g,"&lt;")
        .replace(/>/g,"&gt;").replace(/"/g,"&quot;");
    }

    // ── Poll the server every 3 seconds ─────────────────────────────────────
    fetchMessages();
    fetchInfo();
    setInterval(fetchMessages, 3000);
    setInterval(fetchInfo,     5000);
  </script>
</body>
</html>
]]

-- ─── Route handler ────────────────────────────────────────────────────────────
local start_time   = os.time()
local request_count = 0

local function handle(raw_request)
    request_count = request_count + 1

    -- Parse method and path from the request line
    local method, path = raw_request:match("^(%u+) (/[^ ]*) HTTP")
    method = method or "GET"
    path   = path   or "/"

    -- GET / → serve the HTML page
    if method == "GET" and path == "/" then
        return ok_html(HTML)

    -- GET /messages → return all stored messages as JSON
    elseif method == "GET" and path == "/messages" then
        return ok_json(messages_to_json())

    -- GET /info → return server metadata as JSON
    elseif method == "GET" and path == "/info" then
        local info = {
            host        = HOST,
            port        = tostring(PORT),
            uptime      = tostring(os.time() - start_time),
            requests    = tostring(request_count),
            lua_version = _VERSION,
        }
        return ok_json(to_json(info))

    -- POST /send → accept a message from the client
    elseif method == "POST" and path == "/send" then
        local body   = parse_body(raw_request)
        local params = parse_form(body)

        local name = params["name"]    or "Anonymous"
        local text = params["message"] or ""

        if text == "" then
            return ok_json('{ "ok": false, "error": "empty message" }')
        end

        -- Store the message
        table.insert(messages, {
            name = name,
            text = text,
            time = os.date("%H:%M:%S"),
        })

        print(string.format("[POST /send] %s: %s", name, text))
        return ok_json('{ "ok": true }')

    else
        return not_found()
    end
end

-- ─── Main server loop ─────────────────────────────────────────────────────────
local server = assert(socket.bind(HOST, PORT))
server:settimeout(0)   -- non-blocking accept

print(string.format("Lua HTTP server running at http://%s:%d/", HOST, PORT))
print("Press Ctrl-C to stop.\n")

while true do
    local client = server:accept()
    if client then
        client:settimeout(5)

        -- Read the full request (stop at blank line for GET, read body for POST)
        local raw = {}
        local content_length = 0

        -- Read headers
        while true do
            local line, err = client:receive("*l")
            if not line or line == "" then break end
            table.insert(raw, line)

            -- Capture Content-Length so we can read the body
            local cl = line:match("^[Cc]ontent%-[Ll]ength:%s*(%d+)")
            if cl then content_length = tonumber(cl) end
        end

        local header_str = table.concat(raw, "\r\n") .. "\r\n\r\n"

        -- Read body if present
        local body_str = ""
        if content_length > 0 then
            body_str = client:receive(content_length) or ""
        end

        local full_request = header_str .. body_str
        local response     = handle(full_request)

        client:send(response)
        client:close()
    end

    socket.sleep(0.01)   -- prevent busy-loop
end
