<?php
// Copyright (c) Pickware GmbH. All rights reserved.
// This file is part of software that is released under a proprietary license.
// You must not copy, modify, distribute, make publicly available, or execute
// its contents or parts thereof without express permission by the copyright
// holder, unless otherwise permitted by law.

namespace Shopware\Plugins\ViisonUPS\Classes;

use Shopware\Plugins\ViisonCommon\Classes\Document\PaperLayout;
use Shopware\Plugins\ViisonCommon\Classes\Document\RenderingEngine\DomPdfRenderingEngine;

/**
 * Instance represents the image of an UPS label.
 *
 * This class has the ability to reformat the image (crop, rotate, trim) and convert it into PDF.
 * We offer 4 different print formats:
 *   * "A4" generates a PDF with the label resized to the upper half of a DIN A4 paper. Used for envelopes.
 *   * "A5" generates a PDF with the label in original label size (4" x 7") centered on a DIN A5 page. Used for
 *     printing on DHL labels.
 *   * "ups-4-inch-7-inch" generates a 4" x 7" PDF that contains only the label in original size and no white print
 *     borders. This can be used for label printers together with "FIT_TO_PAGE" feature.
 *   * "ups-4-inch-6-inch" generates a 4" x 6" PDF that contains only the label in original size and no white print
 *     borders. The legal notice on the label may be trimmed away. This can be used for label printers together with
 *     "FIT_TO_PAGE" feature.
 */
class UPSLabelImage
{
    const PDF_SIZE_A4 = 'A4';
    const PDF_SIZE_A5 = 'A5';
    const PDF_SIZE_4X6_INCH = '4x6-inch';
    const PDF_SIZE_4X7_INCH = '4x7-inch';

    const LABEL_WIDTH_IN_MM = 101.6;
    const LABEL_WITH_IN_PIXELS = 800;
    const LABEL_HEIGHT_6_INCH_IN_MM = 152.4;
    const LABEL_HEIGHT_6_INCH_IN_PIXELS = 1201;
    const LABEL_HEIGHT_7_INCH_IN_MM = 177.8;
    const LABEL_HEIGHT_7_INCH_IN_PIXELS = 1400;

    /**
     * @var resource
     */
    private $image;

    /**
     * Converts an UPS label image to PDF format.
     *
     * @param string $imageString Binary representation of any supported image format
     * @param string $pdfSize Size of the PDF to create (one of self::PDF_SIZE_*)
     * @return string Binary representation of the created PDF file
     */
    public static function convertUpsLabelImageToPdf($imageString, $pdfSize)
    {
        $upsLabelImage = new self($imageString);

        // The label image from UPS is always in landscape format and rotated by 90 degrees anti clockwise. It would
        // perfectly fill a landscape orientated A5. But we want a portrait orientated A5, so we need to rotate the
        // label image for that format.
        // For printing on A4 no rotation is necessary. The image is in correct format.

        switch ($pdfSize) {
            case self::PDF_SIZE_A4:
                // Since we stretch the label we want to remove the white bar at the right first.
                $upsLabelImage->cropImage();
                $paperLayout = PaperLayout::createDefaultPaperLayout('A4');
                $htmlTemplate = self::getHtmlTemplateForA4();
                break;
            case self::PDF_SIZE_A5:
                $upsLabelImage->rotateClockwise();
                $paperLayout = PaperLayout::createDefaultPaperLayout('A5');
                $htmlTemplate = self::getHtmlTemplateForA5(self::LABEL_WIDTH_IN_MM, self::LABEL_HEIGHT_7_INCH_IN_MM);
                break;
            case self::PDF_SIZE_4X6_INCH:
                $upsLabelImage->rotateClockwise();
                $upsLabelImage->trimHeightAtBottomTo(self::LABEL_HEIGHT_6_INCH_IN_PIXELS);
                $paperLayout = new PaperLayout();
                $paperLayout->pageHeightInMillimeters = self::LABEL_HEIGHT_6_INCH_IN_MM;
                $paperLayout->pageWidthInMillimeters = self::LABEL_WIDTH_IN_MM;
                $paperLayout->pageOrientation = PaperLayout::PAGE_ORIENTATION_PORTRAIT;
                $htmlTemplate = self::getHtmlTemplateForInchPdfSized(self::LABEL_WIDTH_IN_MM, self::LABEL_HEIGHT_6_INCH_IN_MM);
                break;
            case self::PDF_SIZE_4X7_INCH:
                $upsLabelImage->rotateClockwise();
                $paperLayout = new PaperLayout();
                $paperLayout->pageHeightInMillimeters = self::LABEL_HEIGHT_7_INCH_IN_MM;
                $paperLayout->pageWidthInMillimeters = self::LABEL_WIDTH_IN_MM;
                $paperLayout->pageOrientation = PaperLayout::PAGE_ORIENTATION_PORTRAIT;
                $htmlTemplate = self::getHtmlTemplateForInchPdfSized(self::LABEL_WIDTH_IN_MM, self::LABEL_HEIGHT_7_INCH_IN_MM);
                break;
            default:
                throw new \InvalidArgumentException(sprintf(
                    'Method %s called invalid value for second parameter $pdfSize.',
                    __METHOD__
                ));
        }

        return $upsLabelImage->renderAsPdf($htmlTemplate, $paperLayout);
    }

