import {escapeLatexText} from "./escape_latex"
import {BIBLIOGRAPHY_HEADERS, CATS} from "../../schema/i18n"

export class LatexExporterConvert {
    constructor(exporter, imageDB, bibDB, settings) {
        this.exporter = exporter
        this.settings = settings
        this.imageDB = imageDB
        this.bibDB = bibDB
        this.imageIds = []
        this.usedBibDB = {}
        // While walking the tree, we take note of the kinds of features That
        // are present in the file, so that we can assemble an preamble and
        // epilogue based on our findings.
        this.features = {}
        this.internalLinks = []
        this.categoryCounter = {} // counters for each type of figure (figure/table/photo)
        this.authorsTex = ''
    }

    init(docContent) {
        this.preWalkJson(docContent)
        const rawTransformation = this.walkJson(docContent)
        const body = this.postProcess(rawTransformation)
        const copyright = this.assembleCopyright()
        const preamble = this.assemblePreamble()
        const epilogue = this.assembleEpilogue()
        const latex = copyright + this.docDeclaration + preamble + this.authorsTex + '\n\\begin{document}\n' + body + epilogue + '\n\\end{document}\n'
        const returnObject = {
            latex,
            imageIds: this.imageIds,
            usedBibDB: this.usedBibDB
        }
        return returnObject
    }

    get docDeclaration() {
        return '\\documentclass{article}\n'
    }

    // Check for things needed before creating raw transform
    preWalkJson(node) {
        switch (node.type) {
        // Collect all internal links so that we only set the anchors for those
        // that are being linked to.
        case 'text':
            if (node.marks) {
                const hyperlink = node.marks.find(mark => mark.type === 'link')
                if (hyperlink) {
                    const href = hyperlink.attrs.href
                    if (href[0] === '#' && !this.internalLinks.includes(href)) {
                        this.internalLinks.push(href.slice(1))
                    }
                }
            }
            break
        }
        if (node.content) {
            node.content.forEach(child => this.preWalkJson(child))
        }
    }


