/*
    SPDX-FileCopyrightText: 2011 Geoffry Song <goffrie@gmail.com>

    SPDX-License-Identifier: MIT
*/

#include "code39barcode.h"
#include <QChar>
#include <QColor>

using namespace Prison;

static QList<bool> barSequence(const char* str) {
    Q_ASSERT(strlen(str)==9); // this is a internal helper tool, only called with fixed strings in here, all 9 chars long
    QList<bool> ret;
    for(int i = 0 ; i < 9 ; i++) {
        ret.append(str[i] == '1');
        Q_ASSERT(str[i] == '0' || str[i] == '1');
    }
    return ret;
}

static QList<bool> sequenceForChar(ushort c) {
    switch(QChar::toUpper(c)) {
        case '0': return barSequence("000110100");
        case '1': return barSequence("100100001");
        case '2': return barSequence("001100001");
        case '3': return barSequence("101100000");
        case '4': return barSequence("000110001");
        case '5': return barSequence("100110000");
        case '6': return barSequence("001110000");
        case '7': return barSequence("000100101");
        case '8': return barSequence("100100100");
        case '9': return barSequence("001100100");
        case 'A': return barSequence("100001001");
        case 'B': return barSequence("001001001");
        case 'C': return barSequence("101001000");
        case 'D': return barSequence("000011001");
        case 'E': return barSequence("100011000");
        case 'F': return barSequence("001011000");
        case 'G': return barSequence("000001101");
        case 'H': return barSequence("100001100");
        case 'I': return barSequence("001001100");
        case 'J': return barSequence("000011100");
        case 'K': return barSequence("100000011");
        case 'L': return barSequence("001000011");
        case 'M': return barSequence("101000010");
        case 'N': return barSequence("000010011");
        case 'O': return barSequence("100010010");
        case 'P': return barSequence("001010010");
        case 'Q': return barSequence("000000111");
        case 'R': return barSequence("100000110");
        case 'S': return barSequence("001000110");
        case 'T': return barSequence("000010110");
        case 'U': return barSequence("110000001");
        case 'V': return barSequence("011000001");
        case 'W': return barSequence("111000000");
        case 'X': return barSequence("010010001");
        case 'Y': return barSequence("110010000");
        case 'Z': return barSequence("011010000");
        case '-': return barSequence("010000101");
        case '.': return barSequence("110000100");
        case ' ': return barSequence("011000100");
        case '$': return barSequence("010101000");
        case '/': return barSequence("010100010");
        case '+': return barSequence("010001010");
        case '%': return barSequence("000101010");
        default: return QList<bool>(); // unknown character
    }
}

Code39Barcode::Code39Barcode() : AbstractBarcode(AbstractBarcode::OneDimension) {}
Code39Barcode::~Code39Barcode() = default;

QImage Code39Barcode::paintImage(const QSizeF& size)
{
  Q_UNUSED(size);
  QList<bool> barcode;
  // convert text into sequences of wide/narrow bars
  {
    // the guard sequence that goes on each end
    const QList<bool> endSequence = barSequence("010010100");
    barcode += endSequence;
    barcode += false;
    // translate the string
    const QString str = data();
    for(int i = 0 ; i < str.size(); i++) {
      QList<bool> b = sequenceForChar(str.at(i).unicode());
      if(!b.empty()) {
        barcode += b;
        barcode += false; // add a narrow space between each character
      }
    }
    // ending guard
    barcode += endSequence;
  }

  /*
   calculate integer bar widths that fit inside `size'
   each character has 6 narrow bars and 3 wide bars and there is a narrow bar between characters
   restrictions:
     *) smallWidth * 2 <= largeWidth <= smallWidth * 3
        - in other words, the ratio largeWidth:smallWidth is between 3:1 and 2:1
     *) wide * largeWidth + narrow * smallWidth <= size.width()
        - the barcode has to fit within the given size
   */
  const int wide = barcode.count(true);
  const int narrow = barcode.count(false);
  // wide bar width
  const int largeWidth = 2;
  // narrow bar width
  const int smallWidth = 1;
  Q_ASSERT(largeWidth > smallWidth);

  // one line of the result image
  QVector<QRgb> line;
  line.reserve(wide * largeWidth + narrow * smallWidth);
  for(int i = 0 ; i < barcode.size() ; i++) {
    const QRgb color = (((i&1) == 0) ? foregroundColor() : backgroundColor()).rgba(); // alternate between foreground and background color
    const int width = barcode.at(i) ? largeWidth : smallWidth;
    for(int j = 0 ; j < width ; j++) {
      line.append(color);
    }
  }

  // build the complete barcode
  QImage ret(line.size(), 1, QImage::Format_ARGB32);
  memcpy(ret.scanLine(0), line.data(), line.size() * sizeof(QRgb));
  return ret;
}

