1<?php
2declare(strict_types = 1);
3
4namespace BaconQrCode\Common;
5
6use BaconQrCode\Exception\InvalidArgumentException;
7use DASPRiD\Enum\AbstractEnum;
8
9/**
10 * Encapsulates a Character Set ECI, according to "Extended Channel Interpretations" 5.3.1.1 of ISO 18004.
11 *
12 * @method static self CP437()
13 * @method static self ISO8859_1()
14 * @method static self ISO8859_2()
15 * @method static self ISO8859_3()
16 * @method static self ISO8859_4()
17 * @method static self ISO8859_5()
18 * @method static self ISO8859_6()
19 * @method static self ISO8859_7()
20 * @method static self ISO8859_8()
21 * @method static self ISO8859_9()
22 * @method static self ISO8859_10()
23 * @method static self ISO8859_11()
24 * @method static self ISO8859_12()
25 * @method static self ISO8859_13()
26 * @method static self ISO8859_14()
27 * @method static self ISO8859_15()
28 * @method static self ISO8859_16()
29 * @method static self SJIS()
30 * @method static self CP1250()
31 * @method static self CP1251()
32 * @method static self CP1252()
33 * @method static self CP1256()
34 * @method static self UNICODE_BIG_UNMARKED()
35 * @method static self UTF8()
36 * @method static self ASCII()
37 * @method static self BIG5()
38 * @method static self GB18030()
39 * @method static self EUC_KR()
40 */
41final class CharacterSetEci extends AbstractEnum
42{
43    protected const CP437 = [[0, 2]];
44    protected const ISO8859_1 = [[1, 3], 'ISO-8859-1'];
45    protected const ISO8859_2 = [[4], 'ISO-8859-2'];
46    protected const ISO8859_3 = [[5], 'ISO-8859-3'];
47    protected const ISO8859_4 = [[6], 'ISO-8859-4'];
48    protected const ISO8859_5 = [[7], 'ISO-8859-5'];
49    protected const ISO8859_6 = [[8], 'ISO-8859-6'];
50    protected const ISO8859_7 = [[9], 'ISO-8859-7'];
51    protected const ISO8859_8 = [[10], 'ISO-8859-8'];
52    protected const ISO8859_9 = [[11], 'ISO-8859-9'];
53    protected const ISO8859_10 = [[12], 'ISO-8859-10'];
54    protected const ISO8859_11 = [[13], 'ISO-8859-11'];
55    protected const ISO8859_12 = [[14], 'ISO-8859-12'];
56    protected const ISO8859_13 = [[15], 'ISO-8859-13'];
57    protected const ISO8859_14 = [[16], 'ISO-8859-14'];
58    protected const ISO8859_15 = [[17], 'ISO-8859-15'];
59    protected const ISO8859_16 = [[18], 'ISO-8859-16'];
60    protected const SJIS = [[20], 'Shift_JIS'];
61    protected const CP1250 = [[21], 'windows-1250'];
62    protected const CP1251 = [[22], 'windows-1251'];
63    protected const CP1252 = [[23], 'windows-1252'];
64    protected const CP1256 = [[24], 'windows-1256'];
65    protected const UNICODE_BIG_UNMARKED = [[25], 'UTF-16BE', 'UnicodeBig'];
66    protected const UTF8 = [[26], 'UTF-8'];
67    protected const ASCII = [[27, 170], 'US-ASCII'];
68    protected const BIG5 = [[28]];
69    protected const GB18030 = [[29], 'GB2312', 'EUC_CN', 'GBK'];
70    protected const EUC_KR = [[30], 'EUC-KR'];
71
72    /**
73     * @var int[]
74     */
75    private $values;
76
77    /**
78     * @var string[]
79     */
80    private $otherEncodingNames;
81
82    /**
83     * @var array<int, self>|null
84     */
85    private static $valueToEci;
86
87    /**
88     * @var array<string, self>|null
89     */
90    private static $nameToEci;
91
92    public function __construct(array $values, string ...$otherEncodingNames)
93    {
94        $this->values = $values;
95        $this->otherEncodingNames = $otherEncodingNames;
96    }
97
98    /**
99     * Returns the primary value.
100     */
101    public function getValue() : int
102    {
103        return $this->values[0];
104    }
105
106    /**
107     * Gets character set ECI by value.
108     *
109     * Returns the representing ECI of a given value, or null if it is legal but unsupported.
110     *
111     * @throws InvalidArgumentException if value is not between 0 and 900
112     */
113    public static function getCharacterSetEciByValue(int $value) : ?self
114    {
115        if ($value < 0 || $value >= 900) {
116            throw new InvalidArgumentException('Value must be between 0 and 900');
117        }
118
119        $valueToEci = self::valueToEci();
120
121        if (! array_key_exists($value, $valueToEci)) {
122            return null;
123        }
124
125        return $valueToEci[$value];
126    }
127
128    /**
129     * Returns character set ECI by name.
130     *
131     * Returns the representing ECI of a given name, or null if it is legal but unsupported
132     */
133    public static function getCharacterSetEciByName(string $name) : ?self
134    {
135        $nameToEci = self::nameToEci();
136        $name = strtolower($name);
137
138        if (! array_key_exists($name, $nameToEci)) {
139            return null;
140        }
141
142        return $nameToEci[$name];
143    }
144
145    private static function valueToEci() : array
146    {
147        if (null !== self::$valueToEci) {
148            return self::$valueToEci;
149        }
150
151        self::$valueToEci = [];
152
153        foreach (self::values() as $eci) {
154            foreach ($eci->values as $value) {
155                self::$valueToEci[$value] = $eci;
156            }
157        }
158
159        return self::$valueToEci;
160    }
161
162    private static function nameToEci() : array
163    {
164        if (null !== self::$nameToEci) {
165            return self::$nameToEci;
166        }
167
168        self::$nameToEci = [];
169
170        foreach (self::values() as $eci) {
171            self::$nameToEci[strtolower($eci->name())] = $eci;
172
173            foreach ($eci->otherEncodingNames as $name) {
174                self::$nameToEci[strtolower($name)] = $eci;
175            }
176        }
177
178        return self::$nameToEci;
179    }
180}
181