// Copyright 2014 PDFium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
// Original code is licensed as follows:
/*
 * Copyright 2008 ZXing authors
 *
 * 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 "../barcode.h"
#include "../BC_Reader.h"
#include "../common/BC_CommonBitArray.h"
#include "BC_OneDReader.h"
#include "BC_OnedCode128Reader.h"
const int32_t CBC_OnedCode128Reader::CODE_PATTERNS[107][7] =  {
    {2, 1, 2, 2, 2, 2, 0},
    {2, 2, 2, 1, 2, 2, 0},
    {2, 2, 2, 2, 2, 1, 0},
    {1, 2, 1, 2, 2, 3, 0},
    {1, 2, 1, 3, 2, 2, 0},
    {1, 3, 1, 2, 2, 2, 0},
    {1, 2, 2, 2, 1, 3, 0},
    {1, 2, 2, 3, 1, 2, 0},
    {1, 3, 2, 2, 1, 2, 0},
    {2, 2, 1, 2, 1, 3, 0},
    {2, 2, 1, 3, 1, 2, 0},
    {2, 3, 1, 2, 1, 2, 0},
    {1, 1, 2, 2, 3, 2, 0},
    {1, 2, 2, 1, 3, 2, 0},
    {1, 2, 2, 2, 3, 1, 0},
    {1, 1, 3, 2, 2, 2, 0},
    {1, 2, 3, 1, 2, 2, 0},
    {1, 2, 3, 2, 2, 1, 0},
    {2, 2, 3, 2, 1, 1, 0},
    {2, 2, 1, 1, 3, 2, 0},
    {2, 2, 1, 2, 3, 1, 0},
    {2, 1, 3, 2, 1, 2, 0},
    {2, 2, 3, 1, 1, 2, 0},
    {3, 1, 2, 1, 3, 1, 0},
    {3, 1, 1, 2, 2, 2, 0},
    {3, 2, 1, 1, 2, 2, 0},
    {3, 2, 1, 2, 2, 1, 0},
    {3, 1, 2, 2, 1, 2, 0},
    {3, 2, 2, 1, 1, 2, 0},
    {3, 2, 2, 2, 1, 1, 0},
    {2, 1, 2, 1, 2, 3, 0},
    {2, 1, 2, 3, 2, 1, 0},
    {2, 3, 2, 1, 2, 1, 0},
    {1, 1, 1, 3, 2, 3, 0},
    {1, 3, 1, 1, 2, 3, 0},
    {1, 3, 1, 3, 2, 1, 0},
    {1, 1, 2, 3, 1, 3, 0},
    {1, 3, 2, 1, 1, 3, 0},
    {1, 3, 2, 3, 1, 1, 0},
    {2, 1, 1, 3, 1, 3, 0},
    {2, 3, 1, 1, 1, 3, 0},
    {2, 3, 1, 3, 1, 1, 0},
    {1, 1, 2, 1, 3, 3, 0},
    {1, 1, 2, 3, 3, 1, 0},
    {1, 3, 2, 1, 3, 1, 0},
    {1, 1, 3, 1, 2, 3, 0},
    {1, 1, 3, 3, 2, 1, 0},
    {1, 3, 3, 1, 2, 1, 0},
    {3, 1, 3, 1, 2, 1, 0},
    {2, 1, 1, 3, 3, 1, 0},
    {2, 3, 1, 1, 3, 1, 0},
    {2, 1, 3, 1, 1, 3, 0},
    {2, 1, 3, 3, 1, 1, 0},
    {2, 1, 3, 1, 3, 1, 0},
    {3, 1, 1, 1, 2, 3, 0},
    {3, 1, 1, 3, 2, 1, 0},
    {3, 3, 1, 1, 2, 1, 0},
    {3, 1, 2, 1, 1, 3, 0},
    {3, 1, 2, 3, 1, 1, 0},
    {3, 3, 2, 1, 1, 1, 0},
    {3, 1, 4, 1, 1, 1, 0},
    {2, 2, 1, 4, 1, 1, 0},
    {4, 3, 1, 1, 1, 1, 0},
    {1, 1, 1, 2, 2, 4, 0},
    {1, 1, 1, 4, 2, 2, 0},
    {1, 2, 1, 1, 2, 4, 0},
    {1, 2, 1, 4, 2, 1, 0},
    {1, 4, 1, 1, 2, 2, 0},
    {1, 4, 1, 2, 2, 1, 0},
    {1, 1, 2, 2, 1, 4, 0},
    {1, 1, 2, 4, 1, 2, 0},
    {1, 2, 2, 1, 1, 4, 0},
    {1, 2, 2, 4, 1, 1, 0},
    {1, 4, 2, 1, 1, 2, 0},
    {1, 4, 2, 2, 1, 1, 0},
    {2, 4, 1, 2, 1, 1, 0},
    {2, 2, 1, 1, 1, 4, 0},
    {4, 1, 3, 1, 1, 1, 0},
    {2, 4, 1, 1, 1, 2, 0},
    {1, 3, 4, 1, 1, 1, 0},
    {1, 1, 1, 2, 4, 2, 0},
    {1, 2, 1, 1, 4, 2, 0},
    {1, 2, 1, 2, 4, 1, 0},
    {1, 1, 4, 2, 1, 2, 0},
    {1, 2, 4, 1, 1, 2, 0},
    {1, 2, 4, 2, 1, 1, 0},
    {4, 1, 1, 2, 1, 2, 0},
    {4, 2, 1, 1, 1, 2, 0},
    {4, 2, 1, 2, 1, 1, 0},
    {2, 1, 2, 1, 4, 1, 0},
    {2, 1, 4, 1, 2, 1, 0},
    {4, 1, 2, 1, 2, 1, 0},
    {1, 1, 1, 1, 4, 3, 0},
    {1, 1, 1, 3, 4, 1, 0},
    {1, 3, 1, 1, 4, 1, 0},
    {1, 1, 4, 1, 1, 3, 0},
    {1, 1, 4, 3, 1, 1, 0},
    {4, 1, 1, 1, 1, 3, 0},
    {4, 1, 1, 3, 1, 1, 0},
    {1, 1, 3, 1, 4, 1, 0},
    {1, 1, 4, 1, 3, 1, 0},
    {3, 1, 1, 1, 4, 1, 0},
    {4, 1, 1, 1, 3, 1, 0},
    {2, 1, 1, 4, 1, 2, 0},
    {2, 1, 1, 2, 1, 4, 0},
    {2, 1, 1, 2, 3, 2, 0},
    {2, 3, 3, 1, 1, 1, 2}
};
const int32_t CBC_OnedCode128Reader::MAX_AVG_VARIANCE = (int32_t) (256 * 0.25f);
const int32_t CBC_OnedCode128Reader::MAX_INDIVIDUAL_VARIANCE = (int32_t) (256 * 0.7f);
const int32_t CBC_OnedCode128Reader::CODE_SHIFT = 98;
const int32_t CBC_OnedCode128Reader::CODE_CODE_C = 99;
const int32_t CBC_OnedCode128Reader::CODE_CODE_B = 100;
const int32_t CBC_OnedCode128Reader::CODE_CODE_A = 101;
const int32_t CBC_OnedCode128Reader::CODE_FNC_1 = 102;
const int32_t CBC_OnedCode128Reader::CODE_FNC_2 = 97;
const int32_t CBC_OnedCode128Reader::CODE_FNC_3 = 96;
const int32_t CBC_OnedCode128Reader::CODE_FNC_4_A = 101;
const int32_t CBC_OnedCode128Reader::CODE_FNC_4_B = 100;
const int32_t CBC_OnedCode128Reader::CODE_START_A = 103;
const int32_t CBC_OnedCode128Reader::CODE_START_B = 104;
const int32_t CBC_OnedCode128Reader::CODE_START_C = 105;
const int32_t CBC_OnedCode128Reader::CODE_STOP = 106;
CBC_OnedCode128Reader::CBC_OnedCode128Reader()
{
}
CBC_OnedCode128Reader::~CBC_OnedCode128Reader()
{
}
CFX_Int32Array *CBC_OnedCode128Reader::FindStartPattern(CBC_CommonBitArray *row, int32_t &e)
{
    int32_t width = row->GetSize();
    int32_t rowOffset = 0;
    while (rowOffset < width) {
        if (row->Get(rowOffset)) {
            break;
        }
        rowOffset++;
    }
    int32_t counterPosition = 0;
    CFX_Int32Array counters;
    counters.SetSize(6);
    int32_t patternStart = rowOffset;
    FX_BOOL isWhite = FALSE;
    int32_t patternLength = counters.GetSize();
    for (int32_t i = rowOffset; i < width; i++) {
        FX_BOOL pixel = row->Get(i);
        if (pixel ^ isWhite) {
            counters[counterPosition]++;
        } else {
            if (counterPosition == patternLength - 1) {
                int32_t bestVariance = MAX_AVG_VARIANCE;
                int32_t bestMatch = -1;
                for (int32_t startCode = CODE_START_A; startCode <= CODE_START_C; startCode++) {
                    int32_t variance = PatternMatchVariance(&counters, &CODE_PATTERNS[startCode][0], MAX_INDIVIDUAL_VARIANCE);
                    if (variance < bestVariance) {
                        bestVariance = variance;
                        bestMatch = startCode;
                    }
                }
                if (bestMatch >= 0) {
                    FX_BOOL btemp2 = row->IsRange(FX_MAX(0, patternStart - (i - patternStart) / 2), patternStart, FALSE, e);
                    BC_EXCEPTION_CHECK_ReturnValue(e, NULL);
                    if (btemp2) {
                        CFX_Int32Array *result = FX_NEW CFX_Int32Array;
                        result->SetSize(3);
                        (*result)[0] = patternStart;
                        (*result)[1] = i;
                        (*result)[2] = bestMatch;
                        return result;
                    }
                }
                patternStart += counters[0] + counters[1];
                for (int32_t y = 2; y < patternLength; y++) {
                    counters[y - 2] = counters[y];
                }
                counters[patternLength - 2] = 0;
                counters[patternLength - 1] = 0;
                counterPosition--;
            } else {
                counterPosition++;
            }
            counters[counterPosition] = 1;
            isWhite = !isWhite;
        }
    }
    e = BCExceptionNotFound;
    return NULL;
}
int32_t CBC_OnedCode128Reader::DecodeCode(CBC_CommonBitArray *row, CFX_Int32Array *counters, int32_t rowOffset, int32_t &e)
{
    RecordPattern(row, rowOffset, counters, e);
    BC_EXCEPTION_CHECK_ReturnValue(e, 0);
    int32_t bestVariance = MAX_AVG_VARIANCE;
    int32_t bestMatch = -1;
    for (int32_t d = 0; d < 107; d++) {
        int32_t variance = PatternMatchVariance(counters, &CODE_PATTERNS[d][0], MAX_INDIVIDUAL_VARIANCE);
        if (variance < bestVariance) {
            bestVariance = variance;
            bestMatch = d;
        }
    }
    if (bestMatch >= 0) {
        return bestMatch;
    } else {
        e = BCExceptionNotFound;
        return 0;
    }
    return 0;
}
CFX_ByteString CBC_OnedCode128Reader::DecodeRow(int32_t rowNumber, CBC_CommonBitArray *row, int32_t hints, int32_t &e)
{
    CFX_Int32Array *startPatternInfo = FindStartPattern(row, e);
    BC_EXCEPTION_CHECK_ReturnValue(e, "");
    int32_t startCode = (*startPatternInfo)[2];
    int32_t codeSet;
    switch (startCode) {
        case 103:
            codeSet = CODE_CODE_A;
            break;
        case 104:
            codeSet = CODE_CODE_B;
            break;
        case 105:
            codeSet = CODE_CODE_C;
            break;
        default:
            if(startPatternInfo != NULL) {
                startPatternInfo->RemoveAll();
                delete startPatternInfo;
                startPatternInfo = NULL;
            }
            e = BCExceptionFormatException;
            return "";
    }
    FX_BOOL done = FALSE;
    FX_BOOL isNextShifted = FALSE;
    CFX_ByteString result;
    int32_t lastStart = (*startPatternInfo)[0];
    int32_t nextStart = (*startPatternInfo)[1];
    if(startPatternInfo != NULL) {
        startPatternInfo->RemoveAll();
        delete startPatternInfo;
        startPatternInfo = NULL;
    }
    CFX_Int32Array counters;
    counters.SetSize(6);
    int32_t lastCode = 0;
    int32_t code = 0;
    int32_t checksumTotal = startCode;
    int32_t multiplier = 0;
    FX_BOOL lastCharacterWasPrintable = TRUE;
    while (!done) {
        FX_BOOL unshift = isNextShifted;
        isNextShifted = FALSE;
        lastCode = code;
        code = DecodeCode(row, &counters, nextStart, e);
        BC_EXCEPTION_CHECK_ReturnValue(e, "");
        if (code != CODE_STOP) {
            lastCharacterWasPrintable = TRUE;
        }
        if (code != CODE_STOP) {
            multiplier++;
            checksumTotal += multiplier * code;
        }
        lastStart = nextStart;
        for (int32_t i = 0; i < counters.GetSize(); i++) {
            nextStart += counters[i];
        }
        switch (code) {
            case 103:
            case 104:
            case 105:
                e = BCExceptionFormatException;
                return "";
        }
        switch (codeSet) {
            case 101:
                if (code < 64) {
                    result += (FX_CHAR) (' ' + code);
                } else if (code < 96) {
                    result += (FX_CHAR) (code - 64);
                } else {
                    if (code != CODE_STOP) {
                        lastCharacterWasPrintable = FALSE;
                    }
                    switch (code) {
                        case 102:
                        case 97:
                        case 96:
                        case 101:
                            break;
                        case 98:
                            isNextShifted = TRUE;
                            codeSet = CODE_CODE_B;
                            break;
                        case 100:
                            codeSet = CODE_CODE_B;
                            break;
                        case 99:
                            codeSet = CODE_CODE_C;
                            break;
                        case 106:
                            done = TRUE;
                            break;
                    }
                }
                break;
            case 100:
                if (code < 96) {
                    result += (FX_CHAR) (' ' + code);
                } else {
                    if (code != CODE_STOP) {
                        lastCharacterWasPrintable = FALSE;
                    }
                    switch (code) {
                        case 102:
                        case 97:
                        case 96:
                        case 100:
                            break;
                        case 98:
                            isNextShifted = TRUE;
                            codeSet = CODE_CODE_A;
                            break;
                        case 101:
                            codeSet = CODE_CODE_A;
                            break;
                        case 99:
                            codeSet = CODE_CODE_C;
                            break;
                        case 106:
                            done = TRUE;
                            break;
                    }
                }
                break;
            case 99:
                if (code < 100) {
                    if (code < 10) {
                        result += '0';
                    }
                    FX_CHAR temp[128];
#if defined(_FX_WINAPI_PARTITION_APP_)
                    sprintf_s(temp, 128, "%d", code);
#else
                    sprintf(temp, "%d", code);
#endif
                    result += temp;
                } else {
                    if (code != CODE_STOP) {
                        lastCharacterWasPrintable = FALSE;
                    }
                    switch (code) {
                        case 102:
                            break;
                        case 101:
                            codeSet = CODE_CODE_A;
                            break;
                        case 100:
                            codeSet = CODE_CODE_B;
                            break;
                        case 106:
                            done = TRUE;
                            break;
                    }
                }
                break;
        }
        if (unshift) {
            codeSet = codeSet == CODE_CODE_A ? CODE_CODE_B : CODE_CODE_A;
        }
    }
    int32_t width = row->GetSize();
    while (nextStart < width && row->Get(nextStart)) {
        nextStart++;
    }
    FX_BOOL boolT1 = row->IsRange(nextStart, FX_MIN(width, nextStart + (nextStart - lastStart) / 2), FALSE, e);
    BC_EXCEPTION_CHECK_ReturnValue(e, "");
    if (!boolT1) {
        e = BCExceptionNotFound;
        return "";
    }
    checksumTotal -= multiplier * lastCode;
    if (checksumTotal % 103 != lastCode) {
        e = BCExceptionChecksumException;
        return "";
    }
    int32_t resultLength = result.GetLength();
    if (resultLength > 0 && lastCharacterWasPrintable) {
        if (codeSet == CODE_CODE_C) {
            result = result.Mid(0, result.GetLength() - 2);
        } else {
            result = result.Mid(0, result.GetLength() - 1);
        }
    }
    if (result.GetLength() == 0) {
        e = BCExceptionFormatException;
        return "";
    }
    return result;
}