const std = @import("std");

const escaper = @import("../../../escaper.zig");
const api = @import("../../../api/root.zig");

const icons = @import("icons/root.zig");

pub const Departure = struct {
    time: []const u8,
    time_unix: i128,
    line: []const u8,
    transportation_product_class: api.efa.models.TransportationProductClass,
    operator: ?[]const u8,
    destination: []const u8,
    platform: ?[]const u8,
    date: ?[]const u8,
    on_time: ?u32,
    delayed: ?u32,
    cancelled: bool,
    information: ?[]const []const u8,

    pub fn cmpByTimeUnix(context: void, a: Departure, b: Departure) bool {
        return std.sort.asc(i128)(context, a.time_unix, b.time_unix);
    }

    pub fn escape(self: *const Departure, allocator: std.mem.Allocator) !Departure {
        var copy: Departure = undefined;

        const time = try escaper.escapeHtml(allocator, self.time);
        errdefer if (time) |s| allocator.free(s);
        copy.time = time orelse self.time;

        const line = try escaper.escapeHtml(allocator, self.line);
        errdefer if (line) |s| allocator.free(s);
        copy.line = line orelse self.line;

        copy.transportation_product_class = self.transportation_product_class;

        copy.operator = null;
        if (self.operator) |str| {
            const operator = try escaper.escapeHtml(allocator, str);
            errdefer if (operator) |s| allocator.free(s);
            copy.operator = operator orelse str;
        }

        const destination = try escaper.escapeHtml(allocator, self.destination);
        errdefer if (destination) |s| allocator.free(s);
        copy.destination = destination orelse self.destination;

        copy.platform = null;
        if (self.platform) |str| {
            const platform = try escaper.escapeHtml(allocator, str);
            errdefer if (platform) |s| allocator.free(s);
            copy.platform = platform orelse str;
        }

        copy.date = null;
        if (self.date) |str| {
            const date = try escaper.escapeHtml(allocator, str);
            errdefer if (date) |s| allocator.free(s);
            copy.date = date orelse str;
        }

        copy.on_time = self.on_time;
        copy.delayed = self.delayed;
        copy.cancelled = self.cancelled;

        copy.information = null;
        if (self.information) |arr| {
            const infos = try allocator.alloc([]const u8, arr.len);
            for (arr, 0..) |str, i| {
                const information = try escaper.escapeHtml(allocator, str);
                errdefer if (information) |s| allocator.free(s);
                infos[i] = information orelse str;
            }
            copy.information = infos;
        }

        return copy;
    }
};

pub const Context = struct {
    station: []const u8,
    time: []const u8,
    departures: []const Departure,
    show_operator: bool,

    pub fn escape(self: *const Context, allocator: std.mem.Allocator) !Context {
        var copy: Context = undefined;

        const station = try escaper.escapeHtml(allocator, self.station);
        errdefer if (station) |s| allocator.free(s);
        copy.station = station orelse self.station;

        const time = try escaper.escapeHtml(allocator, self.time);
        errdefer if (time) |s| allocator.free(s);
        copy.time = time orelse self.time;

        const departures = try allocator.alloc(Departure, self.departures.len);
        errdefer allocator.free(copy.departures);
        for (self.departures, 0..) |departure, i| {
            const escaped = try departure.escape(allocator);
            departures[i] = escaped;
        }
        copy.departures = departures;

        copy.show_operator = self.show_operator;

        return copy;
    }
};

pub fn render(context: *const Context, writer: std.io.AnyWriter) !void {
    try writer.writeAll(
        \\<!DOCTYPE html>
        \\<html lang="de">
        \\  <head>
        \\    <meta charset="UTF-8" />
        \\    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    );
    try writer.print(
        \\<style>{[fonts_css]s}</style>
        \\<style>{[style_css]s}</style>
    ,
        .{
            .fonts_css = @embedFile("css/fonts.css.dat"),
            .style_css = @embedFile("css/style.css"),
        },
    );
    try writer.writeAll(
        \\</head>
        \\<body>
        \\  <div class="monitor">
        \\    <header class="header">
    );
    try writer.print(
        \\<h1 class="header-title">Abfahrten: {[station]s}</h1>
        \\<h2 class="header-time">{[time]s}</h2>
    , .{ .station = context.station, .time = context.time });
    try writer.writeAll(
        \\</header>
        \\  <table style="width: 100%" class="departures">
        \\    <colgroup>
        \\      <col span="1" style="width: 15%;">
        \\      <col span="1" style="width: 10%;">
        \\      <col span="1" style="width: 30%;">
        \\      <col span="1" style="width: 30%;">
        \\      <col span="1" style="width: 15%;">
        \\    </colgroup>
        \\    <thead>
        \\      <tr class="border">
        \\        <th>Zeit</th>
        \\        <th colspan="2">Linie</th>
        \\        <th>Ziel</th>
        \\        <th style="text-align: right;">Gleis/<wbr />Steig</th>
        \\      </tr>
        \\    </thead>
        \\    <tbody>
    );

    for (context.departures) |departure| {
        try writer.print(
            \\<tr class="data {[cancelled]s} {[border]s}">
        ,
            .{
                .cancelled = if (departure.cancelled) "status-cancelled" else "",
                .border = if (departure.information == null) "border" else "",
            },
        );

        try writer.print("<td>{s}", .{departure.time});
        if (departure.on_time) |n| {
            try writer.print(
                \\&nbsp;<wbr /><span class="status-time">(-{d})</span>
            , .{n});
        }
        if (departure.delayed) |n| {
            try writer.print(
                \\&nbsp;<wbr /><span class="status-time">(+{d})</span>
            , .{n});
        }
        if (departure.date) |date| {
            try writer.print(
                \\<br /><span class="status-date">({s})</span>
            ,
                .{date},
            );
        }

        if (icons.iconFromTransportationProductClass(departure.transportation_product_class)) |file| {
            try writer.print(
                \\  <td>
                \\    <img class="icon" src="data:{[content_type]s};charset=utf-8;base64,{[data]s}" />
                \\  </td>
            ,
                .{
                    .content_type = file.content_type,
                    .data = file.data,
                },
            );
        } else {
            try writer.writeAll(
                \\<td></td>
            );
        }

        try writer.print(
            \\<td><span>{[line]s}<span>
        ,
            .{ .line = departure.line },
        );
        if (context.show_operator) {
            if (departure.operator) |operator| {
                try writer.print(
                    \\<br /><span class="operator">{[operator]s}</span>
                ,
                    .{ .operator = operator },
                );
            }
        }
        try writer.writeAll(
            \\</td>
        );

        try writer.print(
            \\  <td>{[destination]s}</td>
            \\  <td style="text-align: right;">{[platform]s}</td>
            \\</tr>
        ,
            .{
                .destination = departure.destination,
                .platform = departure.platform orelse "",
            },
        );

        if (departure.information) |infos| {
            try writer.print(
                \\<tr class="border info {[cancelled]s}">
                \\  <td></td>
                \\  <td></td>
                \\  <td colspan="3">
            ,
                .{
                    .cancelled = if (departure.cancelled) "status-cancelled" else "",
                },
            );
            for (infos, 0..) |str, i| {
                try writer.print("<span>{s}</span>", .{str});
                if (i < infos.len - 1)
                    try writer.writeAll("<br />");
            }
            try writer.writeAll(
                \\  </td>
                \\</tr>
            );
        }
    }

    try writer.writeAll(
        \\        </tbody>
        \\      </table>
        \\    </div>
        \\  </body>
        \\</html>
    );
}