    /**
     * @param string $imageString
     * @throws \Exception
     */
    public function __construct($imageString)
    {
        $this->image = imagecreatefromstring($imageString);
        if ($this->image === false) {
            throw new \Exception('Image has not a recognizable image format.'); // TODO
        }
    }

    /**
     * Crops the white border of the label image.
     */
    public function cropImage()
    {
        // In some cases the use of the IMG_CROP_WHITE or IMG_CROP_BLACK does not work. The function returns FALSE. It
        // is best to use the IMG_CROP_THRESHOLD mode and specify the color in fourth argument
        $croppedLabelImage = imagecropauto(
            $this->image,
            IMG_CROP_THRESHOLD,
            0.3,
            imagecolorallocate($this->image, 255, 255, 255)
        );
        if ($croppedLabelImage === false) {
            return;
        }

        $this->image = $croppedLabelImage;
    }

    /**
     * Reduces the height of the label image to $heightInPixels by cutting the content away in the bottom.
     *
     * @param int $heightInPixels
     */
    public function trimHeightAtBottomTo($heightInPixels)
    {
        $croppedImage = imagecrop($this->image, [
            'x' => 0,
            'y' => 0,
            'width' => imagesx($this->image),
            'height' => min($heightInPixels, imagesy($this->image)),
        ]);

        if ($croppedImage === false) {
            return;
        }

        $this->image = $croppedImage;
    }

    /**
     * Rotates the label image by 90 degrees clockwise
     */
    public function rotateClockwise()
    {
        $rotatedImage = imagerotate($this->image, -90, imagecolorallocate($this->image, 255, 255, 255));

        if ($rotatedImage === false) {
            return;
        }

        $this->image = $rotatedImage;
    }

    /**
     * Renders the image to a PDF.
     *
     * @param string $htmlTemplate
     * @param PaperLayout $paperLayout
     * @return string
     */
    public function renderAsPdf($htmlTemplate, PaperLayout $paperLayout)
    {
        // Fix PNG image in PDF rendered by DomPdf being just white on some systems. The actual dependencies that cause
        // the problem could not be found out, but the following line fixes the problem:
        imagepalettetotruecolor($this->image);

        ob_start();
        imagepng($this->image);
        $png = ob_get_contents();
        ob_end_clean();

        $html = sprintf($htmlTemplate, base64_encode($png));

        $domPdfRenderer = new DomPdfRenderingEngine();

        return $domPdfRenderer->render($html, $paperLayout);
    }

    /**
     * @return string
     */
    private static function getHtmlTemplateForA4()
    {
        // Use width of 99% of avoid a page break before the image that DomPdf may add.
        return '<html><body><img style="width: 99%%" src="data:image/png;base64,%s"/></body></html>';
    }

    /**
     * @param float $widthInMm
     * @param float $heightInMm
     * @return string
     */
    private static function getHtmlTemplateForA5($widthInMm, $heightInMm)
    {
        return
            '<html>
                <style type="text/css">
                    img {
                        width: ' . $widthInMm . 'mm;
                        height: ' . $heightInMm . 'mm;
                    }
                    body {
                        text-align: center;
                    }
                </style>
                <body>
                    <img src="data:image/png;base64,%s" />
                </body>
            </html>';
    }

    /**
     * @param float $widthInMm
     * @param float $heightInMm
     * @return string
     */
    private static function getHtmlTemplateForInchPdfSized($widthInMm, $heightInMm)
    {
        return
            '<html>
                <style type="text/css">
                    @page {
                        margin: 0;
                    }
                    img {
                        width: ' . $widthInMm . 'mm;
                        height: ' . $heightInMm . 'mm;
                    }
                    body {
                        margin: 0;
                    }
                </style>
                <body>
                    <img src="data:image/png;base64,%s" />
                </body>
            </html>';
    }
}