    walkJson(node, options = {}) {
        let start = '', content = '', end = '',
            placeFootnotesAfterBlock = false
        switch (node.type) {
        case 'article':
            break
        case 'title':
            start += '\n\\title{'
            end = '}' + end
            break
        case 'heading_part':
            if (node.attrs.metadata === 'subtitle' && node.content) {
                start += '\n\\subtitle{'
                end = '}' + end
                this.features.subtitle = true
                options = Object.assign({}, options)
                options.ignoreHeading = true
            } else if (!options.madeTitle) {
                start += '\n\n\\maketitle\n'
                options.madeTitle = true
            }
            break
        case 'contributor':
            // Ignore - we deal with contributors_part instead.
            break
        case 'contributors_part':
            if (node.content) {
                if (node.attrs.metadata === 'authors') {
                    // TODO: deal with node.attrs.metadata === 'editors'
                    const authorsPerAffil = node.content.map(node => {
                        const author = node.attrs,
                            nameParts = []
                        let affiliation = false
                        if (author.firstname) {
                            nameParts.push(author.firstname)
                        }
                        if (author.lastname) {
                            nameParts.push(author.lastname)
                        }
                        if (nameParts.length && author.institution) {
                            affiliation = author.institution
                        } else if (author.institution) {
                            // We have an institution but no names. Use institution as name.
                            nameParts.push(author.institution)
                        }
                        return {
                            name: nameParts.join(' '),
                            affiliation,
                            email: author.email
                        }
                    }).reduce((affils, author) => {
                        const affil = author.affiliation
                        affils[affil] = affils[affil] || []
                        affils[affil].push(author)
                        return affils
                    }, {})

                    Object.values(authorsPerAffil).forEach(
                        affil => {
                            affil.forEach(
                                author => {
                                    this.authorsTex +=
                                            `\n\\author{${escapeLatexText(author.name)}${
                                                author.email ?
                                                    `\\thanks{${
                                                        escapeLatexText(author.email)
                                                    }}` :
                                                    ''
                                            }}`
                                }
                            )

                            this.authorsTex += `\n\\affil{${
                                affil[0].affiliation ?
                                    escapeLatexText(affil[0].affiliation) :
                                    ''
                            }}`
                        }
                    )
                    this.authorsTex += "\n\n"
                    this.features.authors = true
                } else {
                    if (!options.madeTitle) {
                        start += '\n\n\\maketitle\n'
                        options.madeTitle = true
                    }
                    // TODO: deal with contributor lists of non-authors properly
                    content += node.content.map(
                        contributorNode => {
                            const nameParts = []
                            if (contributorNode.attrs.firstname) {
                                nameParts.push(contributorNode.attrs.firstname)
                            }
                            if (contributorNode.attrs.lastname) {
                                nameParts.push(contributorNode.attrs.lastname)
                            }
                            if (!nameParts.length && contributorNode.attrs.institution) {
                                // We have an institution but no names. Use institution as name.
                                nameParts.push(contributorNode.attrs.institution)
                            }
                            return nameParts.join(' ')
                        }
                    ).join(', ')
                    content += "\n\n"
                }

            }

            break
        case 'tags_part':
            if (node.content) {
                if (node.attrs.metadata === 'keywords') {
                    start += '\n\\keywords{'
                    end = '}' + end
                    this.features.keywords = true
                } else if (!options.madeTitle) {
                    start += '\n\n\\maketitle\n'
                    options.madeTitle = true
                }
                content += node.content.map(
                    keyword => escapeLatexText(keyword.attrs.tag)
                ).join('\\sep ')
            }
            break
        case 'tag':
            // Ignore - we already took all the tags_part from the keywords node.
            break
        case 'richtext_part':
            if (!options.madeTitle) {
                start += '\n\n\\maketitle\n'
                options.madeTitle = true
            }
            if (node.content && node.attrs.metadata === 'abstract') {
                start += '\n\\begin{abstract}\n'
                end = '\n\\end{abstract}\n' + end
            }
            break
        case 'table_of_contents':
            start += '\n\n\\tableofcontents\n'
            break
        case 'separator_part':
        case 'table_part':
            // part separators as in page breaks should usually already be handled
            // by LaTeX and table parts will simply show the table inside of them.
            break
        case 'paragraph':
            start += '\n\n'
            end = '\n' + end
            break
        case 'heading1':
        case 'heading2':
        case 'heading3':
        case 'heading4':
        case 'heading5':
        case 'heading6': {
            if (options.ignoreHeading) {
                break
            }
            const level = parseInt(node.type.slice(-1))
            switch (level) {
            case 1:
                start += '\n\n\\section{'
                break
            case 2:
                start += '\n\n\\subsection{'
                break
            case 3:
            case 4:
            case 5:
            case 6:
                // TODO: Add support for levels 4/5/6
                start += '\n\n\\subsubsection{'
                break
            }
            end = `}\\label{${node.attrs.id}}\n\n` + end
            // Check if this heading is being linked to. If this is the case,
            // place a protected hypertarget here that does not add an extra
            // entry into the PDF TOC.
            if (this.internalLinks.includes(node.attrs.id)) {
                // Add a link target
                end = end + `\\texorpdfstring{\\protect\\hypertarget{${node.attrs.id}}{}}{}`
            }
            options = Object.assign({}, options)
            options.noLineBreak = true
            if (!options.onlyFootnoteMarkers) {
                placeFootnotesAfterBlock = true
                options.onlyFootnoteMarkers = true
                options.unplacedFootnotes = []
            }
            break
        }
        case 'code_block':
            start += '\n\\begin{code}\n\n'
            end = '\n\n\\end{code}\n' + end
            this.features.code = true
            break
        case 'blockquote':
            start += '\n\\begin{quote}\n\n'
            end = '\n\n\\end{quote}\n' + end
            break
        case 'ordered_list':
            if (node.attrs.order !== 1) {
                start += `\n\\begin{enumerate}[start=${node.attrs.order}]`
                this.features.orderedListStart = true
            } else {
                start += '\n\\begin{enumerate}'
            }
            end = '\n\\end{enumerate}' + end
            if (!options.onlyFootnoteMarkers) {
                placeFootnotesAfterBlock = true
                options = Object.assign({}, options)
                options.onlyFootnoteMarkers = true
                options.unplacedFootnotes = []
            }
            break
        case 'bullet_list':
            start += '\n\\begin{itemize}'
            end = '\n\\end{itemize}' + end
            if (!options.onlyFootnoteMarkers) {
                placeFootnotesAfterBlock = true
                options = Object.assign({}, options)
                options.onlyFootnoteMarkers = true
                options.unplacedFootnotes = []
            }
            break
        case 'list_item':
            start += '\n\\item '
            end = '\n' + end
            break
        case 'footnote':
            if (options.onlyFootnoteMarkers) {
                // We are inside a headline or a list and can only place a
                // footnote marker here. The footnote will have to be put
                // beyond the block node instead.
                start += '\\protect\\footnotemark{}'
                options.unplacedFootnotes.push(node.attrs.footnote)
            } else {
                if (!node.attrs.footnote.find(par => par.type === 'figure')) {
                    // LaTeX doesn't allow figures in footnotes, so well move
                    // this footnote into the regular text.
                    start += '\\footnote{'
                    end = '}' + end
                }
                let fnContent = ''
                node.attrs.footnote.forEach(footPar => {
                    fnContent += this.walkJson(footPar, options)
                })
                content += fnContent.replace(/^\s+|\s+$/g, '')
            }
            break
        case 'text': {
            let strong, em, underline, hyperlink
            // Check for hyperlink, bold/strong, italic/em and underline
            if (node.marks) {
                strong = node.marks.find(mark => mark.type === 'strong')
                em = node.marks.find(mark => mark.type === 'em')
                underline = node.marks.find(mark => mark.type === 'underline')
                hyperlink = node.marks.find(mark => mark.type === 'link')
            }
            if (em) {
                start += '\\emph{'
                end = '}' + end
            }
            if (strong) {
                start += '\\textbf{'
                end = '}' + end
            }
            if (underline) {
                start += '\\underline{'
                end = '}' + end
            }
            if (hyperlink) {
                const href = hyperlink.attrs.href
                if (href[0] === '#') {
                    // Internal link
                    start += `\\hyperlink{${href.slice(1)}}{`
                } else {
                    // External link
                    start += `\\href{${href}}{`
                }
                end = '}' + end
                this.features.hyperlinks = true
            }
            content += escapeLatexText(node.text)
            break
        }
        case 'cross_reference': {
            content += `\\hyperref[${node.attrs.id}]{${node.attrs.title || 'MISSING TARGET'}}`
            this.features.hyperlinks = true
            break
        }
        case 'citation': {
            const references = node.attrs.references
            const format = node.attrs.format
            let citationCommand = '\\' + format

            if (references.length > 1 &&
                    references.every(ref => !ref.locator && !ref.prefix)
            ) {
                // multi source citation without page numbers or text before.
                const citationEntryKeys = []

                const allCitationItemsPresent = references.map(ref => ref.id).every(
                    citationEntry => {
                        const bibDBEntry = this.bibDB.db[citationEntry]
                        if (bibDBEntry) {
                            if (!bibDBEntry) {
                                // Not present in bibliography database, skip it.
                                // TODO: Throw an error?
                                return false
                            }
                            if (!this.usedBibDB[citationEntry]) {
                                const citationKey = this.createUniqueCitationKey(
                                    bibDBEntry.entry_key
                                )
                                this.usedBibDB[citationEntry] = Object.assign({}, bibDBEntry)
                                this.usedBibDB[citationEntry].entry_key = citationKey
                            }
                            citationEntryKeys.push(this.usedBibDB[citationEntry].entry_key)
                        }
                        return true
                    }
                )
                if (allCitationItemsPresent) {
                    citationCommand += `{${citationEntryKeys.join(',')}}`
                } else {
                    citationCommand = false
                }
            } else {
                if (references.length > 1) {
                    citationCommand += 's' // Switching from \autocite to \autocites
                }

                const allCitationItemsPresent = references.every(
                    ref => {
                        const bibDBEntry = this.bibDB.db[ref.id]
                        if (!bibDBEntry) {
                            // Not present in bibliography database, skip it.
                            // TODO: Throw an error?
                            return false
                        }

                        if (ref.prefix) {
                            citationCommand += `[${ref.prefix}]`
                            if (!ref.locator) {
                                citationCommand += '[]'
                            }
                        }
                        if (ref.locator) {
                            citationCommand += `[${ref.locator}]`
                        }
                        citationCommand += '{'

                        if (!this.usedBibDB[ref.id]) {
                            const citationKey = this.createUniqueCitationKey(
                                bibDBEntry.entry_key
                            )
                            this.usedBibDB[ref.id] = Object.assign({}, bibDBEntry)
                            this.usedBibDB[ref.id].entry_key = citationKey
                        }
                        citationCommand += this.usedBibDB[ref.id].entry_key
                        citationCommand += '}'

                        return true
                    }
                )

                if (!allCitationItemsPresent) {
                    citationCommand = false
                }
            }
            if (citationCommand) {
                content += citationCommand
                this.features.citations = true
            }
            break
        }
        case 'figure': {
            const category = node.attrs.category
            const captionContent = node.attrs.caption ? node.content.find(node => node.type === 'figure_caption')?.content || [] : []
            let caption
            if (category !== 'none') {
                if (!this.categoryCounter[category]) {
                    this.categoryCounter[category] = 1
                }
                const catCount = this.categoryCounter[category]++
                const catLabel = `${CATS[category][this.settings.language]} ${catCount}`
                if (captionContent.length) {
                    caption = `${catLabel}: ${captionContent.map(node => this.walkJson(node)).join('')}`
                } else {
                    caption = catLabel
                }
            } else {
                caption = captionContent.map(node => this.walkJson(node)).join('')
            }
            let innerFigure = ''
            let aligned = 'left'
            if (node.attrs.width !== '100') {
                aligned = node.attrs.aligned
            }
            if (aligned === 'center') {
                start += '\n\n\\begin{center}'
                end = '\n\n\\end{center}\n' + end
            } else if (aligned === 'right') {
                start += '\n\n{\\raggedleft' // This is not a typo - raggedleft = aligned: right
                end = '\n\n}\n' + end
            } // aligned === 'left' is default
            let copyright
            const image = node.content.find(node => node.type === 'image')?.attrs.image || false
            if (image) {
                this.imageIds.push(image)
                const imageDBEntry = this.imageDB.db[image],
                    filePathName = imageDBEntry.image,
                    filename = filePathName.split('/').pop()
                copyright = imageDBEntry.copyright
                if (filename.split('.').pop() === 'svg') {
                    innerFigure += `\\includesvg[width=${parseInt(node.attrs.width) / 100}\\textwidth]{${filename}}\n`
                    this.features.SVGs = true
                } else {
                    innerFigure += `\\scaledgraphics{${filename}}{${parseInt(node.attrs.width) / 100}}\n`
                    this.features.images = true
                }
            } else {
                const equation = node.content.find(node => node.type === 'figure_equation')?.attrs.equation || ''
                innerFigure += `\\begin{displaymath}\n${equation}\n\\end{displaymath}\n`
            }
            if (category === 'table') {
                start += `\n\\begin{table}\n`
                content += caption.length ? `\\caption*{${caption}}` : ''
                content += `\\label{${node.attrs.id}}\n${innerFigure}`
                end = `\\end{table}\n` + end
            } else { // TODO: handle photo figure types in a special way
                start += `\n\\begin{figure}\n`
                content += `${innerFigure}${caption.length ? `\\caption*{${caption}}` : ''}\\label{${node.attrs.id}}\n`
                end = `\\end{figure}\n` + end
            }
            if (copyright?.holder) {
                content += `% © ${copyright.year ? copyright.year : new Date().getFullYear()} ${copyright.holder}\n`
            }
            if (copyright?.licenses.length) {
                copyright.licenses.forEach(
                    license => {
                        content += `% ${license.title}: ${license.url}${license.start ? ` (${license.start})\n` : ''}\n`
                    }
                )
            }
            if (this.internalLinks.includes(node.attrs.id)) {
                // Add a link target
                end = `\\texorpdfstring{\\protect\\hypertarget{${node.attrs.id}}{}}{}\n` + end
            }
            this.features.captions = true
            break
        }
        case 'figure_caption':
            // We are already dealing with this in the figure. Prevent content from being added a second time.
            return ''
        case 'figure_equation':
            // We are already dealing with this in the figure.
            break
        case 'image':
            // We are already dealing with this in the figure.
            break
        case 'table':
            if (node.content?.length) {
                const category = node.attrs.category

                const captionContent = node.attrs.caption ? node.content[0].content || [] : []
                let caption
                if (category !== 'none') {
                    if (!this.categoryCounter[category]) {
                        this.categoryCounter[category] = 1
                    }
                    const catCount = this.categoryCounter[category]++
                    const catLabel = `${CATS[category][this.settings.language]} ${catCount}`
                    if (captionContent.length) {
                        caption = `${catLabel}: ${captionContent.map(node => this.walkJson(node)).join('')}`
                    } else {
                        caption = catLabel
                    }
                } else {
                    caption = captionContent.map(node => this.walkJson(node)).join('')
                }

                const columns = node.content[1].content[0].content.reduce(
                    (columns, node) => columns + node.attrs.colspan,
                    0
                )
                let aligned = 'left'
                if (node.attrs.width !== '100') {
                    aligned = node.attrs.aligned
                }
                if (aligned === 'center') {
                    start += '\n\n\\begin{center}'
                    end = '\n\n\\end{center}\n' + end
                } else if (aligned === 'right') {
                    start += '\n\n{\\raggedleft' // This is not a typo - raggedleft = aligned: right
                    end = '\n\n}\n'
                } // aligned === 'left' is default
                if (caption.length) {
                    start += `\n\\begin{table}\n`
                    start += `\\caption*{${caption}}\\label{${node.attrs.id}}`
                    end = `\\end{table}\n` + end
                    this.features.captions = true
                }
                start += `\n\n\\begin{tabu} to ${
                    node.attrs.width === '100' ? '' : parseInt(node.attrs.width) / 100
                }\\textwidth { |${'X|'.repeat(columns)} }\n\\hline\n\n`
                end = `\\hline\n\n\\end{tabu}` + end
                this.features.tables = true
            }
            break
        case 'table_body':
            // Pass through to table.
            break
        case 'table_caption':
            // We already deal with this in 'table'.
            return ''
        case 'table_row':
            end += ' \\\\\n'
            break
        case 'table_cell':
        case 'table_header':
            if (node.attrs.colspan > 1) {
                start += `\\multicolumn{${node.attrs.colspan}}{c}{`
                end += '}'
            }
            // TODO: these multirow outputs don't work very well with longer text.
            // If there is another alternative, please change!
            if (node.attrs.rowspan > 1) {
                start += `\\multirow{${node.attrs.rowspan}}{*}{`
                end += '}'
                this.features.rowspan = true
            }
            end += ' & '
            break
        case 'equation':
            content += `$${node.attrs.equation}$`
            break
        case 'hard_break':
            if (!options.noLineBreak) {
                content += '\n\n'
            }
            break
        default:
            break
        }

        if (node.content) {
            node.content.forEach(child => {
                content += this.walkJson(child, options)
            })
        }
        if (
            placeFootnotesAfterBlock &&
            options.unplacedFootnotes?.length
        ) {
            // There are footnotes that needed to be placed behind the node.
            // This happens in the case of headlines and lists.
            end += `\\addtocounter{footnote}{-${(options.unplacedFootnotes.length)}}`
            options.unplacedFootnotes.forEach(footnote => {
                end += '\\stepcounter{footnote}\n'
                end += '\\footnotetext{'
                let fnContent = ''
                footnote.forEach(footPar => {
                    fnContent += this.walkJson(footPar, options)
                })
                end += fnContent.replace(/^\s+|\s+$/g, '')
                end += '}'
            })
            options.unplacedFootnotes = []
        }
        if (['table_cell', 'table_header'].includes(node.type) && node.attrs.rowspan > 1) {
            // \multirow doesn't allow multiple paragraphs.
            content = content.trim().replace(/\n\n/g, ' \\\\ ')
        }

        return start + content + end
    }

