// -*- mode:c++; tab-width:2; indent-tabs-mode:nil; c-basic-offset:2 -*-
/*
 *  Decoder.cpp
 *  zxing
 *
 *  Created by Lukas Stabe on 08/02/2012.
 *  Copyright 2012 ZXing authors All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <zxing/aztec/decoder/Decoder.h>
#ifndef NO_ICONV
#include <win32/zxing/iconv.h>
#else
#include <iconv.h>
#endif
#include <iostream>
#include <zxing/FormatException.h>
#include <zxing/common/reedsolomon/ReedSolomonDecoder.h>
#include <zxing/common/reedsolomon/ReedSolomonException.h>
#include <zxing/common/reedsolomon/GenericGF.h>
#include <zxing/common/IllegalArgumentException.h>
#include <zxing/common/DecoderResult.h>

using zxing::aztec::Decoder;
using zxing::DecoderResult;
using zxing::String;
using zxing::BitArray;
using zxing::BitMatrix;
using zxing::Ref;

using std::string;

namespace {
  void add(string& result, char character) {
#ifndef NO_ICONV
    char character2 = character & 0xff;
    char s[] =  {character2};
    char* ss = s;
    size_t sl = sizeof(s);
    char d[4];
    char* ds = d;
    size_t dl = sizeof(d);
    iconv_t ic = iconv_open("UTF-8", "ISO-8859-1");
    iconv(ic, &ss, &sl, &ds, &dl);
    iconv_close(ic);
    d[sizeof(d)-dl] = 0;
    result.append(d);
#else
    result.push_back(character);
#endif
  }

  const int NB_BITS_COMPACT[] = {
    0, 104, 240, 408, 608
  };
        
  const int NB_BITS[] = {
    0, 128, 288, 480, 704, 960, 1248, 1568, 1920, 2304, 2720, 3168, 3648, 4160, 4704, 5280, 5888, 6528,
    7200, 7904, 8640, 9408, 10208, 11040, 11904, 12800, 13728, 14688, 15680, 16704, 17760, 18848, 19968
  };
        
  const int NB_DATABLOCK_COMPACT[] = {
    0, 17, 40, 51, 76
  };
        
  const int NB_DATABLOCK[] = {
    0, 21, 48, 60, 88, 120, 156, 196, 240, 230, 272, 316, 364, 416, 470, 528, 588, 652, 720, 790, 864,
    940, 1020, 920, 992, 1066, 1144, 1224, 1306, 1392, 1480, 1570, 1664
  };
        
  const char* UPPER_TABLE[] = {
    "CTRL_PS", " ", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P",
    "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "CTRL_LL", "CTRL_ML", "CTRL_DL", "CTRL_BS"
  };
        
  const char* LOWER_TABLE[] = {
    "CTRL_PS", " ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p",
    "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "CTRL_US", "CTRL_ML", "CTRL_DL", "CTRL_BS"
  };
        
  const char* MIXED_TABLE[] = {
    "CTRL_PS", " ", "\1", "\2", "\3", "\4", "\5", "\6", "\7", "\b", "\t", "\n",
    "\13", "\f", "\r", "\33", "\34", "\35", "\36", "\37", "@", "\\", "^", "_",
    "`", "|", "~", "\177", "CTRL_LL", "CTRL_UL", "CTRL_PL", "CTRL_BS"
  };
        
  const char* PUNCT_TABLE[] = {
    "", "\r", "\r\n", ". ", ", ", ": ", "!", "\"", "#", "$", "%", "&", "'", "(", ")",
    "*", "+", ",", "-", ".", "/", ":", ";", "<", "=", ">", "?", "[", "]", "{", "}", "CTRL_UL"
  };
        
  const char* DIGIT_TABLE[] = {
    "CTRL_PS", " ", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ",", ".", "CTRL_UL", "CTRL_US"
  };
}
        
Decoder::Table Decoder::getTable(char t) {
  switch (t) {
  case 'L':
    return LOWER;
  case 'P':
    return PUNCT;
  case 'M':
    return MIXED;
  case 'D':
    return DIGIT;
  case 'B':
    return BINARY;
  case 'U':
  default:
    return UPPER;
  }
}
        
const char* Decoder::getCharacter(zxing::aztec::Decoder::Table table, int code) {
  switch (table) {
  case UPPER:
    return UPPER_TABLE[code];
  case LOWER:
    return LOWER_TABLE[code];
  case MIXED:
    return MIXED_TABLE[code];
  case PUNCT:
    return PUNCT_TABLE[code];
  case DIGIT:
    return DIGIT_TABLE[code];
  default:
    return "";
  }
}
        
Decoder::Decoder() {
  // nothing
}
        
Ref<DecoderResult> Decoder::decode(Ref<zxing::aztec::AztecDetectorResult> detectorResult) {
  ddata_ = detectorResult;
            
  // std::printf("getting bits\n");
            
  Ref<BitMatrix> matrix = detectorResult->getBits();
            
  if (!ddata_->isCompact()) {
    // std::printf("removing lines\n");
    matrix = removeDashedLines(ddata_->getBits());
  }
            
  // std::printf("extracting bits\n");
  Ref<BitArray> rawbits = extractBits(matrix);
            
  // std::printf("correcting bits\n");
  Ref<BitArray> aCorrectedBits = correctBits(rawbits);
            
  // std::printf("decoding bits\n");
  Ref<String> result = getEncodedData(aCorrectedBits);
            
  // std::printf("constructing array\n");
  ArrayRef<char> arrayOut(aCorrectedBits->getSize());
  for (int i = 0; i < aCorrectedBits->count(); i++) {
    arrayOut[i] = (char)aCorrectedBits->get(i);
  }
            
  // std::printf("returning\n");
            
  return Ref<DecoderResult>(new DecoderResult(arrayOut, result));
}
        
Ref<String> Decoder::getEncodedData(Ref<zxing::BitArray> correctedBits) {
  int endIndex = codewordSize_ * ddata_->getNBDatablocks() - invertedBitCount_;
  if (endIndex > (int)correctedBits->getSize()) {
    // std::printf("invalid input\n");
    throw FormatException("invalid input data");
  }
            
  Table lastTable = UPPER;
  Table table = UPPER;
  int startIndex = 0;
  std::string result;
  bool end = false;
  bool shift = false;
  bool switchShift = false;
  bool binaryShift = false;
            
  while (!end) {
    // std::printf("decoooooding\n");
                
    if (shift) {
      switchShift = true;
    } else {
      lastTable = table;
    }
                
    int code;
    if (binaryShift) {
      if (endIndex - startIndex < 5) {
        break;
      }
                    
      int length = readCode(correctedBits, startIndex, 5);
      startIndex += 5;
      if (length == 0) {
        if (endIndex - startIndex < 11) {
          break;
        }
                        
        length = readCode(correctedBits, startIndex, 11) + 31;
        startIndex += 11;
      }
      for (int charCount = 0; charCount < length; charCount++) {
        if (endIndex - startIndex < 8) {
          end = true;
          break;
        }
                        
        code = readCode(correctedBits, startIndex, 8);
        add(result, code);
        startIndex += 8;
      }
      binaryShift = false;
    } else {
      if (table == BINARY) {
        if (endIndex - startIndex < 8) {
          break;
        }
        code = readCode(correctedBits, startIndex, 8);
        startIndex += 8;
                        
        add(result, code);
      } else {
        int size = 5;
                        
        if (table == DIGIT) {
          size = 4;
        }
                        
        if (endIndex - startIndex < size) {
          break;
        }
                        
        code = readCode(correctedBits, startIndex, size);
        startIndex += size;
                        
        const char *str = getCharacter(table, code);
        std::string string(str);
        if ((int)string.find("CTRL_") != -1) {
          table = getTable(str[5]);
                            
          if (str[6] == 'S') {
            shift = true;
            if (str[5] == 'B') {
              binaryShift = true;
            }
          }
        } else {
          result.append(string);
        }
                        
      }
    }
                
    if (switchShift) {
      table = lastTable;
      shift = false;
      switchShift = false;
    }
                
                
  }
            
  return Ref<String>(new String(result));
            
}
        
Ref<BitArray> Decoder::correctBits(Ref<zxing::BitArray> rawbits) {
  //return rawbits;
  // std::printf("decoding stuff:%d datablocks in %d layers\n", ddata_->getNBDatablocks(), ddata_->getNBLayers());
            
  Ref<GenericGF> gf = GenericGF::AZTEC_DATA_6;
            
  if (ddata_->getNBLayers() <= 2) {
    codewordSize_ = 6;
    gf = GenericGF::AZTEC_DATA_6;
  } else if (ddata_->getNBLayers() <= 8) {
    codewordSize_ = 8;
    gf = GenericGF::AZTEC_DATA_8;
  } else if (ddata_->getNBLayers() <= 22) {
    codewordSize_ = 10;
    gf = GenericGF::AZTEC_DATA_10;
  } else {
    codewordSize_ = 12;
    gf = GenericGF::AZTEC_DATA_12;
  }
            
  int numDataCodewords = ddata_->getNBDatablocks();
  int numECCodewords;
  int offset;
            
  if (ddata_->isCompact()) {
    offset = NB_BITS_COMPACT[ddata_->getNBLayers()] - numCodewords_ * codewordSize_;
    numECCodewords = NB_DATABLOCK_COMPACT[ddata_->getNBLayers()] - numDataCodewords;
  } else {
    offset = NB_BITS[ddata_->getNBLayers()] - numCodewords_ * codewordSize_;
    numECCodewords = NB_DATABLOCK[ddata_->getNBLayers()] - numDataCodewords;
  }
            
  ArrayRef<int> dataWords(numCodewords_);
            
  for (int i = 0; i < numCodewords_; i++) {
    int flag = 1;
    for (int j = 1; j <= codewordSize_; j++) {
      if (rawbits->get(codewordSize_ * i + codewordSize_ - j + offset)) {
        dataWords[i] += flag;
      }
      flag <<= 1;
    }
                
    //
    //
    //
  }
            
  try {
    ReedSolomonDecoder rsDecoder(gf);
    rsDecoder.decode(dataWords, numECCodewords);
  } catch (ReedSolomonException const& ignored) {
    (void)ignored;
    // std::printf("got reed solomon exception:%s, throwing formatexception\n", rse.what());
    throw FormatException("rs decoding failed");
  } catch (IllegalArgumentException const& iae) {
    (void)iae;
    // std::printf("illegal argument exception: %s", iae.what());
  }
            
  offset = 0;
  invertedBitCount_ = 0;
            
  Ref<BitArray> correctedBits(new BitArray(numDataCodewords * codewordSize_));
  for (int i = 0; i < numDataCodewords; i++) {
                
    bool seriesColor = false;
    int seriesCount = 0;
    int flag = 1 << (codewordSize_ - 1);
                
    for (int j = 0; j < codewordSize_; j++) {
                    
      bool color = (dataWords[i] & flag) == flag;
                    
      if (seriesCount == codewordSize_ - 1) {
                        
        if (color == seriesColor) {
          throw FormatException("bit was not inverted");
        }
                        
        seriesColor = false;
        seriesCount = 0;
        offset++;
        invertedBitCount_++;
                        
      } else {
                        
        if (seriesColor == color) {
          seriesCount++;
        } else {
          seriesCount = 1;
          seriesColor = color;
        }
                        
        if (color) correctedBits->set(i * codewordSize_ + j - offset);
                        
      }
                    
      flag = ((unsigned int)flag) >> 1;
                    
    }
  }
            
  return correctedBits;
}
        
Ref<BitArray> Decoder::extractBits(Ref<zxing::BitMatrix> matrix) {
  std::vector<bool> rawbits;
            
  if (ddata_->isCompact()) {
    if (ddata_->getNBLayers() > 5) { //NB_BITS_COMPACT length
      throw FormatException("data is too long");
    }
    rawbits = std::vector<bool>(NB_BITS_COMPACT[ddata_->getNBLayers()]);
    numCodewords_ = NB_DATABLOCK_COMPACT[ddata_->getNBLayers()];
  } else {
    if (ddata_->getNBLayers() > 33) { //NB_BITS length
      throw FormatException("data is too long");
    }
    rawbits = std::vector<bool>(NB_BITS[ddata_->getNBLayers()]);
    numCodewords_ = NB_DATABLOCK[ddata_->getNBLayers()];
  }
            
  int layer = ddata_->getNBLayers();
  int size = matrix->getHeight();
  int rawbitsOffset = 0;
  int matrixOffset = 0;
            

  while (layer != 0) {
                
    int flip = 0;
    for (int i = 0; i < 2 * size - 4; i++) {
      rawbits[rawbitsOffset + i] = matrix->get(matrixOffset + flip, matrixOffset + i / 2);
      rawbits[rawbitsOffset + 2 * size - 4 + i] = matrix->get(matrixOffset + i / 2, matrixOffset + size - 1 - flip);
      flip = (flip + 1) % 2;
    }
                
    flip = 0;
    for (int i = 2 * size + 1; i > 5; i--) {
      rawbits[rawbitsOffset + 4 * size - 8 + (2 * size - i) + 1] =
        matrix->get(matrixOffset + size - 1 - flip, matrixOffset + i / 2 - 1);
      rawbits[rawbitsOffset + 6 * size - 12 + (2 * size - i) + 1] =
        matrix->get(matrixOffset + i / 2 - 1, matrixOffset + flip);
      flip = (flip + 1) % 2;
    }
                
    matrixOffset += 2;
    rawbitsOffset += 8 * size - 16;
    layer--;
    size -= 4;
                
  }
            
  Ref<BitArray> returnValue(new BitArray(static_cast<int>(rawbits.size())));
  for (int i = 0; i < (int)rawbits.size(); i++) {
    if (static_cast<size_t>(rawbits[i])) returnValue->set(i);
  }
            
  return returnValue;
            
}
        
Ref<BitMatrix> Decoder::removeDashedLines(Ref<zxing::BitMatrix> matrix) {
  int nbDashed = 1 + 2 * ((matrix->getWidth() - 1) / 2 / 16);
  Ref<BitMatrix> newMatrix(new BitMatrix(matrix->getWidth() - nbDashed, matrix->getHeight() - nbDashed));
            
  int nx = 0;
            
  for (int x = 0; x < (int)matrix->getWidth(); x++) {
                
    if ((matrix->getWidth() / 2 - x) % 16 == 0) {
      continue;
    }
                
    int ny = 0;
    for (int y = 0; y < (int)matrix->getHeight(); y++) {
                    
      if ((matrix->getWidth() / 2 - y) % 16 == 0) {
        continue;
      }
                    
      if (matrix->get(x, y)) {
        newMatrix->set(nx, ny);
      }
      ny++;
                    
    }
    nx++;
                
  }
  return newMatrix;
}
        
int Decoder::readCode(Ref<zxing::BitArray> rawbits, int startIndex, int length) {
  int res = 0;
            
  for (int i = startIndex; i < startIndex + length; i++) {
    res <<= 1;
    if (rawbits->get(i)) {
      res ++;
    }
  }
            
  return res;
}
