/*
    Copyright (c) 2018 Volker Krause <vkrause@kde.org>

    Permission is hereby granted, free of charge, to any person
    obtaining a copy of this software and associated documentation
    files (the "Software"), to deal in the Software without
    restriction, including without limitation the rights to use,
    copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following
    conditions:

    The above copyright notice and this permission notice shall be
    included in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    OTHER DEALINGS IN THE SOFTWARE.

*/

#include "code128barcode.h"
#include "bitvector_p.h"
#include "prison_debug.h"

#include <QImage>
#include <QPainter>

using namespace Prison;

enum {
    SymbolSize = 11,
    StopPatternSize = 13,
    StopPattern = 108,
    QuietZone = 10,
};

enum CodeSet : uint8_t {
    CodeSetA = 0,
    CodeSetB = 1,
    CodeSetC = 2,
    CodeSetUnknown = 3
};

enum CodeSetOp : uint8_t {
    None = 255,
    StartA = 103,
    StartB = 104,
    StartC = 105,
    Shift = 98,
    LatchA = 101,
    LatchB = 100,
    LatchC = 99
};

Code128Barcode::Code128Barcode() = default;
Code128Barcode::~Code128Barcode() = default;

QImage Code128Barcode::paintImage(const QSizeF& size)
{
    if (size.height() < 1)
        return {};

    const auto bits = encode(data().toLatin1());
    const auto width = bits.size() + 2 * QuietZone;
    setMinimumSize(QSizeF(width, 10));

    const auto moduleSize = size.width() / width;
    if (moduleSize < 1) // too small for this
        return {};

    QImage img(moduleSize * width, size.height(), QImage::Format_ARGB32);
    img.fill(backgroundColor());
    QPainter p(&img);
    for (int i = 0; i < bits.size(); ++i) {
        if (bits.at(i))
            p.fillRect(QRectF((QuietZone + i) * moduleSize, 0, moduleSize, img.height()), foregroundColor());
    }

    return img;
}

// Code 128 symbol table
// ### this is the perfect use-case for binary literals (as the binary pattern
// corresponds to the line pattern), adjust this once KF5 moves to C++14
static const uint16_t code128_symbols[] =
{
    1740, // 0b11011001100 // 0
    1644, // 0b11001101100
    1638, // 0b11001100110
    1176, // 0b10010011000
    1164, // 0b10010001100
    1100, // 0b10001001100
    1224, // 0b10011001000
    1220, // 0b10011000100
    1124, // 0b10001100100
    1608, // 0b11001001000
    1604, // 0b11001000100 // 10
    1572, // 0b11000100100
    1436, // 0b10110011100
    1244, // 0b10011011100
    1230, // 0b10011001110
    1484, // 0b10111001100
    1260, // 0b10011101100
    1254, // 0b10011100110
    1650, // 0b11001110010
    1628, // 0b11001011100
    1614, // 0b11001001110 // 20
    1764, // 0b11011100100
    1652, // 0b11001110100
    1902, // 0b11101101110
    1868, // 0b11101001100
    1836, // 0b11100101100
    1830, // 0b11100100110
    1892, // 0b11101100100
    1844, // 0b11100110100
    1842, // 0b11100110010
    1752, // 0b11011011000 // 30
    1734, // 0b11011000110
    1590, // 0b11000110110
    1304, // 0b10100011000
    1112, // 0b10001011000
    1094, // 0b10001000110
    1416, // 0b10110001000
    1128, // 0b10001101000
    1122, // 0b10001100010
    1672, // 0b11010001000
    1576, // 0b11000101000 // 40
    1570, // 0b11000100010
    1464, // 0b10110111000
    1422, // 0b10110001110
    1134, // 0b10001101110
    1496, // 0b10111011000
    1478, // 0b10111000110
    1142, // 0b10001110110
    1910, // 0b11101110110
    1678, // 0b11010001110
    1582, // 0b11000101110 // 50
    1768, // 0b11011101000
    1762, // 0b11011100010
    1774, // 0b11011101110
    1880, // 0b11101011000
    1862, // 0b11101000110
    1814, // 0b11100010110
    1896, // 0b11101101000
    1890, // 0b11101100010
    1818, // 0b11100011010
    1914, // 0b11101111010 // 60
    1602, // 0b11001000010
    1930, // 0b11110001010
    1328, // 0b10100110000
    1292, // 0b10100001100
    1200, // 0b10010110000
    1158, // 0b10010000110
    1068, // 0b10000101100
    1062, // 0b10000100110
    1424, // 0b10110010000
    1412, // 0b10110000100 // 70
    1232, // 0b10011010000
    1218, // 0b10011000010
    1076, // 0b10000110100
    1074, // 0b10000110010
    1554, // 0b11000010010
    1616, // 0b11001010000
    1978, // 0b11110111010
    1556, // 0b11000010100
    1146, // 0b10001111010
    1340, // 0b10100111100 // 80
    1212, // 0b10010111100
    1182, // 0b10010011110
    1508, // 0b10111100100
    1268, // 0b10011110100
    1266, // 0b10011110010
    1956, // 0b11110100100
    1940, // 0b11110010100
    1938, // 0b11110010010
    1758, // 0b11011011110
    1782, // 0b11011110110 // 90
    1974, // 0b11110110110
    1400, // 0b10101111000
    1310, // 0b10100011110
    1118, // 0b10001011110
    1512, // 0b10111101000
    1506, // 0b10111100010
    1960, // 0b11110101000
    1954, // 0b11110100010
    1502, // 0b10111011110
    1518, // 0b10111101110 // 100
    1886, // 0b11101011110
    1966, // 0b11110101110
    1668, // 0b11010000100
    1680, // 0b11010010000
    1692, // 0b11010011100
    1594, // 0b11000111010
    1720, // 0b11010111000
    6379  // 0b1100011101011
};

