import { _ContentParser, _PdfBaseStream, _PdfContentStream, _PdfReference } from '@syncfusion/ej2-pdf';
import { _FontStructure } from './text-extraction/font-structure';
import { _GraphicState } from './graphic-state';
import { _TextProcessingMode } from './enum';
import { _PdfContentParserHelper } from './content-parser-helper';
import { _ImageStructure } from './image-extraction/image-structure';
/**
 * Removes escape sequences from a text string and returns the cleaned text.
 *
 * @param {string} text - The string to process.
 * @returns {string} The processed string without escape sequences.
 */
export function _ignoreEscapeSequence(text) {
    var index = -1;
    do {
        index = text.indexOf('\\', index + 1);
        if (index >= 0) {
            if (text.length > index + 1) {
                var nextLiteral = text[index + 1];
                if (nextLiteral === '\\' || nextLiteral === '(' || nextLiteral === ')') {
                    text = text.slice(0, index) + text.slice(index + 1);
                }
            }
            else {
                text = text.slice(0, index) + text.slice(index + 1);
                index = -1;
            }
        }
    } while (index >= 0);
    return text;
}
/**
 * Adds font resources from a PDF dictionary into a collection.
 *
 * @param {_PdfDictionary} dictionary - PDF Dictionary containing font resources.
 * @param {_PdfCrossReference} crossReference - The cross-reference of the PDF document.
 * @returns {Map<string, _FontStructure>} A map of font structure objects.
 */
export function _addFontResources(dictionary, crossReference) {
    var font = dictionary.get('Font');
    var fontCollection = new Map();
    if (typeof (font) !== 'undefined' && font !== null) {
        font.forEach(function (key, value) {
            var fontDictionary = crossReference._fetch(value);
            var fontstruct = new _FontStructure(fontDictionary, crossReference);
            fontCollection.set(key.toString(), fontstruct);
        });
    }
    return fontCollection;
}
/**
 * Retrieves XObject resources from a PDF dictionary and their associated cross-references.
 *
 * @param {_PdfDictionary} resources - The resources dictionary from a PDF page.
 * @param {_PdfCrossReference} crossReference - The cross-reference of the PDF document.
 * @param { _TextProcessingMode } [mode] - Optional processing mode that controls how XObjects are interpreted
 * @param { PdfPage } page - The current PDF page, used when constructing image structures.
 * @returns {Map<string, any>} A map of XObject resources.
 */
export function _getXObjectResources(resources, crossReference, mode, page) {
    var xObjectCollection = new Map(); // eslint-disable-line
    if (resources && resources.has('XObject')) {
        var xObjects = resources.get('XObject');
        xObjects.forEach(function (key, value) {
            if (value instanceof _PdfReference) {
                var xobject = crossReference._fetch(value); // eslint-disable-line
                if (xobject !== null && typeof (xobject) !== 'undefined') {
                    var subtype = xobject.dictionary.get('Subtype').name;
                    switch (mode) {
                        case _TextProcessingMode.imageExtraction:
                            if (subtype === 'Image') {
                                var imageStruct = new _ImageStructure(xobject, crossReference, page);
                                xObjectCollection.set(key, imageStruct);
                            }
                            break;
                        case _TextProcessingMode.imageRedaction:
                            if (subtype === 'Image') {
                                var imageStruct = new _ImageStructure(xobject, crossReference, page);
                                imageStruct._imageReference = value;
                                xObjectCollection.set(key, imageStruct);
                            }
                            else if (subtype === 'Form') {
                                xObjectCollection.set(key, xobject);
                            }
                            break;
                        default:
                            if (subtype === 'Form') {
                                xObjectCollection.set(key, xobject);
                            }
                            break;
                    }
                }
            }
        });
    }
    return xObjectCollection;
}
/**
 * Converts a hexadecimal string to its equivalent character representation.
 *
 * @param {string} hex - The hexadecimal string to convert.
 * @returns {string} The resulting string of characters.
 */
export function _hexToChar(hex) {
    if (hex.startsWith('0x')) {
        hex = hex.slice(2);
    }
    hex = hex.replace(/\s+/g, '');
    var result = '';
    for (var i = 0; i < hex.length; i += 2) {
        var byte = hex.slice(i, i + 2);
        var charCode = parseInt(byte, 16);
        result += String.fromCharCode(charCode);
    }
    return result;
}
/**
 * Skips recognized escape sequences in a text string.
 *
 * @param {string} text - The string containing escape sequences.
 * @returns {string} A new string with escape sequences removed or handled appropriately.
 */
