<?php

class QRinputItem
{
    public $mode;
    public $size;
    public $data;
    public $bstream;
    public function __construct($mode, $size, $data, $bstream = NULL)
    {
        $setData = array_slice($data, 0, $size);
        if (count($setData) < $size) {
            $setData = array_merge($setData, array_fill(0, $size - count($setData), 0));
        }
        if (!QRinput::check($mode, $size, $setData)) {
            throw new Exception('Error m:' . $mode . ',s:' . $size . ',d:' . join(',', $setData));
            return NULL;
        }
        $this->mode = $mode;
        $this->size = $size;
        $this->data = $setData;
        $this->bstream = $bstream;
    }
    public function encodeModeNum($version)
    {
        try {
            $words = (int) $this->size / 3;
            $bs = new QRbitstream();
            $val = 1;
            $bs->appendNum(4, $val);
            $bs->appendNum(QRspec::lengthIndicator(QR_MODE_NUM, $version), $this->size);
            for ($i = 0; $i < $words; $i++) {
                $val = (ord($this->data[$i * 3]) - ord('0')) * 100;
                $val += (ord($this->data[$i * 3 + 1]) - ord('0')) * 10;
                $val += ord($this->data[$i * 3 + 2]) - ord('0');
                $bs->appendNum(10, $val);
            }
            if ($this->size - $words * 3 == 1) {
                $val = ord($this->data[$words * 3]) - ord('0');
                $bs->appendNum(4, $val);
            } else {
                if ($this->size - $words * 3 == 2) {
                    $val = (ord($this->data[$words * 3]) - ord('0')) * 10;
                    $val += ord($this->data[$words * 3 + 1]) - ord('0');
                    $bs->appendNum(7, $val);
                }
            }
            $this->bstream = $bs;
            return 0;
        } catch (Exception $e) {
            return -1;
        }
    }
    public function encodeModeAn($version)
    {
        try {
            $words = (int) $this->size / 2;
            $bs = new QRbitstream();
            $bs->appendNum(4, 2);
            $bs->appendNum(QRspec::lengthIndicator(QR_MODE_AN, $version), $this->size);
            for ($i = 0; $i < $words; $i++) {
                $val = (int) QRinput::lookAnTable(ord($this->data[$i * 2])) * 45;
                $val += (int) QRinput::lookAnTable(ord($this->data[$i * 2 + 1]));
                $bs->appendNum(11, $val);
            }
            if ($this->size & 1) {
                $val = QRinput::lookAnTable(ord($this->data[$words * 2]));
                $bs->appendNum(6, $val);
            }
            $this->bstream = $bs;
            return 0;
        } catch (Exception $e) {
            return -1;
        }
    }
    public function encodeMode8($version)
    {
        try {
            $bs = new QRbitstream();
            $bs->appendNum(4, 4);
            $bs->appendNum(QRspec::lengthIndicator(QR_MODE_8, $version), $this->size);
            for ($i = 0; $i < $this->size; $i++) {
                $bs->appendNum(8, ord($this->data[$i]));
            }
            $this->bstream = $bs;
            return 0;
        } catch (Exception $e) {
            return -1;
        }
    }
    public function encodeModeKanji($version)
    {
        try {
            $bs = new QRbitrtream();
            $bs->appendNum(4, 8);
            $bs->appendNum(QRspec::lengthIndicator(QR_MODE_KANJI, $version), (int) $this->size / 2);
            for ($i = 0; $i < $this->size; $i += 2) {
                $val = ord($this->data[$i]) << 8 | ord($this->data[$i + 1]);
                if ($val <= 40956) {
                    $val -= 33088;
                } else {
                    $val -= 49472;
                }
                $h = ($val >> 8) * 192;
                $val = ($val & 255) + $h;
                $bs->appendNum(13, $val);
            }
            $this->bstream = $bs;
            return 0;
        } catch (Exception $e) {
            return -1;
        }
    }
    public function encodeModeStructure()
    {
        try {
            $bs = new QRbitstream();
            $bs->appendNum(4, 3);
            $bs->appendNum(4, ord($this->data[1]) - 1);
            $bs->appendNum(4, ord($this->data[0]) - 1);
            $bs->appendNum(8, ord($this->data[2]));
            $this->bstream = $bs;
            return 0;
        } catch (Exception $e) {
            return -1;
        }
    }
    public function estimateBitStreamSizeOfEntry($version)
    {
        $bits = 0;
        if ($version == 0) {
            $version = 1;
        }
        switch ($this->mode) {
            case QR_MODE_NUM:
                $bits = QRinput::estimateBitsModeNum($this->size);
                break;
            case QR_MODE_AN:
                $bits = QRinput::estimateBitsModeAn($this->size);
                break;
            case QR_MODE_8:
                $bits = QRinput::estimateBitsMode8($this->size);
                break;
            case QR_MODE_KANJI:
                $bits = QRinput::estimateBitsModeKanji($this->size);
                break;
            case QR_MODE_STRUCTURE:
                return STRUCTURE_HEADER_BITS;
            default:
                return 0;
        }
        $l = QRspec::lengthIndicator($this->mode, $version);
        $m = 1 << $l;
        $num = (int) ($this->size + $m - 1) / $m;
        $bits += $num * (4 + $l);
        return $bits;
    }
    public function encodeBitStream($version)
    {
        try {
            unset($this->bstream);
            $words = QRspec::maximumWords($this->mode, $version);
            if ($words < $this->size) {
                $st1 = new QRinputItem($this->mode, $words, $this->data);
                $st2 = new QRinputItem($this->mode, $this->size - $words, array_slice($this->data, $words));
                $st1->encodeBitStream($version);
                $st2->encodeBitStream($version);
                $this->bstream = new QRbitstream();
                $this->bstream->append($st1->bstream);
                $this->bstream->append($st2->bstream);
                unset($st1);
                unset($st2);
            } else {
                $ret = 0;
                switch ($this->mode) {
                    case QR_MODE_NUM:
                        $ret = $this->encodeModeNum($version);
                        break;
                    case QR_MODE_AN:
                        $ret = $this->encodeModeAn($version);
                        break;
                    case QR_MODE_8:
                        $ret = $this->encodeMode8($version);
                        break;
                    case QR_MODE_KANJI:
                        $ret = $this->encodeModeKanji($version);
                        break;
                    case QR_MODE_STRUCTURE:
                        $ret = $this->encodeModeStructure();
                        break;
                    default:
                        break;
                }
                if ($ret < 0) {
                    return -1;
                }
            }
            return $this->bstream->size();
        } catch (Exception $e) {
            return -1;
        }
    }
}
class QRinput
{
    public $items;
    private $version;
    private $level;
    public static $anTable = array(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1);
    public function __construct($version = 0, $level = QR_ECLEVEL_L)
    {
        if ($version < 0 || QRSPEC_VERSION_MAX < $version || QR_ECLEVEL_H < $level) {
            throw new Exception('Invalid version no');
            return NULL;
        }
        $this->version = $version;
        $this->level = $level;
    }
    public function getVersion()
    {
        return $this->version;
    }
    public function setVersion($version)
    {
        if ($version < 0 || QRSPEC_VERSION_MAX < $version) {
            throw new Exception('Invalid version no');
            return -1;
        }
        $this->version = $version;
        return 0;
    }
    public function getErrorCorrectionLevel()
    {
        return $this->level;
    }
    public function setErrorCorrectionLevel($level)
    {
        if (QR_ECLEVEL_H < $level) {
            throw new Exception('Invalid ECLEVEL');
            return -1;
        }
        $this->level = $level;
        return 0;
    }
    public function appendEntry(QRinputItem $entry)
    {
        $this->items[] = $entry;
    }
    public function append($mode, $size, $data)
    {
        try {
            $entry = new QRinputItem($mode, $size, $data);
            $this->items[] = $entry;
            return 0;
        } catch (Exception $e) {
            return -1;
        }
    }
    public function insertStructuredAppendHeader($size, $index, $parity)
    {
        if (MAX_STRUCTURED_SYMBOLS < $size) {
            throw new Exception('insertStructuredAppendHeader wrong size');
        }
        if ($index <= 0 || MAX_STRUCTURED_SYMBOLS < $index) {
            throw new Exception('insertStructuredAppendHeader wrong index');
        }
        $buf = array($size, $index, $parity);
        try {
            $entry = new QRinputItem(QR_MODE_STRUCTURE, 3, buf);
            array_unshift($this->items, $entry);
            return 0;
        } catch (Exception $e) {
            return -1;
        }
    }
    public function calcParity()
    {
        $parity = 0;
        foreach ($this->items as $item) {
            if ($item->mode != QR_MODE_STRUCTURE) {
                for ($i = $item->size - 1; 0 <= $i; $i--) {
                    $parity ^= $item->data[$i];
                }
            }
        }
        return $parity;
    }
    public static function checkModeNum($size, $data)
    {
        for ($i = 0; $i < $size; $i++) {
            if (ord($data[$i]) < ord('0') || ord('9') < ord($data[$i])) {
                return false;
            }
        }
        return true;
    }
    public static function estimateBitsModeNum($size)
    {
        $w = (int) $size / 3;
        $bits = $w * 10;
        switch ($size - $w * 3) {
            case 1:
                $bits += 4;
                break;
            case 2:
                $bits += 7;
                break;
            default:
                break;
        }
        $size - $w * 3;
        return $bits;
    }
    public static function lookAnTable($c)
    {
        return 127 < $c ? -1 : self::$anTable[$c];
    }
    public static function checkModeAn($size, $data)
    {
        for ($i = 0; $i < $size; $i++) {
            if (self::lookAnTable(ord($data[$i])) == -1) {
                return false;
            }
        }
        return true;
    }
    public static function estimateBitsModeAn($size)
    {
        $w = (int) $size / 2;
        $bits = $w * 11;
        if ($size & 1) {
            $bits += 6;
        }
        return $bits;
    }
    public static function estimateBitsMode8($size)
    {
        return $size * 8;
    }
    public function estimateBitsModeKanji($size)
    {
        return (int) ($size / 2) * 13;
    }
    public static function checkModeKanji($size, $data)
    {
        if ($size & 1) {
            return false;
        }
        for ($i = 0; $i < $size; $i += 2) {
            $val = ord($data[$i]) << 8 | ord($data[$i + 1]);
            if ($val < 33088 || 40956 < $val && $val < 57408 || 60351 < $val) {
                return false;
            }
        }
        return true;
    }
    public static function check($mode, $size, $data)
    {
        if ($size <= 0) {
            return false;
        }
        switch ($mode) {
            case QR_MODE_NUM:
                return self::checkModeNum($size, $data);
                break;
            case QR_MODE_AN:
                return self::checkModeAn($size, $data);
                break;
            case QR_MODE_KANJI:
                return self::checkModeKanji($size, $data);
                break;
            case QR_MODE_8:
                return true;
                break;
            case QR_MODE_STRUCTURE:
                return true;
                break;
            default:
                break;
        }
        return false;
    }
    public function estimateBitStreamSize($version)
    {
        $bits = 0;
        foreach ($this->items as $item) {
            $bits += $item->estimateBitStreamSizeOfEntry($version);
        }
        return $bits;
    }
    public function estimateVersion()
    {
        $version = 0;
        $prev = 0;
        do {
            $prev = $version;
            $bits = $this->estimateBitStreamSize($prev);
            $version = QRspec::getMinimumVersion((int) ($bits + 7) / 8, $this->level);
            if ($version < 0) {
                return -1;
            }
        } while ($prev < $version);
        return $version;
    }
    public static function lengthOfCode($mode, $version, $bits)
    {
        $payload = $bits - 4 - QRspec::lengthIndicator($mode, $version);
        switch ($mode) {
            case QR_MODE_NUM:
                $chunks = (int) $payload / 10;
                $remain = $payload - $chunks * 10;
                $size = $chunks * 3;
                if (7 <= $remain) {
                    $size += 2;
                } else {
                    if (4 <= $remain) {
                        $size += 1;
                    }
                }
                break;
            case QR_MODE_AN:
                $chunks = (int) $payload / 11;
                $remain = $payload - $chunks * 11;
                $size = $chunks * 2;
                if (6 <= $remain) {
                    $size++;
                }
                break;
            case QR_MODE_8:
                $size = (int) $payload / 8;
                break;
            case QR_MODE_KANJI:
                $size = (int) ($payload / 13) * 2;
                break;
            case QR_MODE_STRUCTURE:
                $size = (int) $payload / 8;
                break;
            default:
                $size = 0;
                break;
        }
        $maxsize = QRspec::maximumWords($mode, $version);
        if ($size < 0) {
            $size = 0;
        }
        if ($maxsize < $size) {
            $size = $maxsize;
        }
        return $size;
    }
    public function createBitStream()
    {
        $total = 0;
        foreach ($this->items as $item) {
            $bits = $item->encodeBitStream($this->version);
            if ($bits < 0) {
                return -1;
            }
            $total += $bits;
        }
        return $total;
    }
    public function convertData()
    {
        $ver = $this->estimateVersion();
        if ($this->getVersion() < $ver) {
            $this->setVersion($ver);
        }
        for (;;) {
            $bits = $this->createBitStream();
            if ($bits < 0) {
                return -1;
            }
            $ver = QRspec::getMinimumVersion((int) ($bits + 7) / 8, $this->level);
            if ($ver < 0) {
                throw new Exception('WRONG VERSION');
                return -1;
            } else {
                if ($this->getVersion() < $ver) {
                    $this->setVersion($ver);
                } else {
                    break;
                }
            }
        }
        return 0;
    }
    public function appendPaddingBit(&$bstream)
    {
        $bits = $bstream->size();
        $maxwords = QRspec::getDataLength($this->version, $this->level);
        $maxbits = $maxwords * 8;
        if ($maxbits == $bits) {
            return 0;
        }
        if ($maxbits - $bits < 5) {
            return $bstream->appendNum($maxbits - $bits, 0);
        }
        $bits += 4;
        $words = (int) ($bits + 7) / 8;
        $padding = new QRbitstream();
        $ret = $padding->appendNum($words * 8 - $bits + 4, 0);
        if ($ret < 0) {
            return $ret;
        }
        $padlen = $maxwords - $words;
        if (0 < $padlen) {
            $padbuf = array();
            for ($i = 0; $i < $padlen; $i++) {
                $padbuf[$i] = $i & 1 ? 17 : 236;
            }
            $ret = $padding->appendBytes($padlen, $padbuf);
            if ($ret < 0) {
                return $ret;
            }
        }
        $ret = $bstream->append($padding);
        return $ret;
    }
    public function mergeBitStream()
    {
        if ($this->convertData() < 0) {
            return NULL;
        }
        $bstream = new QRbitstream();
        foreach ($this->items as $item) {
            $ret = $bstream->append($item->bstream);
            if ($ret < 0) {
                return NULL;
            }
        }
        return $bstream;
    }
    public function getBitStream()
    {
        $bstream = $this->mergeBitStream();
        if ($bstream == NULL) {
            return NULL;
        }
        $ret = $this->appendPaddingBit($bstream);
        if ($ret < 0) {
            return NULL;
        }
        return $bstream;
    }
    public function getByteStream()
    {
        $bstream = $this->getBitStream();
        if ($bstream == NULL) {
            return NULL;
        }
        return $bstream->toByte();
    }
}
define('STRUCTURE_HEADER_BITS', 20);
define('MAX_STRUCTURED_SYMBOLS', 16);