/*
 * Copyright (C) 2007 - 2008 Lars Kuhtz, ExactCODE GmbH Germany.
 * Copyright (C) 2010 - 2019 René Rebe, ExactCODE GmbH Germany.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2. A copy of the GNU General
 * Public License can be found in the file LICENSE.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANT-
 * ABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 *
 * Alternatively, commercial licensing options are available from the
 * copyright holder ExactCODE GmbH Germany.
 */

#ifndef _CODE39_HH_
#define _CODE39_HH_

#include "scanner_utils.hh"

namespace BarDecode
{
    struct code39_t
    {
        code39_t();

        // NOTE: if the first char is SHIFT, then a further barcode is expected to
        // be concatenated.

        // Character set: A-Z0-9-. $/+% and DELIMITER

        // Extended set: full ascii (by use of shift characters)
        // Usage of extended set is not encoded, but needs to be requested explicitly.
        // The code is first scaned normaly. The result is translated afterwards
        // into extend encoding

        // Usage of checksum is not encoded, but needs to be requested explicitly.
        // If requested this is performed after completely having scanned the code.


        // Decoding is based on a binary encoding of the width of bars (rather than
        // modules). Since each symbol has 9 bars we need a table of size 512.
        // Otherwith we would have needed size 2^(13 modules - 2 constant modules) = 2048.
        // ((Maybe we could safe even a bit more by directly encoding 3 of 9 ???)

        template<class TIT>
        scanner_result_t scan(TIT& start, TIT end, pos_t x, pos_t y, psize_t) const;

        template<class TIT>
        scanner_result_t reverse_scan(TIT& start, TIT end, pos_t x, pos_t y, psize_t) const;

        bool check_bar_vector(const bar_vector_t& b,psize_t old_psize = 0) const;
        module_word_t get_key(const bar_vector_t& b) const;
        module_word_t reverse_get_key(const bar_vector_t& b) const;
        
        template<class TIT>
        bool expect_n(TIT& start, TIT end, psize_t old_psize) const;

        static const char DELIMITER  = 254;
        static const char no_entry = 255;

        static const usize_t min_quiet_usize = 5;
        //static const usize_t min_quiet_usize = 10;
        static const usize_t min_quiet_usize_right = 5;

        DECLARE_TABLE(table,512);
        DECLARE_TABLE(aux,128);
    };