export function _skipEscapeSequence(text) {
    var isDefault = false;
    var replaceText = text;
    var isAlphabetic = false;
    while (replaceText.indexOf('\\') !== -1) {
        isAlphabetic = false;
        var i = replaceText.indexOf('\\');
        if (i + 1 !== replaceText.length) {
            var escapeSequence = replaceText.substring(i + 1, i + 2);
            switch (escapeSequence) {
                case 'a':
                    text = text.replace(/\\a/g, '\x07');
                    break;
                case '(':
                    text = text.replace(/\\\(/g, '(');
                    break;
                case ')':
                    text = text.replace(/\\\)/g, ')');
                    break;
                case 'b':
                    text = text.replace(/\\b/g, '\b');
                    break;
                case 'e':
                    text = text.replace(/\\e/g, '\\e');
                    isAlphabetic = true;
                    break;
                case 'f':
                    text = text.replace(/\\f/g, '\f');
                    break;
                case 'n':
                    text = text.replace(/\\n/g, '\n');
                    break;
                case 'r':
                    text = text.replace(/\\r/g, '\r');
                    break;
                case 't':
                    text = text.replace(/\\t/g, '\t');
                    break;
                case 'v':
                    text = text.replace(/\\v/g, '\v');
                    break;
                case "'": //eslint-disable-line
                    text = text.replace(/\\'/g, "'"); //eslint-disable-line
                    break;
                default: {
                    var charCode = escapeSequence.charCodeAt(0);
                    if (charCode === 3) {
                        text = text.replace(/\\/g, '\\');
                        isDefault = true;
                    }
                    else if (charCode >= 127) {
                        text = text.replace(/\\/g, '');
                        isDefault = true;
                    }
                    else if ((charCode >= 65 && charCode <= 90) || (charCode >= 97 && charCode <= 122)) {
                        replaceText = replaceText.slice(0, i) + replaceText.slice(i + 1);
                        isAlphabetic = true;
                    }
                    else {
                        var isUnrecognizedEscapeSequence = false;
                        if (text.includes('\\')) {
                            for (var c = 0; c < text.length - 1; c++) {
                                var nextChar = text[c + 1];
                                if (text[Number.parseInt(c.toString(), 10)] === '\\' && text[c + 1] === '\\') {
                                    c++;
                                }
                                else if (text[Number.parseInt(c.toString(), 10)] === '\\' && /[A-Zdghijklmopqswyz]/.test(nextChar)) {
                                    isUnrecognizedEscapeSequence = true;
                                    break;
                                }
                            }
                        }
                        if (isUnrecognizedEscapeSequence) {
                            text = text.replace(/\\/g, '');
                        }
                        else {
                            text = _parseEscapedText(text);
                        }
                        isDefault = true;
                    }
                    break;
                }
            }
            if (isDefault) {
                break;
            }
            else if (!isAlphabetic) {
                replaceText = text;
            }
        }
        else {
            break;
        }
    }
    return text;
}
/**
 * Converts escape sequences in a string to their corresponding literal characters.
 *
 * @param {string} text The input string containing escape sequences.
 *
 * @returns {string} The parsed string with escape sequences replaced by literal characters.
 */
export function _parseEscapedText(text) {
    return text
        .replace(/\\n/g, '\n')
        .replace(/\\r/g, '\r')
        .replace(/\\t/g, '\t')
        .replace(/\\"/g, '\"') // eslint-disable-line
        .replace(/\\'/g, "'") // eslint-disable-line
        .replace(/\\</g, '<')
        .replace(/\\>/g, '>')
        .replace(/\\\(/g, '(')
        .replace(/\\\)/g, ')')
        .replace(/\\\{/g, '{')
        .replace(/\\\}/g, '}')
        .replace(/\\\[/g, '[')
        .replace(/\\\]/g, ']')
        .replace(/\\\|/g, '|')
        .replace(/\\\*/g, '*')
        .replace(/\\\?/g, '?')
        .replace(/\\\-/g, "-") // eslint-disable-line
        .replace(/\\\+/g, '+')
        .replace(/\\\./g, '.')
        .replace(/\\\//g, '/')
        .replace(/\\,/g, ',')
        .replace(/\\:/g, ':')
        .replace(/\\;/g, ';')
        .replace(/\\=/g, '=')
        .replace(/\\&/g, '&')
        .replace(/\\%/g, '%')
        .replace(/\\#/g, '#')
        .replace(/\\!/g, '!')
        .replace(/\\u([0-9A-Fa-f]{4})/g, function (_, p1) { return String.fromCharCode(parseInt(p1, 16)); }) // eslint-disable-line
        .replace(/\\\\/g, '\\');
}
/**
 * Retrieves a literal string, decoding escape sequences and null characters.
 *
 * @param {string} encodedText - The encoded string to decode.
 * @param {string} [encoding] - The encoding used in the text.
 * @returns {string} The decoded literal string.
 */
export function _getLiteralString(encodedText, encoding) {
    var decodedText = encodedText;
    var octalIndex = -1;
    var limit = 3;
    var temp = '';
    while ((decodedText.indexOf('\\') !== -1 && (decodedText.indexOf('\\\\') === -1)) ||
        decodedText.indexOf('\0') ||
        ((decodedText.indexOf('\\\\') !== -1) && encoding === 'Encoding')) {
        var octalText = '';
        if (decodedText.indexOf('\\', octalIndex + 1) >= 0) {
            var nullPosition = decodedText.indexOf('\0', octalIndex + 1);
            octalIndex = decodedText.indexOf('\\', octalIndex + 1);
            if (nullPosition > -1 && octalIndex > nullPosition) {
                octalIndex = nullPosition;
                limit = 2;
            }
        }
        else {
            octalIndex = decodedText.indexOf('\0', octalIndex + 1);
            if (octalIndex < 0) {
                break;
            }
            limit = 2;
        }
        for (var i = octalIndex + 1; i <= octalIndex + limit; i++) {
            if (i < decodedText.length) {
                var val = parseInt(decodedText[Number.parseInt(i.toString(), 10)], 10);
                if (!isNaN(val) && val <= 8) {
                    octalText += decodedText[Number.parseInt(i.toString(), 10)];
                }
                else {
                    octalText = '';
                    break;
                }
            }
            else {
                octalText = '';
            }
        }
        if (octalText !== '') {
            var decimalValue = parseInt(octalText, 8);
            temp = String.fromCharCode(decimalValue);
            decodedText = decodedText.substring(0, octalIndex) + decodedText.substring(octalIndex + limit + 1);
            decodedText = decodedText.substring(0, octalIndex) + temp + decodedText.substring(octalIndex);
        }
    }
    if (decodedText.indexOf('\\') !== -1 && encoding === 'Encoding') {
        if (decodedText.indexOf('\\\\') === -1) {
            var initialLength = 0;
            while ((decodedText.indexOf('\\') === -1 && decodedText.indexOf('\\') !== -1) && (decodedText.length !== initialLength)) {
                initialLength = decodedText.length;
                decodedText = _skipEscapeSequence(decodedText);
            }
        }
    }
    return decodedText;
}
/**
 * Decodes encoded text using the specified font structure, adjusting for encoding differences.
 *
 * @param {string} encodedText - The encoded string to decode.
 * @param {_FontStructure} font - The font structure for decoding glyphs.
 * @param {string[]} inputText - An array of strings representing parts of the text.
 * @returns {string} The decoded text.
 */
export function _decodeEncodedText(encodedText, font, inputText) {
    var key = '';
    var isHex = false;
    var decodedText; // eslint-disable-line
    var tempString = '';
    var decodedList = [];
    var encoding = font._encoding;
    var splittedText = inputText;
    switch (encodedText[0]) {
        case '(':
            encodedText = encodedText.substring(1, encodedText.length - 1);
            if (encodedText.indexOf('\\\n') !== -1 || encodedText.indexOf('\\(') !== -1 || encodedText.indexOf('\\)') !== -1) {
                if (encodedText.indexOf('\\\n') !== -1) {
                    encodedText = encodedText.replace(/\\\n/g, '');
                }
                if (encodedText.indexOf('\\(') !== -1) {
                    encodedText = encodedText.replace(/\\\(/g, '(');
                }
                if (encodedText.indexOf('\\)') !== -1) {
                    encodedText = encodedText.replace(/\\\)/g, ')');
                }
            }
            if (encodedText.indexOf('\\n') !== -1) {
                encodedText = encodedText.replace(/\\n/g, '\n');
            }
            if (encodedText.indexOf('\\r') !== -1) {
                encodedText = encodedText.replace(/\\r/g, '\r');
            }
            encodedText = _getLiteralString(encodedText, encoding);
            encodedText = _skipEscapeSequence(encodedText);
            decodedText = font._charsToGlyphs(encodedText);
            for (var i = 0; i < decodedText.length; i++) {
                key += decodedText[Number.parseInt(i.toString(), 10)]._unicode;
            }
            decodedText += key;
            break;
        case '[':
            splittedText = inputText;
            for (var i = 0; i < splittedText.length; i++) {
                var input = splittedText[Number.parseInt(i.toString(), 10)];
                if (input.indexOf('\\\n') !== -1) {
                    input = input.replace(/\\\n/g, '');
                }
                if (input[0] === '<') {
                    isHex = true;
                    input = input.slice(1, -1);
                }
                else if (input[0] === '(') {
                    input = input.slice(1, -1);
                }
                else if (input.length > 0) {
                    input = input.replace('\n', '');
                    decodedList.push(input);
                    continue;
                }
                if (isHex) {
                    tempString = _hexToChar(input);
                }
                else {
                    tempString = _getLiteralString(input, encoding);
                    if (tempString.indexOf('\\') !== -1) {
                        tempString = _skipEscapeSequence(tempString);
                    }
                }
                decodedText = font._charsToGlyphs(tempString);
                for (var i_1 = 0; i_1 < decodedText.length; i_1++) {
                    key += decodedText[Number.parseInt(i_1.toString(), 10)]._unicode;
                }
            }
            break;
        case '<':
            encodedText = encodedText.substring(1, encodedText.length - 1);
            tempString = _hexToChar(encodedText);
            decodedText = font._charsToGlyphs(tempString);
            for (var i = 0; i < decodedText.length; i++) {
                key += decodedText[Number.parseInt(i.toString(), 10)]._unicode;
            }
            decodedText += key;
            break;
    }
    return key;
}
/**
 * Retrieves a content stream object for a specified XObject, processing it according to the provided mode.
 *
 * @param {string[]} xObjectElement - The XObject elements to process.
 * @param {PdfPage} page - The PDF page to which the content stream belongs.
 * @param {Map<string, any>} xObjectCollection - A collection of XObject elements.
 * @param {_PdfContentParserHelper | PdfDataExtractor} data - The data extractor or content parser helper.
 * @param {_TextProcessingMode} [mode] - The mode of text processing.
 * @param {_GraphicState} [graphicState] - The current graphic state.
 * @returns {_PdfContentStream | void} The processed PDF content stream or void.
 */
export function _getXObject(xObjectElement, page, xObjectCollection, data, mode, graphicState) {
    var xobject = xObjectElement[0].replace('/', '');
    var array;
    var contentParser;
    var extractor;
    if (data instanceof _PdfContentParserHelper) {
        contentParser = data;
    }
    else {
        extractor = data;
    }
    if (xObjectCollection.has(xobject)) {
        var base = xObjectCollection.get(xobject); //eslint-disable-line
        if (base) {
            if (base instanceof _PdfContentStream) {
                array = new Uint8Array(base._bytes);
            }
            else if (base instanceof _PdfBaseStream) {
                array = base.getBytes();
            }
            if (array) {
                var parser = new _ContentParser(array);
                var recordCollection = parser._readContent();
                var childFontCollection = new Map();
                var xObjectCollection_1 = new Map(); //eslint-disable-line
                if (base.dictionary.has('Resources')) {
                    var childResources = base.dictionary.get('Resources');
                    childFontCollection = _addFontResources(childResources, childResources._crossReference);
                    xObjectCollection_1 = _getXObjectResources(childResources, childResources._crossReference);
                }
                var state = void 0;
                if (typeof (mode) !== 'undefined') {
                    if (base.dictionary.has('Matrix')) {
                        var currentState = graphicState._state._clone();
                        state = new _GraphicState(currentState);
                        var matrix = base.dictionary.get('Matrix');
                        if (matrix) {
                            state._transform(matrix);
                        }
                    }
                    else {
                        state = graphicState;
                    }
                }
                if (mode === _TextProcessingMode.textLineExtraction || mode === _TextProcessingMode.textExtraction) {
                    contentParser._processRecordCollection(recordCollection, page, childFontCollection, xObjectCollection_1, state);
                }
                else if (mode === _TextProcessingMode.redaction) {
                    var pdfStream = contentParser._processRecordCollection(recordCollection, page, // eslint-disable-line
                    childFontCollection, xObjectCollection_1, state);
                    return pdfStream;
                }
                else {
                    extractor._renderTextAsLayOut(recordCollection, page, childFontCollection, xObjectCollection_1);
                }
            }
        }
    }
}
/**
 * Parses encoded text and returns both the decoded string list and width table.
 *
 * @param {string} encodedText - The encoded text string to be parsed.
 * @param {_FontStructure} font - The font structure used to map encoded characters to glyphs.
 * @returns {object} An object containing `decodedStrings`, a list of decoded strings, and `charWidths`, a list of character widths.
 */
export function _parseEncodedText(encodedText, font) {
    var decodedList = [];
    var key = '';
    var decodedText; // eslint-disable-line
    var widths = [];
    var widthtable = [];
    var tempString = '';
    var isRotatePage = false;
    switch (encodedText[0]) {
        case '(':
            encodedText = encodedText.substring(1, encodedText.length - 1);
            if (encodedText.indexOf('\\\n') !== -1 || encodedText.indexOf('\\(') !== -1 || encodedText.indexOf('\\)') !== -1) {
                if (encodedText.indexOf('\\\n') !== -1) {
                    encodedText = encodedText.replace(/\\\n/g, '');
                }
                if (encodedText.indexOf('\\(') !== -1) {
                    encodedText = encodedText.replace(/\\\(/g, '(');
                }
                if (encodedText.indexOf('\\)') !== -1) {
                    encodedText = encodedText.replace(/\\\)/g, ')');
                }
            }
            if (encodedText.indexOf('\\n') !== -1) {
                encodedText = encodedText.replace(/\\n/g, '\n');
            }
            if (encodedText.indexOf('\\r') !== -1) {
                encodedText = encodedText.replace(/\\r/g, '\r');
            }
            encodedText = _getLiteralString(encodedText);
            encodedText = _skipEscapeSequence(encodedText);
            if (!isRotatePage) {
                decodedText = font._charsToGlyphs(encodedText);
                for (var i = 0; i < decodedText.length; i++) {
                    if (decodedText[Number.parseInt(i.toString(), 10)]._unicode.length > 1) {
                        key += decodedText[Number.parseInt(i.toString(), 10)]._unicode;
                        for (var j = 0; j < decodedText[Number.parseInt(i.toString(), 10)]._unicode.length; j++) {
                            widths.push(decodedText[Number.parseInt(i.toString(), 10)]._width);
                        }
                    }
                    else {
                        key += decodedText[Number.parseInt(i.toString(), 10)]._unicode;
                        widths.push(decodedText[Number.parseInt(i.toString(), 10)]._width);
                    }
                }
            }
            else {
                key = encodedText;
            }
            key += 's';
            decodedList.push(key);
            widthtable.push(widths);
            break;
        case '[':
            encodedText = encodedText.replace(/\\\n/g, '');
            if (encodedText.length > 1) {
                encodedText = encodedText.substring(1, encodedText.length - 1);
            }
            while (encodedText.length > 0) {
                var isHex = false;
                var listElement = '';
                var textStart = encodedText.indexOf('(');
                var textEnd = encodedText.indexOf(')');
                for (var j = textEnd + 1; j < encodedText.length; j++) {
                    if (encodedText[Number.parseInt(j.toString(), 10)] === '(') {
                        break;
                    }
                    else if (encodedText[Number.parseInt(j.toString(), 10)] === ')') {
                        textEnd = j;
                        break;
                    }
                }
                var textHexStart = encodedText.indexOf('<');
                var textHexEnd = encodedText.indexOf('>');
                if (textHexStart < textStart && textHexStart > -1) {
                    textStart = textHexStart;
                    textEnd = textHexEnd;
                    isHex = true;
                }
                if (textStart < 0) {
                    textStart = encodedText.indexOf('<');
                    textEnd = encodedText.indexOf('>');
                    if (textStart >= 0) {
                        isHex = true;
                    }
                    else {
                        decodedList.push(encodedText);
                        break;
                    }
                }
                if (textEnd < 0 && encodedText.length > 0) {
                    decodedList.push(encodedText);
                    break;
                }
                else if (textEnd > 0) {
                    while (encodedText[textEnd - 1] === '\\') {
                        if (textEnd - 1 > 0 && encodedText[textEnd - 2] === '\\') {
                            break;
                        }
                        var nextEnd = encodedText.indexOf(')', textEnd + 1);
                        if (nextEnd >= 0) {
                            textEnd = nextEnd;
                        }
                        else {
                            break;
                        }
                    }
                }
                if (textStart !== 0) {
                    listElement = encodedText.substring(0, textStart);
                    decodedList.push(listElement);
                }
                var tempString_1 = encodedText.substring(textStart + 1, textEnd);
                if (isHex) {
                    tempString_1 = _hexToChar(tempString_1);
                }
                else {
                    tempString_1 = _getLiteralString(tempString_1);
                    if (tempString_1.indexOf('\\') !== -1) {
                        tempString_1 = _skipEscapeSequence(tempString_1);
                    }
                }
                if (!isRotatePage) {
                    decodedText = font._charsToGlyphs(tempString_1);
                    for (var i = 0; i < decodedText.length; i++) {
                        if (decodedText[Number.parseInt(i.toString(), 10)]._unicode.length > 1) {
                            key += decodedText[Number.parseInt(i.toString(), 10)]._unicode;
                            for (var j = 0; j < decodedText[Number.parseInt(i.toString(), 10)]._unicode.length; j++) {
                                widths.push(decodedText[Number.parseInt(i.toString(), 10)]._width);
                            }
                        }
                        else {
                            key += decodedText[Number.parseInt(i.toString(), 10)]._unicode;
                            widths.push(decodedText[Number.parseInt(i.toString(), 10)]._width);
                        }
                    }
                }
                else {
                    key = tempString_1;
                }
                key += 's';
                decodedList.push(key);
                widthtable.push(widths);
                key = '';
                widths = [];
                encodedText = encodedText.substring(textEnd + 1);
            }
            break;
        case '<':
            encodedText = encodedText.substring(1, encodedText.length - 1);
            tempString = _hexToChar(encodedText);
            if (!isRotatePage) {
                decodedText = font._charsToGlyphs(tempString);
                for (var i = 0; i < decodedText.length; i++) {
                    key += decodedText[Number.parseInt(i.toString(), 10)]._unicode;
                    widths.push(decodedText[Number.parseInt(i.toString(), 10)]._width);
                }
            }
            else {
                key = tempString;
            }
            key += 's';
            decodedList.push(key);
            widthtable.push(widths);
            break;
    }
    return [decodedList, widthtable];
}
/**
 * Compare two arrays of numbers to determine if they are equal.
 *
 * This function checks if two arrays have the same length and
 * identical elements in the same order.
 *
 * @param {number[]} arr1 - The first array to compare.
 * @param {number[]} arr2 - The second array to compare.
 * @returns {boolean} 'true' if the arrays are equal, otherwise 'false'.
 */
export function _isArrayEqual(arr1, arr2) {
    if (arr1.length !== arr2.length) {
        return false;
    }
    for (var i = 0, ii = arr1.length; i < ii; i++) {
        if (arr1[Number.parseInt(i.toString(), 10)] !== arr2[Number.parseInt(i.toString(), 10)]) {
            return false;
        }
    }
    return true;
}
/**
 * Convert a Base64-encoded string into a Uint8Array of bytes.
 *
 * @param {string} base64String - The Base64-encoded string to decode.
 * @returns {Uint8Array} A Uint8Array containing the decoded bytes.
 *
 */
export function _base64ToUint8Array(base64String) {
    var binaryString = atob(base64String);
    var len = binaryString.length;
    var bytes = new Uint8Array(len);
    for (var i = 0; i < len; i++) {
        bytes[i] = binaryString.charCodeAt(i);
    }
    return bytes;
}
