/*
 * SDK Pullenti Unitext, version 4.33, fabruary 2026. Copyright (c) 2013-2026, Pullenti. All rights reserved. 
 * Non-Commercial Freeware and Commercial Software. 
 * This class is generated using the converter UniSharping (www.unisharping.ru) from Pullenti C# project. 
 * The latest version of the code is available on the site www.pullenti.ru
 */

const Utils = require("./../unisharp/Utils");
const RefOutArgWrapper = require("./../unisharp/RefOutArgWrapper");
const Stream = require("./../unisharp/Stream");

const MiscHelper = require("./MiscHelper");
const EncodingStandard = require("./EncodingStandard");

/**
 * Реализация кодировщика строк, замена системного Encoding. 
 * Введена из-за того, что в .NET Core не поддержаны разные 1251 и пр. 
 * Да и для кросспрограммности и кроссплатформенности это необходимо.
 * Кодировщик строк
 */
class EncodingWrapper {
    
    /**
     * Создать обёртку
     * @param typ Стандартная кодировка, для которой есть собственная реализация
     * @param str Строковое название, например, windows-1251, utf-8 и пр.
     * @param cp Кодовая страница Windows, например, 1251
     */
    constructor(typ, str = null, cp = 0) {
        this.stdTyp = EncodingStandard.UNDEFINED;
        this.codePage = 0;
        this.codePage = cp;
        this.stdTyp = typ;
        if (cp === 1251) 
            this.stdTyp = EncodingStandard.CP1251;
        else if (cp === 1252) 
            this.stdTyp = EncodingStandard.CP1252;
        if (Utils.isNullOrEmpty(str)) 
            return;
        let ss = Utils.replaceString(str, "-", "").toUpperCase();
        if (ss.endsWith("ASCII")) {
            this.stdTyp = EncodingStandard.ACSII;
            return;
        }
        if (ss.startsWith("WINDOWS")) {
            let cod = 0;
            let wrapcod649 = new RefOutArgWrapper();
            let inoutres650 = Utils.tryParseInt(ss.substring(7), wrapcod649);
            cod = wrapcod649.value;
            if (inoutres650) {
                this.codePage = cod;
                if (cod === 1251) 
                    this.stdTyp = EncodingStandard.CP1251;
                else if (cod === 1252) 
                    this.stdTyp = EncodingStandard.CP1252;
                return;
            }
        }
        if (ss.startsWith("CP")) {
            let cod = 0;
            let wrapcod651 = new RefOutArgWrapper();
            let inoutres652 = Utils.tryParseInt(ss.substring(2), wrapcod651);
            cod = wrapcod651.value;
            if (inoutres652) {
                this.codePage = cod;
                if (cod === 1251) 
                    this.stdTyp = EncodingStandard.CP1251;
                else if (cod === 1252) 
                    this.stdTyp = EncodingStandard.CP1252;
                return;
            }
        }
        if (ss === "UTF8") {
            this.stdTyp = EncodingStandard.UTF8;
            return;
        }
        if (ss === "UNICODE" || ss === "UTF16" || ss === "UTF16LE") {
            this.stdTyp = EncodingStandard.UTF16LE;
            return;
        }
        if (ss === "UTF16BE") {
            this.stdTyp = EncodingStandard.UTF16BE;
            return;
        }
    }
    
    toString() {
        if (this.stdTyp !== EncodingStandard.UNDEFINED) 
            return this.stdTyp.toString();
        if (this.codePage > 0) 
            return ("CodePage=" + this.codePage);
        return "?";
    }
    
    /**
     * Закодировать строку
     * @param str кодируемая строка
     * @return массив файт
     */
    getBytes(str) {
        if (str === null) 
            return new Uint8Array(0);
        if (this.stdTyp === EncodingStandard.ACSII) 
            return MiscHelper.encodeStringAscii(str);
        if (this.stdTyp === EncodingStandard.UTF8) 
            return MiscHelper.encodeStringUtf8(str, false);
        if (this.stdTyp === EncodingStandard.CP1251) 
            return MiscHelper.encodeString1251(str);
        if (this.stdTyp === EncodingStandard.CP1252) 
            return MiscHelper.encodeString1252(str);
        if (this.stdTyp === EncodingStandard.UTF16LE) 
            return MiscHelper.encodeStringUnicode(str);
        if (this.stdTyp === EncodingStandard.UTF16BE) 
            return MiscHelper.encodeStringUnicodeBE(str);
        return MiscHelper.encodeString1252(str);
    }
    