    inline code39_t::code39_t()
    {
        INIT_TABLE(table,512,no_entry);
        PUT_IN_TABLE(table,0x34, '0');
        PUT_IN_TABLE(table,0x121, '1');
        PUT_IN_TABLE(table,0x61, '2');
        PUT_IN_TABLE(table,0x160, '3');
        PUT_IN_TABLE(table,0x31, '4');
        PUT_IN_TABLE(table,0x130, '5');
        PUT_IN_TABLE(table,0x70, '6');
        PUT_IN_TABLE(table,0x25, '7');
        PUT_IN_TABLE(table,0x124, '8');
        PUT_IN_TABLE(table,0x64, '9');
        PUT_IN_TABLE(table,0x109, 'A');
        PUT_IN_TABLE(table,0x49, 'B');
        PUT_IN_TABLE(table,0x148, 'C');
        PUT_IN_TABLE(table,0x19, 'D');
        PUT_IN_TABLE(table,0x118, 'E');
        PUT_IN_TABLE(table,0x58, 'F');
        PUT_IN_TABLE(table,0xD, 'G');
        PUT_IN_TABLE(table,0x10C, 'H');
        PUT_IN_TABLE(table,0x4C, 'I');
        PUT_IN_TABLE(table,0x1C, 'J');
        PUT_IN_TABLE(table,0x103, 'K');
        PUT_IN_TABLE(table,0x43, 'L');
        PUT_IN_TABLE(table,0x142, 'M');
        PUT_IN_TABLE(table,0x13, 'N');
        PUT_IN_TABLE(table,0x112, 'O');
        PUT_IN_TABLE(table,0x52, 'P');
        PUT_IN_TABLE(table,0x7, 'Q');
        PUT_IN_TABLE(table,0x106, 'R');
        PUT_IN_TABLE(table,0x46, 'S');
        PUT_IN_TABLE(table,0x16, 'T');
        PUT_IN_TABLE(table,0x181, 'U');
        PUT_IN_TABLE(table,0xC1, 'V');
        PUT_IN_TABLE(table,0x1C0, 'W');
        PUT_IN_TABLE(table,0x91, 'X');
        PUT_IN_TABLE(table,0x190, 'Y');
        PUT_IN_TABLE(table,0xD0, 'Z');
        PUT_IN_TABLE(table,0x85, '-');
        PUT_IN_TABLE(table,0x184, '.');
        PUT_IN_TABLE(table,0xC4, ' ');
        PUT_IN_TABLE(table,0xA8, '$');
        PUT_IN_TABLE(table,0xA2, '/');
        PUT_IN_TABLE(table,0x8A, '+');
        PUT_IN_TABLE(table,0x2A, '%');
        PUT_IN_TABLE(table,0x94, DELIMITER);

        INIT_TABLE(aux,128,no_entry);
        PUT_IN_TABLE(aux,(unsigned)'0', 0);
        PUT_IN_TABLE(aux,(unsigned)'1', 1);
        PUT_IN_TABLE(aux,(unsigned)'2', 2);
        PUT_IN_TABLE(aux,(unsigned)'3', 3);
        PUT_IN_TABLE(aux,(unsigned)'4', 4);
        PUT_IN_TABLE(aux,(unsigned)'5', 5);
        PUT_IN_TABLE(aux,(unsigned)'6', 6);
        PUT_IN_TABLE(aux,(unsigned)'7', 7);
        PUT_IN_TABLE(aux,(unsigned)'8', 8);
        PUT_IN_TABLE(aux,(unsigned)'9', 9);
        PUT_IN_TABLE(aux,(unsigned)'A', 10);
        PUT_IN_TABLE(aux,(unsigned)'B', 11);
        PUT_IN_TABLE(aux,(unsigned)'C', 12);
        PUT_IN_TABLE(aux,(unsigned)'D', 13);
        PUT_IN_TABLE(aux,(unsigned)'E', 14);
        PUT_IN_TABLE(aux,(unsigned)'F', 15);
        PUT_IN_TABLE(aux,(unsigned)'G', 16);
        PUT_IN_TABLE(aux,(unsigned)'H', 17);
        PUT_IN_TABLE(aux,(unsigned)'I', 18);
        PUT_IN_TABLE(aux,(unsigned)'J', 19);
        PUT_IN_TABLE(aux,(unsigned)'K', 20);
        PUT_IN_TABLE(aux,(unsigned)'L', 21);
        PUT_IN_TABLE(aux,(unsigned)'M', 22);
        PUT_IN_TABLE(aux,(unsigned)'N', 23);
        PUT_IN_TABLE(aux,(unsigned)'O', 24);
        PUT_IN_TABLE(aux,(unsigned)'P', 25);
        PUT_IN_TABLE(aux,(unsigned)'Q', 26);
        PUT_IN_TABLE(aux,(unsigned)'R', 27);
        PUT_IN_TABLE(aux,(unsigned)'S', 28);
        PUT_IN_TABLE(aux,(unsigned)'T', 29);
        PUT_IN_TABLE(aux,(unsigned)'U', 30);
        PUT_IN_TABLE(aux,(unsigned)'V', 31);
        PUT_IN_TABLE(aux,(unsigned)'W', 32);
        PUT_IN_TABLE(aux,(unsigned)'X', 33);
        PUT_IN_TABLE(aux,(unsigned)'Y', 34);
        PUT_IN_TABLE(aux,(unsigned)'Z', 35);
        PUT_IN_TABLE(aux,(unsigned)'-', 36);
        PUT_IN_TABLE(aux,(unsigned)'.', 37);
        PUT_IN_TABLE(aux,(unsigned)' ', 38);
        PUT_IN_TABLE(aux,(unsigned)'$', 39);
        PUT_IN_TABLE(aux,(unsigned)'/', 40);
        PUT_IN_TABLE(aux,(unsigned)'+', 41);
        PUT_IN_TABLE(aux,(unsigned)'%', 42);
    }

