<?php

namespace smusatov\bzb2u;

class Printer
{
    private $resourcePrinter;

    public function __construct(string $device = '/dev/usb/lp')
    {
        if(!file_exists($device)) {
            throw new \Exception('The printer path does not exist');
        }

        if(!is_writable($device)) {
            throw new \Exception('The printer is not writable');
        }

        $this->resourcePrinter = fopen($device, 'w+b');

        if($this->resourcePrinter === false) {
            throw new \Exception('The printer could not be opened');
        }

        $this->setCharacterSets();
        $this->setSensorThrough();
        $this->setDarknessLevel();
        $this->setPrintSpeed();
        $this->setMicroMarginTop();
    }

    protected function setCharacterSets(int $n = 0): void
    {
        $n = max(0, min(9, $n));

        fprintf($this->resourcePrinter, '%c%s%u', 27, 'I', $n);
        usleep(100);
    }

    public function setSensorThrough(): void
    {
        fprintf($this->resourcePrinter, '%c%s', 27, 'F0');
        usleep(100);
    }

    public function setDarknessLevel(int $n = 5): void
    {
        $n = max(0, min(7, $n));

        fprintf($this->resourcePrinter, '%c%s%u', 27, 'D', $n);
        usleep(100);
    }

    public function setPrintSpeed(int $n = 1): void
    {
        $n = max(1, min(2, $n));

        fprintf($this->resourcePrinter, '%c%s%u', 27, 'S', $n);
        usleep(100);
    }

    protected function setMicroMarginTop(int $n = 0): void
    {
        $n = max(0, min(7, $n));

        fprintf($this->resourcePrinter, '%c%s%u', 27, 'a', $n);
        usleep(100);
    }

    public function setSensorReflect(): void
    {
        fprintf($this->resourcePrinter, '%c%s', 27, 'F1');
        usleep(100);
    }

    protected function setMarginLeft(int $marginLeft): void
    {
        fprintf($this->resourcePrinter, '%c%s%02u', 27, 'M', $marginLeft);
        usleep(100);
    }

    protected function setLabelLength(int $length): void
    {
        fprintf($this->resourcePrinter, '%c%s%03u', 27, 'q', $length);
        usleep(100);
    }

    public function moveForward(int $mm): void
    {
        while($mm > 0) {
            $move = min($mm, 29);
            fprintf($this->resourcePrinter, '%c%s%02u', 27, 'f', $move);
            usleep(ceil($move * 1000000 / 50.8));

            $mm -= 29;
        }
    }

    public function moveBack(int $mm): void
    {
        while($mm > 0) {
            $move = min($mm, 29);
            fprintf($this->resourcePrinter, '%c%s%02u', 27, 'R', $move);
            usleep(ceil($move * 1000000 / 50.8));

            $mm -= 29;
        }
    }

    public function feedForm(): void
    {
        fprintf($this->resourcePrinter, '%c', 12);
        usleep(125000);
    }

    public function move(int $mm): void
    {
        if($mm > 0) {
            $this->moveForward($mm);
        } elseif($mm < 0) {
            $this->moveBack(abs($mm));
        }
    }

    protected function bufferOn(): void
    {
        fprintf($this->resourcePrinter, '%c%s', 27, 'B');
        usleep(100);
    }

    protected function bufferOff(): void
    {
        fprintf($this->resourcePrinter, '%c%s', 27, 'E');
        usleep(100);
    }

    protected function printBuffer(int $count): void
    {
        fprintf($this->resourcePrinter, '%c%s%02u', 27, 'P', $count);
        usleep(100);
    }

    public function execute(Job $job): void
    {
        $this->setMarginLeft(max(min(floor((56 - $job->getLabel()->getWidth()) / 2), 28), 0));
        $this->setLabelLength($job->getLabel()->getHeight() + $job->getLabel()->getGap());

        $this->bufferOn();

        foreach($job->getLines() as $line) {

            $bytes = array_chunk($line, 8);

            $command = '';
            foreach($bytes as $byte) {
                $command .= chr(bindec(join('', $byte)));
            }

            fprintf($this->resourcePrinter, '%cG%02u%s', 27, count($bytes), $command);
            usleep(100);
        }

        $this->bufferOff();

        $this->printBuffer($job->getCount());
        usleep(ceil(($job->getLabel()->getHeight() + $job->getLabel()->getGap()) * 1000000 * $job->getCount() / 50.8));
    }

}