    // The database doesn't ensure that citation keys are unique.
    // So here we need to make sure that the same key is not used twice in one
    // document.
    createUniqueCitationKey(suggestedKey) {
        const usedKeys = Object.keys(this.usedBibDB).map(key => {
            return this.usedBibDB[key].entry_key
        })
        if (usedKeys.includes(suggestedKey)) {
            suggestedKey += 'X'
            return this.createUniqueCitationKey(suggestedKey)
        } else {
            return suggestedKey
        }
    }

    postProcess(latex) {
        return latex
        // join blocks of the same type that follow oneanother.
            .replace(/\\end{code}\n\n\\begin{code}\n\n/g, '')
            .replace(/\\end{quote}\n\n\\begin{quote}\n\n/g, '')
        // Remove the last divider in any any table row.
            .replace(/& {2}\\\\/g, '\\\\')
        // Remove new lines between table cells.
            .replace(/\n & \n\n/g, ' & ')
        // Remove new lines within itemization
            .replace(/\\item \n\n/g, '\\item ')
    }

    assembleEpilogue() {
        let epilogue = ''
        if (this.features.citations) {
            const bibliographyHeader = this.settings.bibliography_header[this.settings.language] || BIBLIOGRAPHY_HEADERS[this.settings.language]
            epilogue += `\n\n\\printbibliography[title={${escapeLatexText(bibliographyHeader)}}]`
        }
        return epilogue
    }

