<?php

/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

/**
 * A single variable-width line on a receipt, with different formatting options
 */
class ReceiptLine {

    const LINEFORMAT_PLAIN = 1;
    const LINEFORMAT_BOLD = 2;
    const LINEFORMAT_HR = 4;
    const LINEFORMAT_CENTER = 8;
    const LINEFORMAT_BLANK = 16;

    private $left = "";
    private $middle = "";
    private $right = "";
    private $format;

    function __construct($l = "", $m = "", $r = "", $f = ReceiptLine::LINEFORMAT_PLAIN) {
        $this->left = $l;
        $this->middle = $m;
        $this->right = $r;
        $this->format = $f;
    }

    function getLeft() {
        return $this->left;
    }

    function getMiddle() {
        return $this->middle;
    }

    function getRight() {
        return $this->right;
    }

    function hasFormat($format) {
        return ($this->format & $format);
    }

    function getHtml() {
        if ($this->hasFormat($this::LINEFORMAT_HR)) {
            return "<hr />";
        }
        if ($this->hasFormat($this::LINEFORMAT_BLANK)) {
            return "<br />";
        }
        $html = "";
        if (!empty($this->left) || $this->left === 0 || $this->left === "0") {
            $html .= '<span>' . htmlspecialchars($this->left) . '&nbsp;</span>';
        }
        if (!empty($this->middle) || $this->middle === 0 || $this->middle === "0") {
            $html .= '<span>' . htmlspecialchars($this->middle) . '&nbsp;</span>';
        }
        if (!empty($this->right) || $this->right === 0 || $this->right === "0") {
            $html .= '<span>' . htmlspecialchars($this->right) . '</span>';
        }
        $classes = ["flex"];
        if ($this->hasFormat($this::LINEFORMAT_BOLD)) {
            $classes[] = "bold";
        }
        if ($this->hasFormat($this::LINEFORMAT_CENTER)) {
            $classes[] = "centered";
        }
        $classstr = implode(" ", $classes);
        return "<div class=\"$classstr\">$html</div>";
    }

    function getPlainText($width) {
        if ($this->hasFormat($this::LINEFORMAT_HR)) {
            return str_repeat("-", $width);
        }
        if ($this->hasFormat($this::LINEFORMAT_BLANK)) {
            return str_repeat(" ", $width);
        }
        $left = $this->left;
        $middle = $this->middle;
        $right = $this->right;
        $leftln = strlen($left);
        $middleln = strlen($middle);
        $rightln = strlen($right);
        if ($middleln > 0) {
            $middleln++;
            $middle = " " . $middle;
        }
        if ($rightln > 0) {
            $rightln++;
            $right = " " . $right;
        }
        $strln = $leftln + $middleln + $rightln;
        if ($strln < $width) {
            if ($this->hasFormat($this::LINEFORMAT_CENTER)) {
                return str_pad($left . $middle . $right, $width, " ", STR_PAD_BOTH);
            } else {
                $middle = str_pad($middle, $width - $leftln - $rightln, " ", STR_PAD_BOTH);
            }
        } else if ($strln > $width) {
            $loseln = $strln - $width;
            $left = substr($this->left, 0, $leftln - $loseln);
        }
        return $left . $middle . $right;
    }

    function getArray($width = 64) {
        $data = [
            "left" => $this->left,
            "middle" => $this->middle,
            "right" => $this->right,
            "text" => $this->getPlainText($width),
            "format" => []
        ];
        if ($this->hasFormat($this::LINEFORMAT_PLAIN)) {
            $data['format'][] = "plain";
        }
        if ($this->hasFormat($this::LINEFORMAT_BOLD)) {
            $data['format'][] = "bold";
        }
        if ($this->hasFormat($this::LINEFORMAT_HR)) {
            $data['format'][] = "hr";
        }
        if ($this->hasFormat($this::LINEFORMAT_CENTER)) {
            $data['format'][] = "center";
        }
        if ($this->hasFormat($this::LINEFORMAT_BLANK)) {
            $data['format'][] = "blank";
        }
        return $data;
    }

}

class Receipt {

    private $lines = [];
    private $header = [];
    private $footer = [];

    function __construct() {

    }

    function appendLine(ReceiptLine $line) {
        $this->lines[] = $line;
    }

    function appendLines($lines) {
        foreach ($lines as $l) {
            $this->lines[] = $l;
        }
    }

    function appendHeader(ReceiptLine $line) {
        $this->header[] = $line;
    }

    function appendFooter(ReceiptLine $line) {
        $this->footer[] = $line;
    }

    function appendBreak() {
        $this->lines[] = new ReceiptLine("", "", "", ReceiptLine::LINEFORMAT_HR);
    }

    function appendBlank() {
        $this->lines[] = new ReceiptLine("", "", "", ReceiptLine::LINEFORMAT_BLANK);
    }

    function getHtml($title = "") {
        global $SECURE_NONCE;
        $html = <<<END
<!DOCTYPE html>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>$title</title>
<style nonce="$SECURE_NONCE">
.flex {
    display: flex;
    justify-content: space-between;
    margin: 0;
}
.bold {
    font-weight: bold;
}
.centered {
    justify-content: center;
}
</style>
END;
        if (count($this->header) > 0) {
            foreach ($this->header as $line) {
                $html .= $line->getHtml() . "\n";
            }
            $html .= (new ReceiptLine("", "", "", ReceiptLine::LINEFORMAT_HR))->getHtml();
        }
        foreach ($this->lines as $line) {
            $html .= $line->getHtml() . "\n";
        }
        if (count($this->footer) > 0) {
            $html .= (new ReceiptLine("", "", "", ReceiptLine::LINEFORMAT_HR))->getHtml();
            foreach ($this->footer as $line) {
                $html .= $line->getHtml() . "\n";
            }
        }
        return $html;
    }

    function getPlainText($width) {
        $lines = [];
        if (count($this->header) > 0) {
            foreach ($this->header as $line) {
                $lines[] = $line->getPlainText($width);
            }
            $lines[] = (new ReceiptLine("", "", "", ReceiptLine::LINEFORMAT_HR))->getPlainText($width);
        }
        foreach ($this->lines as $line) {
            $lines[] = $line->getPlainText($width);
        }
        if (count($this->footer) > 0) {
            $lines[] = (new ReceiptLine("", "", "", ReceiptLine::LINEFORMAT_HR))->getPlainText($width);
            foreach ($this->footer as $line) {
                $lines[] = $line->getPlainText($width);
            }
        }
        return implode("\n", $lines);
    }

    function getArray($width = 64) {
        $header = [];
        $lines = [];
        $footer = [];
        foreach ($this->header as $line) {
            $header[] = $line->getArray($width);
        }
        foreach ($this->lines as $line) {
            $lines[] = $line->getArray($width);
        }
        foreach ($this->footer as $line) {
            $footer[] = $line->getArray($width);
        }
        return ["header" => $header, "lines" => $lines, "footer" => $footer];
    }

    function getJson($width = 64) {
        return json_encode($this->getArray($width));
    }

}