    /**
     * Раскодировать строку
     * @param dat массив байт
     * @param len начальная позиция в массиве
     * @param pos длина
     * @return 
     */
    getString(dat, pos = 0, len = -1) {
        if (dat === null || (pos < 0)) 
            return null;
        if (dat.length === 0) 
            return "";
        if (len < 0) 
            len = dat.length - pos;
        if (this.stdTyp === EncodingStandard.ACSII) 
            return MiscHelper.decodeStringAscii(dat, pos, len);
        if (this.stdTyp === EncodingStandard.UTF8) 
            return MiscHelper.decodeStringUtf8(dat, pos, len);
        if (this.stdTyp === EncodingStandard.CP1251) 
            return MiscHelper.decodeString1251(dat, pos, len);
        if (this.stdTyp === EncodingStandard.CP1252) 
            return MiscHelper.decodeString1252(dat, pos, len);
        if (this.stdTyp === EncodingStandard.UTF16LE) 
            return MiscHelper.decodeStringUnicode(dat, pos, len);
        if (this.stdTyp === EncodingStandard.UTF16BE) 
            return MiscHelper.decodeStringUnicodeBE(dat, pos, len);
        return MiscHelper.decodeString1252(dat, pos, len);
    }
    
    /**
     * Определение кодировки по байтовому массиву
     * @param data кодированный массив
     * @param headLen размер префикса, если он есть
     * @return результирующая кодировка
     */
    static checkEncoding(data, headLen) {
        headLen.value = 0;
        if ((data.length >= 3 && data[0] === (0xEF) && data[1] === (0xBB)) && data[2] === (0xBF)) {
            if (data.length === 3) 
                return null;
            headLen.value = 3;
            return new EncodingWrapper(EncodingStandard.UTF8);
        }
        if (data.length >= 2 && data[0] === (0xFF) && data[1] === (0xFE)) {
            if (data.length === 2) 
                return null;
            headLen.value = 2;
            return new EncodingWrapper(EncodingStandard.UTF16LE);
        }
        if (data.length >= 2 && data[0] === (0xFE) && data[1] === (0xFF)) {
            if (data.length === 2) 
                return null;
            headLen.value = 2;
            return new EncodingWrapper(EncodingStandard.UTF16BE);
        }
        if (data.length > 10000) {
            let buf = new Uint8Array(10000);
            for (let ii = 0; ii < buf.length; ii++) {
                buf[ii] = data[ii];
            }
            data = buf;
        }
        let i = 0;
        let j = 0;
        let dos = 0;
        let win = 0;
        let utf8 = 0;
        let d0 = 0;
        let rus = 0;
        for (i = 0; i < data.length; i++) {
            j = data[i];
            if (j === 0xE2 && ((i + 2) < data.length)) {
                if (data[i + 1] === (0x80) && data[i + 2] === (0x99)) {
                    utf8++;
                    i += 2;
                    continue;
                }
            }
            if (j >= 0xC0) 
                win++;
            if ((j >= 0x80 && j <= 0xAF)) 
                dos++;
            else if (j >= 0xE0 && j <= 0xEF) 
                dos++;
            if (j >= 0x80) {
                rus++;
                if (j === 0xD0 || j === 0xD1) 
                    d0++;
            }
        }
        if (dos > win && utf8 === 0) {
            let data2 = new Uint8Array(data.length);
            for (i = 0; i < data.length; i++) {
                data2[i] = data[i];
                j = data[i];
                if (j >= 0xE0 && j <= 0xEF) 
                    j -= 0x30;
                if (j >= 0x80 && j <= 0xDF) 
                    data2[i] = (j + 0x40);
                if (j === 0xF1) 
                    data2[i] = 0xB8;
                if (j === 0xF0) 
                    data2[i] = 0xA8;
            }
            data = data2;
        }
        let enc = null;
        let txt = null;
        if (d0 > ((Utils.intDiv(rus, 5))) || utf8 > 0) {
            try {
                txt = MiscHelper.decodeStringUtf8(data, 0, -1);
                enc = new EncodingWrapper(EncodingStandard.UTF8);
                if (utf8 > 0) 
                    return enc;
            } catch (ex653) {
            }
        }
        if (txt === null || enc === null) 
            return new EncodingWrapper(EncodingStandard.CP1251);
        try {
            let txt2 = MiscHelper.decodeString1251(data, 0, -1);
            let ru = 0;
            let ru2 = 0;
            for (const ch of txt) {
                if ((ch.charCodeAt(0)) >= 0x400 && ((ch.charCodeAt(0)) < 0x500)) {
                    if (Utils.isLetter(ch)) 
                        ru++;
                }
            }
            for (const ch of txt2) {
                if ((ch.charCodeAt(0)) >= 0x400 && ((ch.charCodeAt(0)) < 0x500)) {
                    if (Utils.isLetter(ch)) 
                        ru2++;
                }
            }
            if (ru2 > ((ru * 2))) 
                return new EncodingWrapper(EncodingStandard.CP1251);
            return enc;
        } catch (ex) {
        }
        return new EncodingWrapper(EncodingStandard.UTF8);
    }
    
    /**
     * Считать строку из потока и раскодировать (использовать вместо StreamReader, 
     * чтобы и на Linux раскодировало винданутые кодировки)
     * @param s 
     * @return 
     */
    readLine(s) {
        let arr = new Array();
        while (true) {
            let i = s.readByte();
            if (i < 0) 
                break;
            if (i === 0xD || i === 0xA) {
                if (arr.length === 0) 
                    continue;
                break;
            }
            arr.push(i);
        }
        if (arr.length === 0) 
            return null;
        return this.getString(arr, 0, -1);
    }
}


module.exports = EncodingWrapper