    assembleCopyright() {
        let note = ''
        if (this.settings.copyright) {
            if (this.settings.copyright.holder) {
                note += `% © ${this.settings.copyright.year ? this.settings.copyright.year : new Date().getFullYear()} ${this.settings.copyright.holder}\n`
            }
            if (this.settings.copyright.licenses.length) {
                this.settings.copyright.licenses.forEach(
                    license => {
                        note += `% ${license.url}${license.start ? ` (${license.start})` : ''}\n`
                    }
                )
            }
        }

        if (note.length) {
            note += '\n\n'
        }
        return note
    }

    assemblePreamble() {
        let preamble = ''

        if (this.features.subtitle) {
            preamble += `
                \n\\usepackage{titling}
                \n\\newcommand{\\subtitle}[1]{%
                    \n\t\\posttitle{%
                        \n\t\t\\par\\end{center}
                        \n\t\t\\begin{center}\\large#1\\end{center}
                        \n\t\t\\vskip 0.5em}%
                }
            `
        }
        if (this.features.authors) {
            preamble += `
                \n\\usepackage{authblk}
                \n\\makeatletter
                \n\\let\\@fnsymbol\\@alph
                \n\\makeatother
            `
        }

        if (this.features.keywords) {
            preamble += `
                \n\\def\\keywords{\\vspace{.5em}
                \n{\\textit{Keywords}:\\,\\relax%
                \n}}
                \n\\def\\endkeywords{\\par}
                \n\\newcommand{\\sep}{, }
            `
        }

        if (this.features.hyperlinks) {
            preamble += '\n\\usepackage{hyperref}'
        }

        if (this.features.captions) {
            preamble += '\n\\usepackage{caption}'
        }

        if (this.features.citations) {
            preamble += `
                \n\\usepackage[backend=biber,hyperref=false,citestyle=authoryear,bibstyle=authoryear]{biblatex}
                \n\\bibliography{bibliography}
            `
        }

        if (this.features.SVGs) {
            preamble += '\n\\usepackage{svg}'
        }

        if (this.features.images) {
            preamble += '\n\\usepackage{graphicx}'
            // The following scales graphics down to text width, but not scaling them up if they are smaller
            preamble += `
                \n\\usepackage{calc}
                \n\\newlength{\\imgwidth}
                \n\\newcommand\\scaledgraphics[2]{%
                \n\\settowidth{\\imgwidth}{\\includegraphics{#1}}%
                \n\\setlength{\\imgwidth}{\\minof{\\imgwidth}{#2\\textwidth}}%
                \n\\includegraphics[width=\\imgwidth,height=\\textheight,keepaspectratio]{#1}%
                \n}
            `
        }

        if (this.features.tables) {
            preamble += '\n\\usepackage{tabu}'
        }

        if (this.features.orderedListStart) {
            preamble += '\n\\usepackage{enumitem}'
        }

        if (this.features.rowspan) {
            preamble += '\n\\usepackage{multirow}'
        }

        if (this.features.code) {
            // See https://tex.stackexchange.com/questions/445424/making-a-multiline-code-environment
            preamble += `
            \n\\usepackage{xcolor}
            \\definecolor{mygray}{gray}{0.9}
            \\usepackage{fvextra}
            \\usepackage{tcolorbox}
            \\newenvironment{code}%
            {\\VerbatimEnvironment
            \\begin{tcolorbox}[colback=mygray, boxsep=0pt, arc=0pt, boxrule=0pt]
            \\begin{Verbatim}[fontsize=\\scriptsize, commandchars=\\\\\\{\\},
            breaklines, breakafter=*, breaksymbolsep=0.5em,
            breakaftersymbolpre={\\,\\tiny\\ensuremath{\\rfloor}}]}%
            {\\end{Verbatim}%
             \\end{tcolorbox}}
            `

        }

        return preamble

    }

}