static uint8_t symbolForCharacter(const QByteArray &data, int index, CodeSet set)
{
    const auto c1 = data.at(index);
    switch (set) {
        case CodeSetA:
            return (c1 < ' ') ? c1 + 64 : c1 - ' ';
        case CodeSetB:
            return c1 - ' ';
        case CodeSetC:
        {
            const auto c2 = data.at(index + 1);
            return ((c1 - '0') * 10) + c2 - '0';
        }
        case CodeSetUnknown:
            Q_UNREACHABLE();
    }

    Q_UNREACHABLE();
    return {};
}

struct CodeSetChange { CodeSet set; CodeSetOp symbol; };

static bool isInCodeSetA(char c)
{
    return c <= 95;
}

static bool isInCodeSetB(char c)
{
    // ### this does not consider FNC4 high byte encoding
    return c >= 32;
}

static CodeSetChange opForData(const QByteArray &data, int index, CodeSet currentSet)
{
    // determine if Code C makes sense at this point
    int codeC = 0;
    for (int i = index; i < data.size(); ++i, ++codeC) {
        if (data.at(i) < '0' || data.at(i) > '9')
            break;
    }
    if (currentSet == CodeSetC && codeC >= 2) { // already in C
        return { CodeSetC, None };
    }
    if (codeC >= 6 // that's always good enough
        || (index == 0 && codeC >= 4) // beginning of data
        || (index + codeC == data.size() && codeC >= 4) // end of data
        || (codeC == data.size() && codeC == 2) // 2 ...
        || (codeC == data.size() && codeC == 4)) // ... or 4 as the entire data
    {
        return currentSet == CodeSetUnknown ? CodeSetChange{ CodeSetC, StartC } : CodeSetChange{ CodeSetC, LatchC };
    }

    // if we are in Code A or Code B, check if we need to switch for the next char
    // this is a shortcut to prevent the below more extensive search from making this O(n²) in the common case
    if ((currentSet == CodeSetA && isInCodeSetA(data.at(index))) || (currentSet == CodeSetB && isInCodeSetB(data.at(index)))) {
        return { currentSet, None };
    }

    // we need to switch to A or B, select which one, and select whether to use start, shift or latch
    const auto nextA = isInCodeSetA(data.at(index));
    const auto nextB = isInCodeSetB(data.at(index));

    // count how many following characters we could encode in A or B
    int countA = 0;
    for (int i = index + 1; i < data.size(); ++i, ++countA) {
        if (!isInCodeSetA(data.at(i)))
            break;
    }
    int countB = 0;
    for (int i = index + 1; i < data.size(); ++i, ++countB) {
        if (!isInCodeSetB(data.at(i)))
            break;
    }

    // select how we want to switch to Code A or Code B, biased to B as that's the more useful one in general
    switch (currentSet) {
        case CodeSetUnknown:
            // if we are at the start, take whichever code will get us further, or the only one that works
            if (nextA && nextB) {
                return countA > countB ? CodeSetChange{ CodeSetA, StartA } : CodeSetChange{ CodeSetB, StartB };
            }
            return nextA ? CodeSetChange{ CodeSetA, StartA } : CodeSetChange{ CodeSetB, StartB };
        case CodeSetC:
            // same for Code C
            if (nextA && nextB) {
                return countA > countB ? CodeSetChange{ CodeSetA, LatchA } : CodeSetChange{ CodeSetB, LatchB };
            }
            return nextA ? CodeSetChange{ CodeSetA, LatchA } : CodeSetChange{ CodeSetB, LatchB };
        case CodeSetA:
            // switch or latch to B?
            return CodeSetChange{ CodeSetB, countB >= countA ? LatchB : Shift };
        case CodeSetB:
            // switch or latch to A?
            return CodeSetChange{ CodeSetA, countA > countB ? LatchA : Shift };
    }

    Q_UNREACHABLE();
    return CodeSetChange{ currentSet, None };
}

BitVector Code128Barcode::encode(const QByteArray& data) const
{
    BitVector v;
    if (data.isEmpty()) {
        return v;
    }

    // determine code set for start
    const auto op = opForData(data, 0, CodeSetUnknown);
    auto currentSet = op.set;

    // write start code
    qCDebug(Log) << "start symbol:" << op.symbol << code128_symbols[op.symbol];
    v.appendMSB(code128_symbols[op.symbol], SymbolSize);

    uint32_t checksum = op.symbol;
    uint32_t checksumWeight = 1;

    for (int i = 0; i < data.size(); i += currentSet == CodeSetC ? 2 : 1) {
        if (static_cast<uint8_t>(data.at(i)) > 127) { // FNC4 encoding not implemented yet
            continue;
        }

        // perform code switch if needed
        const auto op = opForData(data, i, currentSet);
        if (op.symbol != None) {
            qCDebug(Log) << "op symbol:" << op.symbol << code128_symbols[op.symbol];
            v.appendMSB(code128_symbols[op.symbol], SymbolSize);
            checksum += op.symbol * checksumWeight++;
        }

        // encode current symbol
        const auto symbol = symbolForCharacter(data, i, op.set);
        qCDebug(Log) << "data symbol:" << symbol << code128_symbols[symbol];
        v.appendMSB(code128_symbols[symbol], SymbolSize);
        checksum += symbol * checksumWeight++;

        // update current code set
        if (op.symbol != Shift) {
            currentSet = op.set;
        }
    }

    // encode checksum
    qCDebug(Log) << "checksum:" << checksum << code128_symbols[checksum % 103];
    v.appendMSB(code128_symbols[checksum % 103], SymbolSize);

    // add stop pattern
    v.appendMSB(code128_symbols[StopPattern], StopPatternSize);
    return v;
}