    inline module_word_t code39_t::get_key(const bar_vector_t& b) const
    {
#ifdef STRICT
        u_t n_l = ((double) b.psize / 15.0); // ((b.size / (6*1+3*3)) * 1
        u_t n_h = ((double) b.psize / 12.0); // ((b.size / (6*1+3*2)) * 1
        u_t w_l = ((double) b.psize / 6.0);  // ((b.size / (6*1+3*2)) * 2
        u_t w_h = ((double) b.psize / 5.0);   // ((b.size / (6*1+3*3)) * 3
#else
        u_t n_l = ((double) b.psize / 30.0);
        u_t n_h = ((double) b.psize / 8.0);
        u_t w_l = ((double) b.psize / 7.9);
        u_t w_h = ((double) b.psize / 1.0);

#endif
        assert(b.size() == 9);
        module_word_t r = 0;
        for (unsigned i = 0; i < 9; ++i) {
            r <<= 1;
            if (w_l <= b[i].second && b[i].second <= w_h) r += 1;
            else if (! (n_l <= b[i].second && b[i].second <= n_h)) return 0;
        }
        return r;
    }

    inline module_word_t code39_t::reverse_get_key(const bar_vector_t& b) const
    {
#ifdef STRICT
        u_t n_l = ((double) b.psize / 15.0); // ((b.size / (6*1+3*3)) * 1
        u_t n_h = ((double) b.psize / 12.0); // ((b.size / (6*1+3*2)) * 1
        u_t w_l = ((double) b.psize / 6.0);  // ((b.size / (6*1+3*2)) * 2
        u_t w_h = ((double) b.psize / 5.0);   // ((b.size / (6*1+3*3)) * 3
#else
        u_t n_l = ((double) b.psize / 30.0);
        u_t n_h = ((double) b.psize / 8.0);
        u_t w_l = ((double) b.psize / 7.9);
        u_t w_h = ((double) b.psize / 1.0);

#endif
        assert(b.size() == 9);
        module_word_t r = 0;
        for (int i = 8; i >= 0; --i) {
            r <<= 1;
            if (w_l <= b[i].second && b[i].second <= w_h) r += 1;
            else if (! (n_l <= b[i].second && b[i].second <= n_h)) return 0;
        }
        return r;
    }

    // psize = 0 means skip that test
    inline bool code39_t::check_bar_vector(const bar_vector_t& b,psize_t old_psize) const
    {
        // check psize
        // check colors
        assert(b.size() == 9);
#if 0
        return 
            (!old_psize || fabs((long)b.psize - (long)old_psize) < 0.5 * old_psize) && 
            b[0].first && b[8].first;
#else
        if (old_psize && ! (fabs((long) b.psize - (long) old_psize) < 0.5 * old_psize)) {
            return false;
        }
        if ( ! (b[0].first && b[8].first) ) {
                return false;
        }
        return true;
#endif
    }

    template<class TIT>
    bool code39_t::expect_n(TIT& start, TIT end, psize_t old_psize) const
    {
        using namespace scanner_utilities;
        bar_vector_t b(1);
        if ( get_bars(start,end,b,1) != 1 || b[0].first ) return false;
#ifdef STRICT
        u_t n_l = ((double) old_psize / 15.0); // ((b.size / (6*1+3*3)) * 1
        u_t n_h = ((double) old_psize / 12.0); // ((b.size / (6*1+3*2)) * 1
#else
        u_t n_l = ((double) old_psize / 30.0);
        u_t n_h = ((double) old_psize / 7.0);
#endif
        return n_l <= b[0].second && b[0].second <= n_h;
    }

