<?php
/**
 * Zend Framework (http://framework.zend.com/)
 *
 * @link      http://github.com/zendframework/zf2 for the canonical source repository
 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
 * @license   http://framework.zend.com/license/new-bsd New BSD License
 */

namespace Zend\Validator\Barcode;

use 
Zend\Validator\Exception;
use 
Zend\Stdlib\StringUtils;
use 
Zend\Stdlib\StringWrapper\StringWrapperInterface;

class 
Code128 extends AbstractAdapter
{
    
/**
     * The used string wrapper used for basic UTF-8 string functions
     *
     * @var StringWrapperInterface
     */
    
protected $utf8StringWrapper;

    
/**
     * Constructor for this barcode adapter
     */
    
public function __construct()
    {
        
$this->setLength(-1);
        
$this->setCharacters(array(
        
'A' => array(
             
=> ' ',  => '!',  => '"',  => '#',  => '$',  => '%',  => '&',  => "'",
             
=> '(',  => ')'10 => '*'11 => '+'12 => ','13 => '-'14 => '.'15 => '/',
            
16 => '0'17 => '1'18 => '2'19 => '3'20 => '4'21 => '5'22 => '6'23 => '7',
            
24 => '8'25 => '9'26 => ':'27 => ';'28 => '<'29 => '='30 => '>'31 => '?',
            
32 => '@'33 => 'A'34 => 'B'35 => 'C'36 => 'D'37 => 'E'38 => 'F'39 => 'G',
            
40 => 'H'41 => 'I'42 => 'J'43 => 'K'44 => 'L'45 => 'M'46 => 'N'47 => 'O',
            
48 => 'P'49 => 'Q'50 => 'R'51 => 'S'52 => 'T'53 => 'U'54 => 'V'55 => 'W',
            
56 => 'X'57 => 'Y'58 => 'Z'59 => '['60 => '\\',61 => ']'62 => '^'63 => '_',
            
64 =>0x0065 =>0x0166 =>0x0267 =>0x0368 =>0x0469 =>0x0570 =>0x0671 =>0x07,
            
72 =>0x0873 =>0x0974 =>0x0A75 =>0x0B76 =>0x0C77 =>0x0D78 =>0x0E79 =>0x0F,
            
80 =>0x1081 =>0x1182 =>0x1283 =>0x1384 =>0x1485 =>0x1586 =>0x1687 =>0x17,
            
88 =>0x1889 =>0x1990 =>0x1A91 =>0x1B92 =>0x1C93 =>0x1D94 =>0x1E95 =>0x1F,
            
96 => 'Ç'97 => 'ü'98 => 'é'99 => 'â',100 => 'ä',101 => 'à',102 => 'å',103 => '‡',
           
104 => 'ˆ',105 => '‰',106 => 'Š'),
        
'B' => array(
             
=> ' ',  => '!',  => '"',  => '#',  => '$',  => '%',  => '&',  => "'",
             
=> '(',  => ')'10 => '*'11 => '+'12 => ','13 => '-'14 => '.'15 => '/',
            
16 => '0'17 => '1'18 => '2'19 => '3'20 => '4'21 => '5'22 => '6'23 => '7',
            
24 => '8'25 => '9'26 => ':'27 => ';'28 => '<'29 => '='30 => '>'31 => '?',
            
32 => '@'33 => 'A'34 => 'B'35 => 'C'36 => 'D'37 => 'E'38 => 'F'39 => 'G',
            
40 => 'H'41 => 'I'42 => 'J'43 => 'K'44 => 'L'45 => 'M'46 => 'N'47 => 'O',
            
48 => 'P'49 => 'Q'50 => 'R'51 => 'S'52 => 'T'53 => 'U'54 => 'V'55 => 'W',
            
56 => 'X'57 => 'Y'58 => 'Z'59 => '['60 => '\\',61 => ']'62 => '^'63 => '_',
            
64 => '`'65 => 'a'66 => 'b'67 => 'c'68 => 'd'69 => 'e'70 => 'f'71 => 'g',
            
72 => 'h'73 => 'i'74 => 'j'75 => 'k'76 => 'l'77 => 'm'78 => 'n'79 => 'o',
            
80 => 'p'81 => 'q'82 => 'r'83 => 's'84 => 't'85 => 'u'86 => 'v'87 => 'w',
            
88 => 'x'89 => 'y'90 => 'z'91 => '{'92 => '|'93 => '}'94 => '~'95 =>0x7F,
            
96 => 'Ç'97 => 'ü'98 => 'é'99 => 'â',100 => 'ä',101 => 'à',102 => 'å',103 => '‡',
           
104 => 'ˆ',105 => '‰',106 => 'Š'),
        
'C' => array(
             
=> '00',  => '01',  => '02',  => '03',  => '04',  => '05',  => '06',  => '07',
             
=> '08',  => '09'10 => '10'11 => '11'12 => '12'13 => '13'14 => '14'15 => '15',
            
16 => '16'17 => '17'18 => '18'19 => '19'20 => '20'21 => '21'22 => '22'23 => '23',
            
24 => '24'25 => '25'26 => '26'27 => '27'28 => '28'29 => '29'30 => '30'31 => '31',
            
32 => '32'33 => '33'34 => '34'35 => '35'36 => '36'37 => '37'38 => '38'39 => '39',
            
40 => '40'41 => '41'42 => '42'43 => '43'44 => '44'45 => '45'46 => '46'47 => '47',
            
48 => '48'49 => '49'50 => '50'51 => '51'52 => '52'53 => '53'54 => '54'55 => '55',
            
56 => '56'57 => '57'58 => '58'59 => '59'60 => '60'61 => '61'62 => '62'63 => '63',
            
64 => '64'65 => '65'66 => '66'67 => '67'68 => '68'69 => '69'70 => '70'71 => '71',
            
72 => '72'73 => '73'74 => '74'75 => '75'76 => '76'77 => '77'78 => '78'79 => '79',
            
80 => '80'81 => '81'82 => '82'83 => '83'84 => '84'85 => '85'86 => '86'87 => '87',
            
88 => '88'89 => '89'90 => '90'91 => '91'92 => '92'93 => '93'94 => '94'95 => '95',
            
96 => '96'97 => '97'98 => '98'99 => '99',100 => 'ä'101 => 'à'102 => 'å'103 => '‡',
           
104 => 'ˆ'105 => '‰'106 => 'Š')));
        
$this->setChecksum('code128');

    }

    public function 
setUtf8StringWrapper(StringWrapperInterface $utf8StringWrapper)
    {
        if (!
$utf8StringWrapper->isSupported('UTF-8')) {
            throw new 
Exception\InvalidArgumentException(
                
"The string wrapper needs to support UTF-8 character encoding"
            
);
        }
        
$this->utf8StringWrapper $utf8StringWrapper;
    }

    
/**
     * Get the string wrapper supporting UTF-8 character encoding
     *
     * @return StringWrapperInterface
     */
    
public function getUtf8StringWrapper()
    {
        if (!
$this->utf8StringWrapper) {
            
$this->utf8StringWrapper StringUtils::getWrapper('UTF-8');
        }
        return 
$this->utf8StringWrapper;
    }

    
/**
     * Checks for allowed characters within the barcode
     *
     * @param  string $value The barcode to check for allowed characters
     * @return bool
     */
    
public function hasValidCharacters($value)
    {
        if (!
is_string($value)) {
            return 
false;
        }

        
// get used string wrapper for UTF-8 character encoding
        
$strWrapper $this->getUtf8StringWrapper();

        
// detect starting charset
        
$set        $this->getCodingSet($value);
        
$read       $set;
        if (
$set != '') {
            
$value $strWrapper->substr($value1null);
        }

        
// process barcode
        
while ($value != '') {
            
$char $strWrapper->substr($value01);

            switch (
$char) {
                
// Function definition
                
case 'Ç' :
                case 
'ü' :
                case 
'å' :
                    break;

                
// Switch 1 char between A and B
                
case 'é' :
                    if (
$set == 'A') {
                        
$read 'B';
                    } elseif (
$set == 'B') {
                        
$read 'A';
                    }
                    break;

                
// Switch to C
                
case 'â' :
                    
$set 'C';
                    
$read 'C';
                    break;

                
// Switch to B
                
case 'ä' :
                    
$set  'B';
                    
$read 'B';
                    break;

                
// Switch to A
                
case 'à' :
                    
$set  'A';
                    
$read 'A';
                    break;

                
// Doubled start character
                
case '‡' :
                case 
'ˆ' :
                case 
'‰' :
                    return 
false;
                    break;

                
// Chars after the stop character
                
case 'Š' :
                    break 
2;

                default:
                    
// Does the char exist within the charset to read?
                    
if ($this->ord128($char$read) == -1) {
                        return 
false;
                    }

                    break;
            }

            
$value $strWrapper->substr($value1null);
            
$read  $set;
        }

        if ((
$value != '') && ($strWrapper->strlen($value) != 1)) {
            return 
false;
        }

        return 
true;
    }

    
/**
     * Validates the checksum ()
     *
     * @param  string $value The barcode to validate
     * @return bool
     */
    
protected function code128($value)
    {
        
$sum        0;
        
$pos        1;
        
$set        $this->getCodingSet($value);
        
$read       $set;
        
$usecheck   $this->useChecksum(null);
        
$strWrapper $this->getUtf8StringWrapper();
        
$char       $strWrapper->substr($value01);
        if (
$char == '‡') {
            
$sum 103;
        } elseif (
$char == 'ˆ') {
            
$sum 104;
        } elseif (
$char == '‰') {
            
$sum 105;
        } elseif (
$usecheck == true) {
            
// no start value, unable to detect an proper checksum
            
return false;
        }

        
$value $strWrapper->substr($value1null);
        while (
$strWrapper->strpos($value'Š') || ($value != '')) {
            
$char $strWrapper->substr($value01);
            if (
$read == 'C') {
                
$char $strWrapper->substr($value02);
            }

            switch (
$char) {
                
// Function definition
                
case 'Ç' :
                case 
'ü' :
                case 
'å' :
                    
$sum += ($pos $this->ord128($char$set));
                    break;

                case 
'é' :
                    
$sum += ($pos $this->ord128($char$set));
                     if (
$set == 'A') {
                        
$read 'B';
                    } elseif (
$set == 'B') {
                        
$read 'A';
                    }
                    break;

                
// Switch to C
                
case 'â' :
                    
$sum += ($pos $this->ord128($char$set));
                    
$set 'C';
                    
$read 'C';
                    break;

                
// Switch to B
                
case 'ä' :
                    
$sum += ($pos $this->ord128($char$set));
                    
$set  'B';
                    
$read 'B';
                    break;

                
// Switch to A
                
case 'à' :
                    
$sum += ($pos $this->ord128($char$set));
                    
$set  'A';
                    
$read 'A';
                    break;

                case 
'‡' :
                case 
'ˆ' :
                case 
'‰' :
                    return 
false;
                    break;

                default:
                    
// Does the char exist within the charset to read?
                    
if ($this->ord128($char$read) == -1) {
                        return 
false;
                    }

                    
$sum += ($pos $this->ord128($char$set));
                    break;
            }

            
$value $strWrapper->substr($value1);
            ++
$pos;
            if ((
$strWrapper->strpos($value'Š') == 1) && ($strWrapper->strlen($value) == 2)) {
                
// break by stop and checksum char
                
break;
            }
            
$read  $set;
        }

        if ((
$strWrapper->strpos($value'Š') != 1) || ($strWrapper->strlen($value) != 2)) {
            
// return false if checksum is not readable and true if no startvalue is detected
            
return (!$usecheck);
        }

        
$mod $sum 103;
        if (
$strWrapper->substr($value01) == $this->chr128($mod$set)) {
            return 
true;
        }

        return 
false;
    }

    
/**
     * Returns the coding set for a barcode
     *
     * @param string $value Barcode
     * @return string
     */
    
protected function getCodingSet($value)
    {
        
$value $this->getUtf8StringWrapper()->substr($value01);
        switch (
$value) {
            case 
'‡' :
                return 
'A';
                break;
            case 
'ˆ' :
                return 
'B';
                break;
            case 
'‰' :
                return 
'C';
                break;
        }

        return 
'';
    }

    
/**
     * Internal method to return the code128 integer from an ascii value
     *
     * Table A
     *    ASCII       CODE128
     *  32 to  95 ==  0 to  63
     *   0 to  31 == 64 to  95
     * 128 to 138 == 96 to 106
     *
     * Table B
     *    ASCII       CODE128
     *  32 to 138 == 0 to 106
     *
     * Table C
     *    ASCII       CODE128
     *  "00" to "99" ==   0 to  99
     *   132 to  138 == 100 to 106
     *
     * @param string $value
     * @param string $set
     * @return integer
     */
    
protected function ord128($value$set)
    {
        
$ord ord($value);
        if (
$set == 'A') {
            if (
$ord 32) {
                return (
$ord 64);
            } elseif (
$ord 96) {
                return (
$ord 32);
            } elseif (
$ord 138) {
                return -
1;
            } else {
                return (
$ord 32);
            }
        } elseif (
$set == 'B') {
            if (
$ord 32) {
                return -
1;
            } elseif (
$ord <= 138) {
                return (
$ord 32);
            } else {
                return -
1;
            }
        } elseif (
$set == 'C') {
            
$val = (int) $value;
            if ((
$val >= 0) && ($val <= 99)) {
                return 
$val;
            } elseif ((
$ord >= 132) && ($ord <= 138)) {
                return (
$ord 32);
            } else {
                return -
1;
            }
        } else {
            if (
$ord 32) {
                return (
$ord +64);
            } elseif (
$ord <= 138) {
                return (
$ord 32);
            } else {
                return -
1;
            }
        }
    }

    
/**
     * Internal Method to return the ascii value from an code128 integer
     *
     * Table A
     *    ASCII       CODE128
     *  32 to  95 ==  0 to  63
     *   0 to  31 == 64 to  95
     * 128 to 138 == 96 to 106
     *
     * Table B
     *    ASCII       CODE128
     *  32 to 138 == 0 to 106
     *
     * Table C
     *    ASCII       CODE128
     *  "00" to "99" ==   0 to  99
     *   132 to  138 == 100 to 106
     *
     * @param integer $value
     * @param string $set
     * @return string
     */
    
protected function chr128($value$set)
    {
        if (
$set == 'A') {
            if (
$value 64) {
                return 
chr($value 32);
            } elseif (
$value 96) {
                return 
chr($value 64);
            } elseif (
$value 106) {
                return -
1;
            } else {
                return 
chr($value 32);
            }
        } elseif (
$set == 'B') {
            if (
$value 106) {
                return -
1;
            } else {
                return 
chr($value 32);
            }
        } elseif (
$set == 'C') {
            if ((
$value >= 0) && ($value <= 9)) {
                return 
"0" . (string) $value;
            } elseif (
$value <= 99) {
                return (string) 
$value;
            } elseif (
$value <= 106) {
                return 
chr($value 32);
            } else {
                return -
1;
            }
        } else {
            if (
$value <= 106) {
                return (
$value 32);
            } else {
                return -
1;
            }
        }
    }
}