    template<class TIT>
    scanner_result_t code39_t::scan(TIT& start, TIT end, pos_t x, pos_t y, psize_t quiet_psize) const
    {
        using namespace scanner_utilities;

        // try to match start marker
        // do relatively cheap though rough test on the first two bars only.
        bar_vector_t b(9);
        if ( get_bars(start,end,b,2) != 2 ) return scanner_result_t();
        if (b[0].second > 0.8 * b[1].second) return scanner_result_t();
        if (b[1].second > 3.5 * b[0].second) return scanner_result_t();

        if ( add_bars(start,end,b,7) != 7 ) return scanner_result_t();
        if (! check_bar_vector(b) ) return scanner_result_t();

        // check quiet_zone with respect to length of the first symbol
        //if (quiet_psize < (double) b.psize * 0.7) return scanner_result_t(); // 10 x quiet zone
        if (quiet_psize < (double) b.psize * 0.4) return scanner_result_t(); // 10 x quiet zone

        // expect start sequence
        module_word_t key = get_key(b);
        if ( ! key || table[key] != DELIMITER ) {
            return scanner_result_t();
        }

        std::string code = "";
        psize_t old_psize;
        bool at_end = false;
        while (! at_end) {
            old_psize = b.psize;
            // consume narrow separator
            if (! expect_n(start,end,old_psize)) return scanner_result_t();

            // get new symbol
            if ( get_bars(start,end,b,9) != 9) return scanner_result_t();
            if (! check_bar_vector(b,old_psize) ) return scanner_result_t();

            key = get_key(b);
            if (! key ) return scanner_result_t();
            const uint8_t c = table[key];
            switch(c) {
            case (uint8_t) no_entry: return scanner_result_t();
            case (uint8_t) DELIMITER: at_end = true; break;
            default: code.push_back(c);
            }
        }

        return scanner_result_t(code39,code,x,y);
    }

    template<class TIT>
    scanner_result_t code39_t::reverse_scan(TIT& start, TIT end, pos_t x, pos_t y, psize_t quiet_psize) const
    {
        using namespace scanner_utilities;

        // try to match start marker
        // do relatively cheap though rough test on the first two bars only.
        bar_vector_t b(9);
        if ( get_bars(start,end,b,2) != 2 ) return scanner_result_t();
        if (b[0].second > 1.8 * b[1].second) return scanner_result_t();
        if (b[1].second > 1.8 * b[0].second) return scanner_result_t();

        if ( add_bars(start,end,b,7) != 7 ) return scanner_result_t();
        if (! check_bar_vector(b) ) return scanner_result_t();

        // check quiet_zone with respect to length of the first symbol
        if (quiet_psize < (double) b.psize * 0.4) return scanner_result_t(); // 10 x quiet zone

        // expect start sequence
        module_word_t key = reverse_get_key(b);
        if ( ! key || table[key] != DELIMITER ) {
            return scanner_result_t();
        }

        std::string code = "";
        psize_t old_psize;
        bool at_end = false;
        while (! at_end) {
            old_psize = b.psize;
            // consume narrow separator
            if (! expect_n(start,end,old_psize)) return scanner_result_t();

            // get new symbol
            if ( get_bars(start,end,b,9) != 9) return scanner_result_t();
            if (! check_bar_vector(b,old_psize) ) return scanner_result_t();

            key = reverse_get_key(b);
            if (! key ) return scanner_result_t();
            const uint8_t c = table[key];
            switch(c) {
            case (uint8_t) no_entry: return scanner_result_t();
            case (uint8_t) DELIMITER: at_end = true; break;
            default: code.push_back(c);
            }
        }

        return scanner_result_t(code39,std::string(code.rbegin(),code.rend()),x,y);
    }

}; // namespace BarDecode

#endif // _CODE39_HH_
