# This file is part of lyx2lyx
# Copyright (C) 2015 The LyX team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

"""Convert files to the file format generated by lyx 2.2"""

import os
import re

# Uncomment only what you need to import, please.
from lyx2lyx_tools import (
    add_to_preamble,
    convert_info_insets,
    get_ert,
    length_in_bp,
    lyx2latex,
    lyx2verbatim,
    put_cmd_in_ert,
    revert_language,
)
from parser_tools import (
    check_token,
    del_complete_lines,
    find_end_of_inset,
    find_end_of_layout,
    find_nonempty_line,
    find_re,
    find_substring,
    find_token,
    find_token_backwards,
    get_containing_inset,
    get_containing_layout,
    get_quoted_value,
    get_value,
    is_in_inset,
    set_bool_value,
)

####################################################################
# Private helper functions


def revert_Argument_to_TeX_brace(document, line, endline, n, nmax, environment, opt, nolastopt):
    r"""
    Reverts an InsetArgument to TeX-code
    usage:
    revert_Argument_to_TeX_brace(document, LineOfBegin, LineOfEnd, StartArgument, EndArgument, isEnvironment, isOpt, notLastOpt)
    LineOfBegin is the line  of the \begin_layout or \begin_inset statement
    LineOfEnd is the line  of the \end_layout or \end_inset statement, if "0" is given, the end of the file is used instead
    StartArgument is the number of the first argument that needs to be converted
    EndArgument is the number of the last argument that needs to be converted or the last defined one
    isEnvironment must be true, if the layout is for a LaTeX environment
    isOpt must be true, if the argument is an optional one
    notLastOpt must be true if the argument is mandatory and followed by optional ones
    """
    lineArg = 0
    wasOpt = False
    while lineArg != -1 and n < nmax + 1:
        lineArg = find_token(document.body, "\\begin_inset Argument " + str(n), line)
        if lineArg > endline and endline != 0:
            return wasOpt
        if lineArg != -1:
            beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
            # we have to assure that no other inset is in the Argument
            beginInset = find_token(document.body, "\\begin_inset", beginPlain)
            endInset = find_token(document.body, "\\end_inset", beginPlain)
            k = beginPlain + 1
            l = k
            while beginInset < endInset and beginInset != -1:
                beginInset = find_token(document.body, "\\begin_inset", k)
                endInset = find_token(document.body, "\\end_inset", l)
                k = beginInset + 1
                l = endInset + 1
            if environment == False:
                if opt == False:
                    document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("}")
                    document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("{")
                    wasOpt = False
                else:
                    document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("]")
                    document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("[")
                    wasOpt = True
            else:
                if opt == False:
                    document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("}")
                    document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("{")
                    wasOpt = False
                else:
                    document.body[endInset - 2 : endInset + 1] = put_cmd_in_ert("]")
                    document.body[lineArg : beginPlain + 1] = put_cmd_in_ert("[")
                    wasOpt = True
            n += 1
    return wasOpt


###############################################################################
###
### Conversion and reversion routines
###
###############################################################################


def convert_longtable_label_internal(document, forward):
    """
    Convert reference to "LongTableNoNumber" into "Unnumbered" if forward is True
    else revert it.
    """
    old_reference = "\\begin_inset Caption LongTableNoNumber"
    new_reference = "\\begin_inset Caption Unnumbered"

    # if the purpose is to revert swap the strings roles
    if not forward:
        old_reference, new_reference = new_reference, old_reference

    i = 0
    while True:
        i = find_token(document.body, old_reference, i)

        if i == -1:
            return

        document.body[i] = new_reference


def convert_longtable_label(document):
    convert_longtable_label_internal(document, True)


def revert_longtable_label(document):
    convert_longtable_label_internal(document, False)


def convert_separator(document):
    """
    Convert layout separators to separator insets and add (LaTeX) paragraph
    breaks in order to mimic previous LaTeX export.
    """

    parins = ["\\begin_inset Separator parbreak", "\\end_inset", ""]
    parlay = [
        "\\begin_layout Standard",
        "\\begin_inset Separator parbreak",
        "\\end_inset",
        "",
        "\\end_layout",
        "",
    ]
    sty_dict = {
        "family": "default",
        "series": "default",
        "shape": "default",
        "size": "default",
        "bar": "default",
        "color": "inherit",
    }

    i = 0
    while True:
        i = find_token(document.body, "\\begin_deeper", i)
        if i == -1:
            break

        j = find_token_backwards(document.body, "\\end_layout", i - 1)
        if j != -1:
            # reset any text style before inserting the inset
            lay = get_containing_layout(document.body, j - 1)
            if lay != False:
                content = "\n".join(document.body[lay[1] : lay[2]])
                for val in list(sty_dict.keys()):
                    if content.find("\\%s" % val) != -1:
                        document.body[j:j] = [f"\\{val} {sty_dict[val]}"]
                        i = i + 1
                        j = j + 1
            document.body[j:j] = parins
            i = i + len(parins) + 1
        else:
            i = i + 1

    i = 0
    while True:
        i = find_token(document.body, "\\align", i)
        if i == -1:
            break

        lay = get_containing_layout(document.body, i)
        if lay != False and lay[0] == "Plain Layout":
            i = i + 1
            continue

        j = find_token_backwards(document.body, "\\end_layout", i - 1)
        if j != -1:
            # Very old LyX files do not have Plain Layout in insets (but Standard).
            # So we additionally check here if there is no inset boundary
            # between the previous layout and this one.
            n = find_token(document.body, "\\end_inset", j, lay[1])
            if n != -1:
                i = i + 1
                continue
            lay = get_containing_layout(document.body, j - 1)
            if (
                lay != False
                and lay[0] == "Standard"
                and find_token(document.body, "\\align", lay[1], lay[2]) == -1
                and find_token(document.body, "\\begin_inset VSpace", lay[1], lay[2]) == -1
            ):
                # reset any text style before inserting the inset
                content = "\n".join(document.body[lay[1] : lay[2]])
                for val in list(sty_dict.keys()):
                    if content.find("\\%s" % val) != -1:
                        document.body[j:j] = [f"\\{val} {sty_dict[val]}"]
                        i = i + 1
                        j = j + 1
                document.body[j:j] = parins
                i = i + len(parins) + 1
            else:
                i = i + 1
        else:
            i = i + 1

    regexp = re.compile(
        r"^\\begin_layout (?:(-*)|(\s*))(Separator|EndOfSlide)(?:(-*)|(\s*))$",
        re.IGNORECASE,
    )

    i = 0
    while True:
        i = find_re(document.body, regexp, i)
        if i == -1:
            return

        j = find_end_of_layout(document.body, i)
        if j == -1:
            document.warning("Malformed LyX document: Missing `\\end_layout'.")
            return

        lay = get_containing_layout(document.body, j - 1)
        if lay != False:
            lines = document.body[lay[3] : lay[2]]
        else:
            lines = []

        document.body[i : j + 1] = parlay
        if len(lines) > 0:
            document.body[i + 1 : i + 1] = lines

        i = i + len(parlay) + len(lines) + 1


def revert_separator(document):
    "Revert separator insets to layout separators"

    beamer_classes = ["beamer", "article-beamer", "scrarticle-beamer"]
    if document.textclass in beamer_classes:
        beglaysep = "\\begin_layout Separator"
    else:
        beglaysep = "\\begin_layout --Separator--"

    parsep = [beglaysep, "", "\\end_layout", ""]
    comert = [
        "\\begin_inset ERT",
        "status collapsed",
        "",
        "\\begin_layout Plain Layout",
        "%",
        "\\end_layout",
        "",
        "\\end_inset",
        "",
    ]
    empert = [
        "\\begin_inset ERT",
        "status collapsed",
        "",
        "\\begin_layout Plain Layout",
        " ",
        "\\end_layout",
        "",
        "\\end_inset",
        "",
    ]

    i = 0
    while True:
        i = find_token(document.body, "\\begin_inset Separator", i)
        if i == -1:
            return

        lay = get_containing_layout(document.body, i)
        if lay == False:
            document.warning(
                "Malformed LyX document: Can't convert separator inset at line " + str(i)
            )
            i = i + 1
            continue

        layoutname = lay[0]
        beg = lay[1]
        end = lay[2]
        kind = get_value(document.body, "\\begin_inset Separator", i, i + 1, "plain").split()[1]
        before = document.body[beg + 1 : i]
        something_before = len(before) > 0 and len("".join(before)) > 0
        j = find_end_of_inset(document.body, i)
        after = document.body[j + 1 : end]
        something_after = len(after) > 0 and len("".join(after)) > 0
        if kind == "plain":
            beg = beg + len(before) + 1
        elif something_before:
            document.body[i:i] = ["\\end_layout", ""]
            i = i + 2
            j = j + 2
            beg = i
            end = end + 2

        if kind == "plain":
            if something_after:
                document.body[beg : j + 1] = empert
                i = i + len(empert)
            else:
                document.body[beg : j + 1] = comert
                i = i + len(comert)
        else:
            if something_after:
                if layoutname == "Standard":
                    if not something_before:
                        document.body[beg : j + 1] = parsep
                        i = i + len(parsep)
                        document.body[i:i] = ["", "\\begin_layout Standard"]
                        i = i + 2
                    else:
                        document.body[beg : j + 1] = ["\\begin_layout Standard"]
                        i = i + 1
                else:
                    document.body[beg : j + 1] = ["\\begin_deeper"]
                    i = i + 1
                    end = end + 1 - (j + 1 - beg)
                    if not something_before:
                        document.body[i:i] = parsep
                        i = i + len(parsep)
                        end = end + len(parsep)
                    document.body[i:i] = ["\\begin_layout Standard"]
                    document.body[end + 2 : end + 2] = ["", "\\end_deeper", ""]
                    i = i + 4
            else:
                next_par_is_aligned = False
                k = find_nonempty_line(document.body, end + 1)
                if k != -1 and check_token(document.body[k], "\\begin_layout"):
                    lay = get_containing_layout(document.body, k)
                    next_par_is_aligned = (
                        lay != False
                        and find_token(document.body, "\\align", lay[1], lay[2]) != -1
                    )
                if (
                    k != -1
                    and not next_par_is_aligned
                    and not check_token(document.body[k], "\\end_deeper")
                    and not check_token(document.body[k], "\\begin_deeper")
                ):
                    if layoutname == "Standard":
                        document.body[beg : j + 1] = [beglaysep]
                        i = i + 1
                    else:
                        document.body[beg : j + 1] = ["\\begin_deeper", beglaysep]
                        end = end + 2 - (j + 1 - beg)
                        document.body[end + 1 : end + 1] = ["", "\\end_deeper", ""]
                        i = i + 3
                else:
                    if something_before:
                        del document.body[i : end + 1]
                    else:
                        del document.body[i : end - 1]

        i = i + 1


def convert_parbreak(document):
    """
    Convert parbreak separators not specifically used to separate
    environments to latexpar separators.
    """
    parbreakinset = "\\begin_inset Separator parbreak"
    i = 0
    while True:
        i = find_token(document.body, parbreakinset, i)
        if i == -1:
            return
        lay = get_containing_layout(document.body, i)
        if lay == False:
            document.warning(
                "Malformed LyX document: " "Can't convert separator inset at line %d" % i
            )
            i += 1
            continue
        if lay[0] == "Standard":
            # Convert only if not alone in the paragraph
            k1 = find_nonempty_line(document.body, lay[1] + 1, i + 1)
            k2 = find_nonempty_line(document.body, i + 1, lay[2])
            if (k1 < i) or (k2 > i + 1) or not check_token(document.body[i], parbreakinset):
                document.body[i] = document.body[i].replace("parbreak", "latexpar")
        else:
            document.body[i] = document.body[i].replace("parbreak", "latexpar")
        i += 1


def revert_parbreak(document):
    """
    Revert latexpar separators to parbreak separators.
    """
    i = 0
    while True:
        i = find_token(document.body, "\\begin_inset Separator latexpar", i)
        if i == -1:
            return
        document.body[i] = document.body[i].replace("latexpar", "parbreak")
        i += 1


def revert_smash(document):
    "Set amsmath to on if smash commands are used"

    commands = ["smash[t]", "smash[b]", "notag"]
    i = find_token(document.header, "\\use_package amsmath", 0)
    if i == -1:
        document.warning("Malformed LyX document: Can't find \\use_package amsmath.")
        return
    value = get_value(document.header, "\\use_package amsmath", i).split()[1]
    if value != "1":
        # nothing to do if package is not auto but on or off
        return
    j = 0
    while True:
        j = find_token(document.body, "\\begin_inset Formula", j)
        if j == -1:
            return
        k = find_end_of_inset(document.body, j)
        if k == -1:
            document.warning(
                "Malformed LyX document: Can't find end of Formula inset at line " + str(j)
            )
            j += 1
            continue
        code = "\n".join(document.body[j:k])
        for c in commands:
            if code.find("\\%s" % c) != -1:
                # set amsmath to on, since it is loaded by the newer format
                document.header[i] = "\\use_package amsmath 2"
                return
        j = k


def revert_swissgerman(document):
    "Set language german-ch-old to german"
    i = 0
    if document.language == "german-ch-old":
        document.language = "german"
        i = find_token(document.header, "\\language", 0)
        if i != -1:
            document.header[i] = "\\language german"
    j = 0
    while True:
        j = find_token(document.body, "\\lang german-ch-old", j)
        if j == -1:
            return
        document.body[j] = document.body[j].replace("\\lang german-ch-old", "\\lang german")
        j = j + 1


def revert_use_package(document, pkg, commands, oldauto, supported):
    # oldauto defines how the version we are reverting to behaves:
    # if it is true, the old version uses the package automatically.
    # if it is false, the old version never uses the package.
    # If "supported" is true, the target version also supports this
    # package natively.
    regexp = re.compile(r"(\\use_package\s+%s)" % pkg)
    p = find_re(document.header, regexp, 0)
    value = "1"  # default is auto
    if p != -1:
        value = get_value(document.header, "\\use_package", p).split()[1]
        if not supported:
            del document.header[p]
    if value == "2" and not supported:  # on
        add_to_preamble(document, ["\\usepackage{" + pkg + "}"])
    elif value == "1" and not oldauto:  # auto
        i = 0
        while True:
            i = find_token(document.body, "\\begin_inset Formula", i)
            if i == -1:
                return
            j = find_end_of_inset(document.body, i)
            if j == -1:
                document.warning(
                    "Malformed LyX document: Can't find end of Formula inset at line " + str(i)
                )
                i += 1
                continue
            code = "\n".join(document.body[i:j])
            for c in commands:
                if code.find("\\%s" % c) != -1:
                    if supported:
                        document.header[p] = "\\use_package " + pkg + " 2"
                    else:
                        add_to_preamble(document, ["\\usepackage{" + pkg + "}"])
                    return
            i = j


mathtools_commands = [
    "xhookrightarrow",
    "xhookleftarrow",
    "xRightarrow",
    "xrightharpoondown",
    "xrightharpoonup",
    "xrightleftharpoons",
    "xLeftarrow",
    "xleftharpoondown",
    "xleftharpoonup",
    "xleftrightarrow",
    "xLeftrightarrow",
    "xleftrightharpoons",
    "xmapsto",
]


def revert_xarrow(document):
    "remove use_package mathtools"
    revert_use_package(document, "mathtools", mathtools_commands, False, True)


def revert_beamer_lemma(document):
    "Reverts beamer lemma layout to ERT"

    beamer_classes = ["beamer", "article-beamer", "scrarticle-beamer"]
    if document.textclass not in beamer_classes:
        return

    consecutive = False
    i = 0
    while True:
        i = find_token(document.body, "\\begin_layout Lemma", i)
        if i == -1:
            return
        j = find_end_of_layout(document.body, i)
        if j == -1:
            document.warning("Malformed LyX document: Can't find end of Lemma layout")
            i += 1
            continue
        arg1 = find_token(document.body, "\\begin_inset Argument 1", i, j)
        endarg1 = find_end_of_inset(document.body, arg1)
        arg2 = find_token(document.body, "\\begin_inset Argument 2", i, j)
        endarg2 = find_end_of_inset(document.body, arg2)
        subst1 = []
        subst2 = []
        if arg1 != -1:
            beginPlain1 = find_token(
                document.body, "\\begin_layout Plain Layout", arg1, endarg1
            )
            if beginPlain1 == -1:
                document.warning("Malformed LyX document: Can't find arg1 plain Layout")
                i += 1
                continue
            endPlain1 = find_end_of_inset(document.body, beginPlain1)
            content1 = document.body[beginPlain1 + 1 : endPlain1 - 2]
            subst1 = put_cmd_in_ert("<") + content1 + put_cmd_in_ert(">")
        if arg2 != -1:
            beginPlain2 = find_token(
                document.body, "\\begin_layout Plain Layout", arg2, endarg2
            )
            if beginPlain2 == -1:
                document.warning("Malformed LyX document: Can't find arg2 plain Layout")
                i += 1
                continue
            endPlain2 = find_end_of_inset(document.body, beginPlain2)
            content2 = document.body[beginPlain2 + 1 : endPlain2 - 2]
            subst2 = put_cmd_in_ert("[") + content2 + put_cmd_in_ert("]")

        # remove Arg insets
        if arg1 < arg2:
            del document.body[arg2 : endarg2 + 1]
            if arg1 != -1:
                del document.body[arg1 : endarg1 + 1]
        if arg2 < arg1:
            del document.body[arg1 : endarg1 + 1]
            if arg2 != -1:
                del document.body[arg2 : endarg2 + 1]

        # index of end layout has probably changed
        j = find_end_of_layout(document.body, i)
        if j == -1:
            document.warning("Malformed LyX document: Can't find end of Lemma layout")
            i += 1
            continue

        begcmd = []

        # if this is not a consecutive env, add start command
        if not consecutive:
            begcmd = put_cmd_in_ert("\\begin{lemma}")

        # has this a consecutive lemma?
        consecutive = document.body[j + 2] == "\\begin_layout Lemma"

        # if this is not followed by a consecutive env, add end command
        if not consecutive:
            document.body[j : j + 1] = put_cmd_in_ert("\\end{lemma}") + ["\\end_layout"]

        document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd + subst1 + subst2

        i = j


def revert_question_env(document):
    """
    Reverts question and question* environments of
    theorems-ams-extended-bytype module to ERT
    """

    # Do we use theorems-ams-extended-bytype module?
    if "theorems-ams-extended-bytype" not in document.get_module_list():
        return

    consecutive = False
    i = 0
    while True:
        i = find_token(document.body, "\\begin_layout Question", i)
        if i == -1:
            return

        starred = document.body[i] == "\\begin_layout Question*"

        j = find_end_of_layout(document.body, i)
        if j == -1:
            document.warning("Malformed LyX document: Can't find end of Question layout")
            i += 1
            continue

        # if this is not a consecutive env, add start command
        begcmd = []
        if not consecutive:
            if starred:
                begcmd = put_cmd_in_ert("\\begin{question*}")
            else:
                begcmd = put_cmd_in_ert("\\begin{question}")

        # has this a consecutive theorem of same type?
        consecutive = False
        if starred:
            consecutive = document.body[j + 2] == "\\begin_layout Question*"
        else:
            consecutive = document.body[j + 2] == "\\begin_layout Question"

        # if this is not followed by a consecutive env, add end command
        if not consecutive:
            if starred:
                document.body[j : j + 1] = put_cmd_in_ert("\\end{question*}") + ["\\end_layout"]
            else:
                document.body[j : j + 1] = put_cmd_in_ert("\\end{question}") + ["\\end_layout"]

        document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd

        add_to_preamble(document, "\\providecommand{\\questionname}{Question}")

        if starred:
            add_to_preamble(
                document,
                "\\theoremstyle{plain}\n" "\\newtheorem*{question*}{\\protect\\questionname}",
            )
        else:
            add_to_preamble(
                document,
                "\\theoremstyle{plain}\n" "\\newtheorem{question}{\\protect\\questionname}",
            )

        i = j


def convert_dashes(document):
    "convert -- and --- to \\twohyphens and \\threehyphens"

    if document.backend != "latex":
        return

    i = 0
    while True:
        i = find_substring(document.body, "--", i + 1)
        if i == -1:
            break
        line = document.body[i]
        # skip label width string (bug 10243):
        if line.startswith("\\labelwidthstring"):
            continue
        # Do not touch hyphens in some insets:
        try:
            value, start, end = get_containing_inset(document.body, i)
        except TypeError:
            # False means no (or malformed) containing inset
            value, start, end = "no inset", -1, -1
        # We must not replace anything in insets that store LaTeX contents in .lyx files
        # (math and command insets without overridden read() and write() methods.
        # Filtering out IPA and ERT makes Text::readParToken() more simple,
        # Flex Code is logical markup, typically rendered as typewriter
        if value.split()[0] in [
            "CommandInset",
            "ERT",
            "External",
            "Formula",
            "FormulaMacro",
            "Graphics",
            "IPA",
            "listings",
        ] or value in ["Flex Code", "Flex URL"]:
            i = end
            continue
        try:
            layout, start, end, j = get_containing_layout(document.body, i)
        except TypeError:  # no (or malformed) containing layout
            document.warning("Malformed LyX document: " "Can't find layout at line %d" % i)
            continue
        if layout == "LyX-Code":
            i = end
            continue
        # We can have an arbitrary number of consecutive hyphens.
        # Replace as LaTeX does: First try emdash, then endash
        line = line.replace("---", "\\threehyphens\n")
        line = line.replace("--", "\\twohyphens\n")
        document.body[i : i + 1] = line.split("\n")

    # remove ligature breaks between dashes
    i = 0
    while True:
        i = find_substring(document.body, r"-\SpecialChar \textcompwordmark{}", i + 1)
        if i == -1:
            break
        if document.body[i + 1].startswith("-"):
            document.body[i] = document.body[i].replace(
                r"\SpecialChar \textcompwordmark{}", document.body.pop(i + 1)
            )


def revert_dashes(document):
    """
    Remove preamble code from 2.3->2.2 conversion.
    Prevent ligatures of existing --- and --.
    Revert \\twohyphens and \\threehyphens to -- and ---.
    """
    del_complete_lines(
        document.preamble,
        [
            "% Added by lyx2lyx",
            r"\renewcommand{\textendash}{--}",
            r"\renewcommand{\textemdash}{---}",
        ],
    )

    # Insert ligature breaks to prevent ligation of hyphens to dashes:
    i = 0
    while True:
        i = find_substring(document.body, "--", i + 1)
        if i == -1:
            break
        line = document.body[i]
        # skip label width string (bug 10243):
        if line.startswith("\\labelwidthstring"):
            continue
        # do not touch hyphens in some insets (cf. convert_dashes):
        try:
            value, start, end = get_containing_inset(document.body, i)
        except TypeError:
            # False means no (or malformed) containing inset
            value, start, end = "no inset", -1, -1
        if (
            value.split()[0]
            in [
                "CommandInset",
                "ERT",
                "External",
                "Formula",
                "FormulaMacro",
                "Graphics",
                "IPA",
                "listings",
            ]
            or value == "Flex URL"
        ):
            i = end
            continue
        line = line.replace("--", "-\\SpecialChar \\textcompwordmark{}\n-")
        document.body[i : i + 1] = line.split("\n")

    # Revert \twohyphens and \threehyphens:
    i = 1
    while i < len(document.body):
        line = document.body[i]
        if not line.endswith("hyphens"):
            i += 1
        elif line.endswith("\\twohyphens") or line.endswith("\\threehyphens"):
            line = line.replace("\\twohyphens", "--")
            line = line.replace("\\threehyphens", "---")
            document.body[i] = line + document.body.pop(i + 1)
        else:
            i += 1


# order is important for the last three!
phrases = ["LyX", "LaTeX2e", "LaTeX", "TeX"]


def is_part_of_converted_phrase(line, j, phrase):
    "is phrase part of an already converted phrase?"
    for p in phrases:
        converted = "\\SpecialCharNoPassThru \\" + p
        pos = j + len(phrase) - len(converted)
        if pos >= 0:
            if line[pos : pos + len(converted)] == converted:
                return True
    return False


def convert_phrases(document):
    "convert special phrases from plain text to \\SpecialCharNoPassThru"

    if document.backend != "latex":
        return

    i = 0
    while i < len(document.body):
        if document.body[i] and document.body[i][0] == "\\":
            words = document.body[i].split()
            if (
                len(words) > 1
                and words[0] == "\\begin_inset"
                and words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]
            ):
                # must not replace anything in insets that store LaTeX contents in .lyx files
                # (math and command insets without overridden read() and write() methods)
                j = find_end_of_inset(document.body, i)
                if j == -1:
                    document.warning(
                        "Malformed LyX document: Can't find end of inset at line %d" % (i)
                    )
                    i += 1
                else:
                    i = j
            else:
                i += 1
            continue
        for phrase in phrases:
            j = document.body[i].find(phrase)
            if j == -1:
                continue
            if not is_part_of_converted_phrase(document.body[i], j, phrase):
                front = document.body[i][:j]
                back = document.body[i][j + len(phrase) :]
                if len(back) > 0:
                    document.body.insert(i + 1, back)
                # We cannot use SpecialChar since we do not know whether we are outside passThru
                document.body[i] = front + "\\SpecialCharNoPassThru \\" + phrase
        i += 1


def revert_phrases(document):
    "revert special phrases to plain text"

    i = 0
    while i < len(document.body):
        words = document.body[i].split()
        if (
            len(words) > 1
            and words[0] == "\\begin_inset"
            and words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]
        ):
            # see convert_phrases
            j = find_end_of_inset(document.body, i)
            if j == -1:
                document.warning(
                    "Malformed LyX document: Can't find end of Formula inset at line " + str(i)
                )
                i += 1
            else:
                i = j
            continue
        replaced = False
        for phrase in phrases:
            # we can replace SpecialChar since LyX ensures that it cannot be inserted into passThru parts
            if document.body[i].find("\\SpecialChar \\" + phrase) >= 0:
                document.body[i] = document.body[i].replace("\\SpecialChar \\" + phrase, phrase)
                replaced = True
            if document.body[i].find("\\SpecialCharNoPassThru \\" + phrase) >= 0:
                document.body[i] = document.body[i].replace(
                    "\\SpecialCharNoPassThru \\" + phrase, phrase
                )
                replaced = True
        if (
            replaced
            and i + 1 < len(document.body)
            and (
                document.body[i + 1].find("\\") != 0
                or document.body[i + 1].find("\\SpecialChar") == 0
            )
            and len(document.body[i]) + len(document.body[i + 1]) <= 80
        ):
            document.body[i] = document.body[i] + document.body[i + 1]
            document.body[i + 1 : i + 2] = []
            i -= 1
        i += 1


def convert_specialchar_internal(document, forward):
    specialchars = {
        "\\-": "softhyphen",
        "\\textcompwordmark{}": "ligaturebreak",
        "\\@.": "endofsentence",
        "\\ldots{}": "ldots",
        "\\menuseparator": "menuseparator",
        "\\slash{}": "breakableslash",
        "\\nobreakdash-": "nobreakdash",
        "\\LyX": "LyX",
        "\\TeX": "TeX",
        "\\LaTeX2e": "LaTeX2e",
        "\\LaTeX": "LaTeX",  # must be after LaTeX2e
    }

    i = 0
    while i < len(document.body):
        if document.body[i] and document.body[i][0] == "\\":
            words = document.body[i].split()
            if (
                len(words) > 1
                and words[0] == "\\begin_inset"
                and words[1] in ["CommandInset", "External", "Formula", "Graphics", "listings"]
            ):
                # see convert_phrases
                j = find_end_of_inset(document.body, i)
                if j == -1:
                    document.warning(
                        "Malformed LyX document: Can't find end of %s inset at line %d"
                        % (words[1], i)
                    )
                    i += 1
                else:
                    i = j
                continue
            # else...
        if "\\SpecialChar" not in document.body[i]:
            i += 1
            continue
        for key, value in specialchars.items():
            if forward:
                document.body[i] = document.body[i].replace(
                    "\\SpecialChar " + key, "\\SpecialChar " + value
                )
                document.body[i] = document.body[i].replace(
                    "\\SpecialCharNoPassThru " + key, "\\SpecialCharNoPassThru " + value
                )
            else:
                document.body[i] = document.body[i].replace(
                    "\\SpecialChar " + value, "\\SpecialChar " + key
                )
                document.body[i] = document.body[i].replace(
                    "\\SpecialCharNoPassThru " + value, "\\SpecialCharNoPassThru " + key
                )
        i += 1


def convert_specialchar(document):
    "convert special characters to new syntax"
    convert_specialchar_internal(document, True)


def revert_specialchar(document):
    "convert special characters to old syntax"
    convert_specialchar_internal(document, False)


def revert_georgian(document):
    "Set the document language to English but assure Georgian output"

    revert_language(document, "georgian", "georgian", "")


def revert_sigplan_doi(document):
    "Reverts sigplanconf DOI layout to ERT"

    if document.textclass != "sigplanconf":
        return

    i = 0
    while True:
        i = find_token(document.body, "\\begin_layout DOI", i)
        if i == -1:
            return
        j = find_end_of_layout(document.body, i)
        if j == -1:
            document.warning("Malformed LyX document: Can't find end of DOI layout")
            i += 1
            continue

        content = lyx2latex(document, document.body[i : j + 1])
        add_to_preamble(document, ["\\doi{" + content + "}"])
        del document.body[i : j + 1]
        # no need to reset i


def revert_ex_itemargs(document):
    "Reverts \\item arguments of the example environments (Linguistics module) to TeX-code"

    if "linguistics" not in document.get_module_list():
        return

    i = 0
    example_layouts = ["Numbered Examples (consecutive)", "Subexample"]
    while True:
        i = find_token(document.body, "\\begin_inset Argument item:", i)
        if i == -1:
            return
        j = find_end_of_inset(document.body, i)
        # Find containing paragraph layout
        parent = get_containing_layout(document.body, i)
        if parent == False:
            document.warning("Malformed LyX document: Can't find parent paragraph layout")
            i += 1
            continue
        parbeg = parent[3]
        layoutname = parent[0]
        if layoutname in example_layouts:
            beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
            endPlain = find_end_of_layout(document.body, beginPlain)
            content = document.body[beginPlain + 1 : endPlain]
            del document.body[i : j + 1]
            subst = put_cmd_in_ert("[") + content + put_cmd_in_ert("]")
            document.body[parbeg:parbeg] = subst
        i += 1


def revert_forest(document):
    "Reverts the forest environment (Linguistics module) to TeX-code"

    if "linguistics" not in document.get_module_list():
        return

    i = 0
    while True:
        i = find_token(document.body, "\\begin_inset Flex Structure Tree", i)
        if i == -1:
            return
        j = find_end_of_inset(document.body, i)
        if j == -1:
            document.warning("Malformed LyX document: Can't find end of Structure Tree inset")
            i += 1
            continue

        beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
        endPlain = find_end_of_layout(document.body, beginPlain)
        content = lyx2latex(document, document.body[beginPlain:endPlain])

        add_to_preamble(document, ["\\usepackage{forest}"])

        document.body[i : j + 1] = put_cmd_in_ert("\\begin{forest}" + content + "\\end{forest}")
        # no need to reset i


def revert_glossgroup(document):
    "Reverts the GroupGlossedWords inset (Linguistics module) to TeX-code"

    if "linguistics" not in document.get_module_list():
        return

    i = 0
    while True:
        i = find_token(document.body, "\\begin_inset Flex GroupGlossedWords", i)
        if i == -1:
            return
        j = find_end_of_inset(document.body, i)
        if j == -1:
            document.warning(
                "Malformed LyX document: Can't find end of GroupGlossedWords inset"
            )
            i += 1
            continue

        beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
        endPlain = find_end_of_layout(document.body, beginPlain)
        content = lyx2verbatim(document, document.body[beginPlain:endPlain])

        document.body[i : j + 1] = ["{", "", content, "", "}"]
        # no need to reset i


def revert_newgloss(document):
    "Reverts the new Glosse insets (Linguistics module) to the old format"

    if "linguistics" not in document.get_module_list():
        return

    glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
    for glosse in glosses:
        i = 0
        while True:
            i = find_token(document.body, glosse, i)
            if i == -1:
                break
            j = find_end_of_inset(document.body, i)
            if j == -1:
                document.warning("Malformed LyX document: Can't find end of Glosse inset")
                i += 1
                continue

            arg = find_token(document.body, "\\begin_inset Argument 1", i, j)
            endarg = find_end_of_inset(document.body, arg)
            argcontent = ""
            if arg != -1:
                argbeginPlain = find_token(
                    document.body, "\\begin_layout Plain Layout", arg, endarg
                )
                if argbeginPlain == -1:
                    document.warning("Malformed LyX document: Can't find arg plain Layout")
                    i += 1
                    continue
                argendPlain = find_end_of_inset(document.body, argbeginPlain)
                argcontent = lyx2verbatim(
                    document, document.body[argbeginPlain : argendPlain - 2]
                )

                document.body[j:j] = [
                    "",
                    "\\begin_layout Plain Layout",
                    "\\backslash",
                    "glt ",
                    argcontent,
                    "\\end_layout",
                ]

                # remove Arg insets and paragraph, if it only contains this inset
                if (
                    document.body[arg - 1] == "\\begin_layout Plain Layout"
                    and find_end_of_layout(document.body, arg - 1) == endarg + 3
                ):
                    del document.body[arg - 1 : endarg + 4]
                else:
                    del document.body[arg : endarg + 1]

            beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
            endPlain = find_end_of_layout(document.body, beginPlain)
            content = lyx2verbatim(document, document.body[beginPlain:endPlain])

            document.body[beginPlain + 1 : endPlain] = [content]
            i = beginPlain + 1

    # Dissolve ERT insets
    for glosse in glosses:
        i = 0
        while True:
            i = find_token(document.body, glosse, i)
            if i == -1:
                break
            j = find_end_of_inset(document.body, i)
            if j == -1:
                document.warning("Malformed LyX document: Can't find end of Glosse inset")
                i += 1
                continue
            while True:
                ert = find_token(document.body, "\\begin_inset ERT", i, j)
                if ert == -1:
                    break
                ertend = find_end_of_inset(document.body, ert)
                if ertend == -1:
                    document.warning("Malformed LyX document: Can't find end of ERT inset")
                    ert += 1
                    continue
                ertcontent = get_ert(document.body, ert, True)
                document.body[ert : ertend + 1] = [ertcontent]
            i += 1


def convert_newgloss(document):
    "Converts Glosse insets (Linguistics module) to the new format"

    if "linguistics" not in document.get_module_list():
        return

    glosses = ("\\begin_inset Flex Glosse", "\\begin_inset Flex Tri-Glosse")
    for glosse in glosses:
        i = 0
        while True:
            i = find_token(document.body, glosse, i)
            if i == -1:
                break
            j = find_end_of_inset(document.body, i)
            if j == -1:
                document.warning("Malformed LyX document: Can't find end of Glosse inset")
                i += 1
                continue

            k = i
            while True:
                argcontent = []
                beginPlain = find_token(document.body, "\\begin_layout Plain Layout", k, j)
                if beginPlain == -1:
                    break
                endPlain = find_end_of_layout(document.body, beginPlain)
                if endPlain == -1:
                    document.warning("Malformed LyX document: Can't find end of Glosse layout")
                    i += 1
                    continue

                glt = find_token(document.body, "\\backslash", beginPlain, endPlain)
                if glt != -1 and document.body[glt + 1].startswith("glt"):
                    document.body[glt + 1] = document.body[glt + 1].lstrip("glt").lstrip()
                    argcontent = document.body[glt + 1 : endPlain]
                    document.body[beginPlain + 1 : endPlain] = (
                        [
                            "\\begin_inset Argument 1",
                            "status open",
                            "",
                            "\\begin_layout Plain Layout",
                            "\\begin_inset ERT",
                            "status open",
                            "",
                            "\\begin_layout Plain Layout",
                            "",
                        ]
                        + argcontent
                        + [
                            "\\end_layout",
                            "",
                            "\\end_inset",
                            "",
                            "\\end_layout",
                            "",
                            "\\end_inset",
                        ]
                    )
                else:
                    content = document.body[beginPlain + 1 : endPlain]
                    document.body[beginPlain + 1 : endPlain] = (
                        [
                            "\\begin_inset ERT",
                            "status open",
                            "",
                            "\\begin_layout Plain Layout",
                        ]
                        + content
                        + ["\\end_layout", "", "\\end_inset"]
                    )

                endPlain = find_end_of_layout(document.body, beginPlain)
                k = endPlain
                j = find_end_of_inset(document.body, i)

            i = endPlain + 1


def convert_BoxFeatures(document):
    "adds new box features"

    i = 0
    while True:
        i = find_token(document.body, "height_special", i)
        if i == -1:
            return
        document.body[i + 1 : i + 1] = [
            'thickness "0.4pt"',
            'separation "3pt"',
            'shadowsize "4pt"',
        ]
        i = i + 4


def revert_BoxFeatures(document):
    "outputs new box features as TeX code"

    i = 0
    defaultSep = "3pt"
    defaultThick = "0.4pt"
    defaultShadow = "4pt"
    while True:
        i = find_token(document.body, "thickness", i)
        if i == -1:
            return
        binset = find_token(document.body, "\\begin_inset Box", i - 11)
        if binset == -1 or binset != i - 11:
            i = i + 1
            continue  # then "thickness" is is just a word in the text
        einset = find_end_of_inset(document.body, binset)
        if einset == -1:
            document.warning("Malformed LyX document: Can't find end of box inset!")
            i = i + 1
            continue
        # read out the values
        beg = document.body[i].find('"')
        end = document.body[i].rfind('"')
        thickness = document.body[i][beg + 1 : end]
        beg = document.body[i + 1].find('"')
        end = document.body[i + 1].rfind('"')
        separation = document.body[i + 1][beg + 1 : end]
        beg = document.body[i + 2].find('"')
        end = document.body[i + 2].rfind('"')
        shadowsize = document.body[i + 2][beg + 1 : end]
        # output ERT
        # first output the closing brace
        if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
            document.body[einset + 1 : einset + 1] = put_cmd_in_ert("}")
        # delete the specification
        del document.body[i : i + 3]
        # we have now the problem that if there is already \(f)colorbox in ERT around the inset
        # the ERT from this routine must be around it
        regexp = re.compile(r"^.*colorbox{.*$")
        pos = find_re(document.body, regexp, binset - 4)
        if pos != -1 and pos == binset - 4:
            pos = i - 11 - 10
        else:
            pos = i - 11
        # now output the lengths
        if shadowsize != defaultShadow or separation != defaultSep or thickness != defaultThick:
            document.body[pos:pos] = put_cmd_in_ert("{")
        if thickness != defaultThick:
            document.body[pos + 5 : pos + 6] = ["{\\backslash fboxrule " + thickness]
        if separation != defaultSep and thickness == defaultThick:
            document.body[pos + 5 : pos + 6] = ["{\\backslash fboxsep " + separation]
        if separation != defaultSep and thickness != defaultThick:
            document.body[pos + 5 : pos + 6] = [
                "{\\backslash fboxrule " + thickness + "\\backslash fboxsep " + separation
            ]
        if (
            shadowsize != defaultShadow
            and separation == defaultSep
            and thickness == defaultThick
        ):
            document.body[pos + 5 : pos + 6] = ["{\\backslash shadowsize " + shadowsize]
        if (
            shadowsize != defaultShadow
            and separation != defaultSep
            and thickness == defaultThick
        ):
            document.body[pos + 5 : pos + 6] = [
                "{\\backslash fboxsep " + separation + "\\backslash shadowsize " + shadowsize
            ]
        if (
            shadowsize != defaultShadow
            and separation == defaultSep
            and thickness != defaultThick
        ):
            document.body[pos + 5 : pos + 6] = [
                "{\\backslash fboxrule " + thickness + "\\backslash shadowsize " + shadowsize
            ]
        if (
            shadowsize != defaultShadow
            and separation != defaultSep
            and thickness != defaultThick
        ):
            document.body[pos + 5 : pos + 6] = [
                "{\\backslash fboxrule "
                + thickness
                + "\\backslash fboxsep "
                + separation
                + "\\backslash shadowsize "
                + shadowsize
            ]


def convert_origin(document):
    "Insert the origin tag"

    i = find_token(document.header, "\\textclass ", 0)
    if i == -1:
        document.warning("Malformed LyX document: No \\textclass!!")
        return
    if document.dir == "":
        origin = "stdin"
    else:
        relpath = ""
        if document.systemlyxdir and document.systemlyxdir != "":
            try:
                if os.path.isabs(document.dir):
                    absdir = os.path.normpath(document.dir)
                else:
                    absdir = os.path.normpath(os.path.abspath(document.dir))
                if os.path.isabs(document.systemlyxdir):
                    abssys = os.path.normpath(document.systemlyxdir)
                else:
                    abssys = os.path.normpath(os.path.abspath(document.systemlyxdir))
                relpath = os.path.relpath(absdir, abssys)
                if relpath.find("..") == 0:
                    relpath = ""
            except:
                relpath = ""
        if relpath == "":
            origin = document.dir.replace("\\", "/") + "/"
        else:
            origin = os.path.join("/systemlyxdir", relpath).replace("\\", "/") + "/"
    document.header[i:i] = ["\\origin " + origin]


def revert_origin(document):
    "Remove the origin tag"

    i = find_token(document.header, "\\origin ", 0)
    if i == -1:
        document.warning("Malformed LyX document: No \\origin!!")
        return
    del document.header[i]


color_names = [
    "brown",
    "darkgray",
    "gray",
    "lightgray",
    "lime",
    "olive",
    "orange",
    "pink",
    "purple",
    "teal",
    "violet",
]


def revert_textcolor(document):
    "revert new \\textcolor colors to TeX code"

    i = 0
    j = 0
    xcolor = False
    while True:
        i = find_token(document.body, "\\color ", i)
        if i == -1:
            return
        else:
            for color in list(color_names):
                if document.body[i] == "\\color " + color:
                    # register that xcolor must be loaded in the preamble
                    if xcolor == False:
                        xcolor = True
                        add_to_preamble(
                            document,
                            ["\\@ifundefined{rangeHsb}{\\usepackage{xcolor}}{}"],
                        )
                    # find the next \\color and/or the next \\end_layout
                    j = find_token(document.body, "\\color", i + 1)
                    k = find_token(document.body, "\\end_layout", i + 1)
                    if j == -1 and k != -1:
                        j = k + 1
                    # output TeX code
                    # first output the closing brace
                    if k < j:
                        document.body[k:k] = put_cmd_in_ert("}")
                    else:
                        document.body[j:j] = put_cmd_in_ert("}")
                    # now output the \textcolor command
                    document.body[i : i + 1] = put_cmd_in_ert("\\textcolor{" + color + "}{")
        i = i + 1


def convert_colorbox(document):
    "Add color settings for boxes."
    i = 1
    while True:
        i = find_token(document.body, "shadowsize", i)
        if i == -1:
            return
        # check whether this is really a LyX Box setting
        start, end = is_in_inset(document.body, i, "\\begin_inset Box")
        if end == -1 or find_token(document.body, "\\begin_layout", start, i) != -1:
            i += 1
            continue
        document.body[i + 1 : i + 1] = ['framecolor "black"', 'backgroundcolor "none"']
        i += 3


def revert_colorbox(document):
    """Change box color settings to LaTeX code."""

    i = 0
    while True:
        i = find_token(document.body, "\\begin_inset Box", i)
        if i == -1:
            return

        j = find_end_of_inset(document.body, i)
        k = find_token(document.body, "\\begin_layout", i, j)
        if k == -1:
            document.warning("Malformed LyX document: no layout in Box inset!")
            i += 1
            continue
        # Get and delete colour settings:
        framecolor = get_quoted_value(document.body, "framecolor", i, k, delete=True)
        backcolor = get_quoted_value(document.body, "backgroundcolor", i, k + 1, delete=True)
        if not framecolor or not backcolor:
            document.warning("Malformed LyX document: color options not found in Box inset!")
            i += 1
            continue
        if framecolor == "black" and backcolor == "none":  # default values
            i += 1
            continue

        # Emulate non-default colours with LaTeX code:
        einset = find_end_of_inset(document.body, i)
        if einset == -1:
            document.warning("Malformed LyX document: Can't find end of box inset!")
            i += 1
            continue
        add_to_preamble(document, ["\\@ifundefined{rangeHsb}{\\usepackage{xcolor}}{}"])
        # insert the closing brace first (keeps indices 'i' and 'einset' valid)
        document.body[einset + 1 : einset + 1] = put_cmd_in_ert("}")
        # now insert the (f)color box command
        if "Box Boxed" in document.body[i]:  # framed box, use \fcolorbox
            # change the box type (frame added by \fcolorbox)
            document.body[i] = "\\begin_inset Box Frameless"
            # ensure an inner box:
            try:
                if not set_bool_value(document.body, "has_inner_box", True, i + 3, i + 4):
                    set_bool_value(document.body, "use_makebox", True, i + 6, i + 7)
            except ValueError:
                document.warning(
                    "Malformed LyX document: 'has_inner_box' or "
                    "'use_makebox' option not found in box inset!"
                )
            ertinset = put_cmd_in_ert(f"\\fcolorbox{{{framecolor}}}{{{backcolor}}}{{")
        else:
            ertinset = put_cmd_in_ert("\\colorbox{%s}{" % backcolor)
        document.body[i:i] = ertinset + [""]
        i += 13


def revert_mathmulticol(document):
    "Convert formulas to ERT if they contain multicolumns"

    i = 0
    while True:
        i = find_token(document.body, "\\begin_inset Formula", i)
        if i == -1:
            return
        j = find_end_of_inset(document.body, i)
        if j == -1:
            document.warning(
                "Malformed LyX document: Can't find end of Formula inset at line " + str(i)
            )
            i += 1
            continue
        lines = document.body[i:j]
        lines[0] = lines[0].replace("\\begin_inset Formula", "").lstrip()
        code = "\n".join(lines)
        converted = False
        k = 0
        n = 0
        while n >= 0:
            n = code.find("\\multicolumn", k)
            # no need to convert degenerated multicolumn cells,
            # they work in old LyX versions as "math ERT"
            if n != -1 and code.find("\\multicolumn{1}", k) != n:
                ert = put_cmd_in_ert(code)
                document.body[i : j + 1] = ert
                converted = True
                break
            else:
                k = n + 12
        if converted:
            i = find_end_of_inset(document.body, i)
        else:
            i = j


def revert_jss(document):
    "Reverts JSS In_Preamble commands to ERT in preamble"

    if document.textclass != "jss":
        return

    # at first revert the inset layouts because
    # they can be part of the In_Preamble layouts
    il_dict = {
        "Pkg": "pkg",
        "Proglang": "proglang",
        "Code": "code",
        "E-mail": "email",
        "URL": "url",
    }
    for iltype in il_dict.keys():
        i = 0
        while True:
            i = find_token(document.body, "\\begin_inset Flex " + iltype, i)
            if i == -1:
                break

            if document.body[i] != "\\begin_inset Flex " + iltype:
                # Not an exact match!
                i += 1
                continue

            end = find_end_of_inset(document.body, i)
            if end == -1:
                document.warning("Malformed LyX document: No end of Flex " + iltype + " found!")
                i += 1
                continue
            document.body[end - 2 : end + 1] = put_cmd_in_ert("}")
            document.body[i : i + 4] = put_cmd_in_ert("\\%s{" % il_dict[iltype])
            i += 1

    # now revert the In_Preamble layouts
    ipl_dict = {
        "Title": "title",
        "Author": "author",
        "Plain Author": "Plainauthor",
        "Plain Title": "Plaintitle",
        "Short Title": "Shorttitle",
        "Abstract": "Abstract",
        "Keywords": "Keywords",
        "Plain Keywords": "Plainkeywords",
        "Address": "Address",
    }
    for ipltype in ipl_dict.keys():
        i = 0
        while True:
            i = find_token(document.body, "\\begin_layout " + ipltype, i)
            if i == -1:
                break

            end = find_end_of_layout(document.body, i)
            if end == -1:
                document.warning(
                    "Malformed LyX document: Can't find end of " + ipltype + " layout"
                )
                i += 1
                continue

            content = lyx2latex(document, document.body[i : end + 1])
            add_to_preamble(document, ["\\" + ipl_dict[ipltype] + "{" + content + "}"])
            del document.body[i : end + 1]
            i += 1

    # Now code chunks
    i = 0
    while True:
        # \CodeChunk
        i = find_token(document.body, "\\begin_inset Flex Code Chunk", i)
        if i == -1:
            break

        end = find_end_of_inset(document.body, i)
        if end == -1:
            document.warning("Malformed LyX document: No end of Flex Code Chunk found!")
            i += 1
            continue

        document.body[end : end + 1] = [
            "\\end_layout",
            "",
            "\\begin_layout Standard",
        ] + put_cmd_in_ert("\\end{CodeChunk}")
        document.body[i : i + 2] = put_cmd_in_ert("\\begin{CodeChunk}")
        i += 1

    # finally handle the code layouts
    codes_dict = {
        "Code Input": "CodeInput",
        "Code Output": "CodeOutput",
        "Code": "Code",
    }
    for ctype in codes_dict.keys():
        i = 0
        while True:
            i = find_token(document.body, "\\begin_layout " + ctype, i)
            if i == -1:
                break
            if document.body[i] != "\\begin_layout " + ctype:
                # Not an exact match!
                i += 1
                continue
            end = find_end_of_layout(document.body, i)
            if end == -1:
                document.warning(
                    "Malformed LyX document: No end of " + ctype + " layout found!"
                )
                i += 1
                continue
            seq_end = end
            # Handle subsequent layouts
            while True:
                j = find_token(document.body, "\\begin_layout ", seq_end)
                if j == -1 or document.body[j] != "\\begin_layout " + ctype:
                    break
                this_end = find_end_of_layout(document.body, j)
                if this_end == -1:
                    document.warning(
                        "Malformed LyX document: No end of " + ctype + " layout found!"
                    )
                    j += 1
                    continue
                seq_end = this_end
            document.body[seq_end + 1 : seq_end + 1] = [
                "\\end_inset",
                "\\end_layout",
                "",
                "\\begin_layout Standard",
            ] + put_cmd_in_ert("\\end{%s}" % codes_dict[ctype])
            if seq_end != end:
                k = i + 1
                while k < seq_end:
                    document.body[k] = document.body[k].replace(
                        "\\begin_layout " + ctype, "\\begin_layout Plain Layout"
                    )
                    k += 1
            document.body[i : i + 1] = (
                ["\\end_layout", "", "\\begin_layout Standard"]
                + put_cmd_in_ert("\\begin{%s}" % codes_dict[ctype])
                + [
                    "\\end_layout",
                    "",
                    "\\begin_layout Standard",
                    "",
                    "\\begin_inset ERT",
                    "status open",
                    "",
                    "\\begin_layout Plain Layout",
                ]
            )
            i += 1


def convert_subref(document):
    "converts sub: ref prefixes to subref:"

    # 1) label insets
    rx = re.compile(r"^name \"sub:(.+)$")
    i = 0
    while True:
        i = find_token(document.body, "\\begin_inset CommandInset label", i)
        if i == -1:
            break
        j = find_end_of_inset(document.body, i)
        if j == -1:
            document.warning(
                "Malformed LyX document: Can't find end of Label inset at line " + str(i)
            )
            i += 1
            continue

        for p in range(i, j):
            m = rx.match(document.body[p])
            if m:
                label = m.group(1)
                document.body[p] = 'name "subsec:' + label
        i += 1

    # 2) xref insets
    rx = re.compile(r"^reference \"sub:(.+)$")
    i = 0
    while True:
        i = find_token(document.body, "\\begin_inset CommandInset ref", i)
        if i == -1:
            return
        j = find_end_of_inset(document.body, i)
        if j == -1:
            document.warning(
                "Malformed LyX document: Can't find end of Ref inset at line " + str(i)
            )
            i += 1
            continue

        for p in range(i, j):
            m = rx.match(document.body[p])
            if m:
                label = m.group(1)
                document.body[p] = 'reference "subsec:' + label
                break
        i += 1


def revert_subref(document):
    "reverts subref: ref prefixes to sub:"

    # 1) label insets
    rx = re.compile(r"^name \"subsec:(.+)$")
    i = 0
    while True:
        i = find_token(document.body, "\\begin_inset CommandInset label", i)
        if i == -1:
            break
        j = find_end_of_inset(document.body, i)
        if j == -1:
            document.warning(
                "Malformed LyX document: Can't find end of Label inset at line " + str(i)
            )
            i += 1
            continue

        for p in range(i, j):
            m = rx.match(document.body[p])
            if m:
                label = m.group(1)
                document.body[p] = 'name "sub:' + label
                break
        i += 1

    # 2) xref insets
    rx = re.compile(r"^reference \"subsec:(.+)$")
    i = 0
    while True:
        i = find_token(document.body, "\\begin_inset CommandInset ref", i)
        if i == -1:
            return
        j = find_end_of_inset(document.body, i)
        if j == -1:
            document.warning(
                "Malformed LyX document: Can't find end of Ref inset at line " + str(i)
            )
            i += 1
            continue

        for p in range(i, j):
            m = rx.match(document.body[p])
            if m:
                label = m.group(1)
                document.body[p] = 'reference "sub:' + label
                break
        i += 1


def convert_nounzip(document):
    "remove the noUnzip parameter of graphics insets"

    rx = re.compile(r"\s*noUnzip\s*$")
    i = 0
    while True:
        i = find_token(document.body, "\\begin_inset Graphics", i)
        if i == -1:
            break
        j = find_end_of_inset(document.body, i)
        if j == -1:
            document.warning(
                "Malformed LyX document: Can't find end of graphics inset at line " + str(i)
            )
            i += 1
            continue

        k = find_re(document.body, rx, i, j)
        if k != -1:
            del document.body[k]
            j = j - 1
        i = j + 1


def convert_revert_external_bbox(document, forward):
    "add units to bounding box of external insets"

    rx = re.compile(r"^\s*boundingBox\s+\S+\s+\S+\s+\S+\s+\S+\s*$")
    i = 0
    while True:
        i = find_token(document.body, "\\begin_inset External", i)
        if i == -1:
            break
        j = find_end_of_inset(document.body, i)
        if j == -1:
            document.warning(
                "Malformed LyX document: Can't find end of external inset at line " + str(i)
            )
            i += 1
            continue
        k = find_re(document.body, rx, i, j)
        if k == -1:
            i = j + 1
            continue
        tokens = document.body[k].split()
        if forward:
            for t in range(1, 5):
                tokens[t] += "bp"
        else:
            for t in range(1, 5):
                tokens[t] = length_in_bp(document, tokens[t])
        document.body[k] = (
            "\tboundingBox " + tokens[1] + " " + tokens[2] + " " + tokens[3] + " " + tokens[4]
        )
        i = j + 1


def convert_external_bbox(document):
    convert_revert_external_bbox(document, True)


def revert_external_bbox(document):
    convert_revert_external_bbox(document, False)


def revert_tcolorbox_1(document):
    "Reverts the Flex:Subtitle inset of tcolorbox to TeX-code"

    i = find_token(document.header, "tcolorbox", 0)
    if i == -1:
        return

    flex = 0

    while True:
        flex = find_token(document.body, "\\begin_inset Flex Subtitle", flex)
        if flex == -1:
            return

        flexEnd = find_end_of_inset(document.body, flex)
        if flexEnd == -1:
            document.warning("Malformed LyX document! No end of Flex Subtitle found.")
            flex += 1
            continue

        wasOpt = revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, False, True, False)
        flexEnd = find_end_of_inset(document.body, flex)
        if flexEnd == -1:
            document.warning("Malformed LyX document! No end of Flex Subtitle found.")
            flex += 1
            continue
        revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, False, False, False)
        flexEnd = find_end_of_inset(document.body, flex)
        if flexEnd == -1:
            document.warning("Malformed LyX document! No end of Flex Subtitle found.")
            flex += 1
            continue

        bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
        if bp == -1:
            document.warning("Malformed LyX document! No Flex Subtitle layout found.")
            flex += 1
            continue

        ep = find_end_of_layout(document.body, bp)
        if ep == -1:
            document.warning("Malformed LyX document! No end of layout found.")
            flex += 1
            continue

        document.body[ep : flexEnd + 1] = put_cmd_in_ert("}")
        if wasOpt == True:
            document.body[flex : bp + 1] = put_cmd_in_ert("\\tcbsubtitle")
        else:
            document.body[flex : bp + 1] = put_cmd_in_ert("\\tcbsubtitle{")
        flex += 1


def revert_tcolorbox_2(document):
    "Reverts the Flex:Raster_Color_Box inset of tcolorbox to TeX-code"

    i = find_token(document.header, "tcolorbox", 0)
    if i == -1:
        return

    flex = 0
    while True:
        flex = find_token(document.body, "\\begin_inset Flex Raster Color Box", flex)
        if flex == -1:
            return

        flexEnd = find_end_of_inset(document.body, flex)
        if flexEnd == -1:
            document.warning("Malformed LyX document! No end of Flex Raster Color Box found.")
            flex += 1
            continue

        revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)

        bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
        if bp == -1:
            document.warning(
                "Malformed LyX document! No plain layout in Raster Color Box found."
            )
            flex += 1
            continue

        ep = find_end_of_layout(document.body, bp)
        if ep == -1:
            document.warning("Malformed LyX document! No end of layout found.")
            flex += 1
            continue

        flexEnd = find_end_of_inset(document.body, flex)
        if flexEnd == -1:
            document.warning("Malformed LyX document! No end of Flex Raster Color Box found.")
            flex += 1
            continue
        document.body[ep : flexEnd + 1] = put_cmd_in_ert("\\end{tcbraster}")
        document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{tcbraster}")
        flex += 1


def revert_tcolorbox_3(document):
    "Reverts the Flex:Custom_Color_Box_1 inset of tcolorbox to TeX-code"

    i = find_token(document.header, "tcolorbox", 0)
    if i == -1:
        return

    flex = 0
    while True:
        flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 1", flex)
        if flex == -1:
            return

        flexEnd = find_end_of_inset(document.body, flex)
        if flexEnd == -1:
            document.warning("Malformed LyX document! No end of Flex Custom Color Box 1 found.")
            flex += 1
            continue

        revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
        flexEnd = find_end_of_inset(document.body, flex)
        if flexEnd == -1:
            document.warning("Malformed LyX document! No end of Flex Subtitle found.")
            flex += 1
            continue
        revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)

        bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
        if bp == -1:
            document.warning(
                "Malformed LyX document! No plain layout in Custom Color Box 1 found."
            )
            flex += 1
            continue

        ep = find_end_of_layout(document.body, bp)
        if ep == -1:
            document.warning("Malformed LyX document! No end of layout found.")
            flex += 1
            continue

        flexEnd = find_end_of_inset(document.body, flex)
        if flexEnd == -1:
            document.warning("Malformed LyX document! No end of Flex Custom Color Box 1 found.")
            flex += 1
            continue

        document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxA}")
        document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxA}")
        flex += 1


def revert_tcolorbox_4(document):
    "Reverts the Flex:Custom_Color_Box_2 inset of tcolorbox to TeX-code"

    i = find_token(document.header, "tcolorbox", 0)
    if i == -1:
        return

    flex = 0
    while True:
        flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 2", flex)
        if flex == -1:
            return

        flexEnd = find_end_of_inset(document.body, flex)
        if flexEnd == -1:
            document.warning("Malformed LyX document! No end of Flex Custom Color Box 2 found.")
            flex += 1
            continue

        revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
        flexEnd = find_end_of_inset(document.body, flex)
        if flexEnd == -1:
            document.warning("Malformed LyX document! No end of Flex Subtitle found.")
            flex += 1
            continue
        revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
        flexEnd = find_end_of_inset(document.body, flex)
        if flexEnd == -1:
            document.warning("Malformed LyX document! No end of Flex Custom Color Box 2 found.")
            flex += 1
            continue

        bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
        if bp == -1:
            document.warning(
                "Malformed LyX document! No plain layout in Custom Color Box 2 found."
            )
            flex += 1
            continue

        ep = find_end_of_layout(document.body, bp)
        if ep == -1:
            document.warning("Malformed LyX document! No end of layout found.")
            flex += 1
            continue

        document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxB}")
        document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxB}")
        flex += 1


def revert_tcolorbox_5(document):
    "Reverts the Flex:Custom_Color_Box_3 inset of tcolorbox to TeX-code"

    i = find_token(document.header, "tcolorbox", 0)
    if i == -1:
        return

    flex = 0
    while True:
        flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 3", flex)
        if flex == -1:
            return

        flexEnd = find_end_of_inset(document.body, flex)
        if flexEnd == -1:
            document.warning("Malformed LyX document! No end of Flex Custom Color Box 3 found.")
            flex += 1
            continue

        revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
        flexEnd = find_end_of_inset(document.body, flex)
        if flexEnd == -1:
            document.warning("Malformed LyX document! No end of Flex Subtitle found.")
            flex += 1
            continue
        revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
        flexEnd = find_end_of_inset(document.body, flex)
        if flexEnd == -1:
            document.warning("Malformed LyX document! No end of Flex Custom Color Box 3 found.")
            flex += 1
            continue

        bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
        if bp == -1:
            document.warning(
                "Malformed LyX document! No plain layout in Custom Color Box 3 found."
            )
            flex += 1
            continue

        ep = find_end_of_layout(document.body, bp)
        if ep == -1:
            document.warning("Malformed LyX document! No end of layout found.")
            flex += 1
            continue

        document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxC}")
        document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxC}")
        flex += 1


def revert_tcolorbox_6(document):
    "Reverts the Flex:Custom_Color_Box_4 inset of tcolorbox to TeX-code"

    i = find_token(document.header, "tcolorbox", 0)
    if i == -1:
        return

    flex = 0
    while True:
        flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 4", flex)
        if flex == -1:
            return

        flexEnd = find_end_of_inset(document.body, flex)
        if flexEnd == -1:
            document.warning("Malformed LyX document! No end of Flex Custom Color Box 4 found.")
            flex += 1
            continue

        revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
        flexEnd = find_end_of_inset(document.body, flex)
        if flexEnd == -1:
            document.warning("Malformed LyX document! No end of Flex Subtitle found.")
            flex += 1
            continue
        revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
        flexEnd = find_end_of_inset(document.body, flex)
        if flexEnd == -1:
            document.warning("Malformed LyX document! No end of Flex Custom Color Box 4 found.")
            flex += 1
            continue

        bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
        if bp == -1:
            document.warning(
                "Malformed LyX document! No plain layout in Custom Color Box 4 found."
            )
            flex += 1
            continue

        ep = find_end_of_layout(document.body, bp)
        if ep == -1:
            document.warning("Malformed LyX document! No end of layout found.")
            flex += 1
            continue

        document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxD}")
        document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxD}")
        flex += 1


def revert_tcolorbox_7(document):
    "Reverts the Flex:Custom_Color_Box_5 inset of tcolorbox to TeX-code"

    i = find_token(document.header, "tcolorbox", 0)
    if i == -1:
        return

    flex = 0
    while True:
        flex = find_token(document.body, "\\begin_inset Flex Custom Color Box 5", flex)
        if flex == -1:
            return

        flexEnd = find_end_of_inset(document.body, flex)
        if flexEnd == -1:
            document.warning("Malformed LyX document! No end of Flex Custom Color Box 5 found.")
            flex += 1
            continue

        revert_Argument_to_TeX_brace(document, flex, flexEnd, 1, 1, True, True, False)
        flexEnd = find_end_of_inset(document.body, flex)
        if flexEnd == -1:
            document.warning("Malformed LyX document! No end of Flex Subtitle found.")
            flex += 1
            continue
        revert_Argument_to_TeX_brace(document, flex, flexEnd, 2, 2, True, False, False)
        flexEnd = find_end_of_inset(document.body, flex)
        if flexEnd == -1:
            document.warning("Malformed LyX document! No end of Flex Custom Color Box 5 found.")
            flex += 1
            continue

        bp = find_token(document.body, "\\begin_layout Plain Layout", flex)
        if bp == -1:
            document.warning(
                "Malformed LyX document! No plain layout in Custom Color Box 5 found."
            )
            flex += 1
            continue

        ep = find_end_of_layout(document.body, bp)
        if ep == -1:
            document.warning("Malformed LyX document! No end of layout found.")
            flex += 1
            continue

        document.body[ep : flexEnd + 1] = put_cmd_in_ert("{}\\end{cBoxE}")
        document.body[flex : bp + 1] = put_cmd_in_ert("\\begin{cBoxE}")
        flex += 1


def revert_tcolorbox_8(document):
    "Reverts the layout New Color Box Type of tcolorbox to TeX-code"

    i = 0
    while True:
        i = find_token(document.body, "\\begin_layout New Color Box Type", i)
        if i == -1:
            return

        j = find_end_of_layout(document.body, i)
        if j == -1:
            document.warning(
                "Malformed LyX document! No end of New Color Box Type layout found."
            )
            i += 1
            continue

        wasOpt = revert_Argument_to_TeX_brace(document, i, j, 1, 1, False, True, True)
        j = find_end_of_layout(document.body, i)
        if j == -1:
            document.warning(
                "Malformed LyX document! No end of New Color Box Type layout found."
            )
            i += 1
            continue
        revert_Argument_to_TeX_brace(document, i, j, 2, 2, False, False, True)
        j = find_end_of_layout(document.body, i)
        if j == -1:
            document.warning(
                "Malformed LyX document! No end of New Color Box Type layout found."
            )
            i += 1
            continue
        revert_Argument_to_TeX_brace(document, i, j, 3, 4, False, True, False)
        j = find_end_of_layout(document.body, i)
        if j == -1:
            document.warning(
                "Malformed LyX document! No end of New Color Box Type layout found."
            )
            i += 1
            continue
        document.body[i] = document.body[i].replace(
            "\\begin_layout New Color Box Type", "\\begin_layout Standard"
        )
        document.body[i + 1 : i + 1] = put_cmd_in_ert("\\newtcolorbox")
        k = find_end_of_inset(document.body, j)
        document.body[k + 2 : j + 2] = put_cmd_in_ert("{") + [
            "\\begin_inset ERT",
            "status collapsed",
            "\\begin_layout Plain Layout",
        ]
        j = find_token(document.body, "\\begin_layout Standard", j + 1)
        document.body[j - 2 : j - 2] = ["\\end_layout", "\\end_inset"] + put_cmd_in_ert("}")
        i += 1


def revert_moderncv_1(document):
    "Reverts the new inset of moderncv to TeX-code in preamble"

    if document.textclass != "moderncv":
        return
    i = 0
    j = 0
    lineArg = 0
    while True:
        # at first revert the new styles
        # \moderncvicons
        i = find_token(document.body, "\\begin_layout CVIcons", 0)
        if i == -1:
            return
        j = find_end_of_layout(document.body, i)
        if j == -1:
            document.warning("Malformed LyX document: Can't find end of CVIcons layout")
            i += 1
            continue
        content = lyx2latex(document, document.body[i : j + 1])
        add_to_preamble(document, ["\\moderncvicons{" + content + "}"])
        del document.body[i : j + 1]
        # \hintscolumnwidth
        i = find_token(document.body, "\\begin_layout CVColumnWidth", 0)
        if i == -1:
            return
        j = find_end_of_layout(document.body, i)
        if j == -1:
            document.warning("Malformed LyX document: Can't find end of CVColumnWidth layout")
            i += 1
            continue
        content = lyx2latex(document, document.body[i : j + 1])
        add_to_preamble(document, ["\\setlength{\\hintscolumnwidth}{" + content + "}"])
        del document.body[i : j + 1]
        # now change the new styles to the obsolete ones
        # \name
        i = find_token(document.body, "\\begin_layout Name", 0)
        if i == -1:
            return
        j = find_end_of_layout(document.body, i)
        if j == -1:
            document.warning("Malformed LyX document: Can't find end of Name layout")
            i += 1
            continue
        lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
        if lineArg > j and j != 0:
            return
        if lineArg != -1:
            beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
            # we have to assure that no other inset is in the Argument
            beginInset = find_token(document.body, "\\begin_inset", beginPlain)
            endInset = find_token(document.body, "\\end_inset", beginPlain)
            k = beginPlain + 1
            l = k
            while beginInset < endInset and beginInset != -1:
                beginInset = find_token(document.body, "\\begin_inset", k)
                endInset = find_token(document.body, "\\end_inset", l)
                k = beginInset + 1
                l = endInset + 1
            Arg2 = document.body[l + 5 : l + 6]
            # rename the style
            document.body[i : i + 1] = ["\\begin_layout FirstName"]
            # delete the Argument inset
            del document.body[endInset - 2 : endInset + 3]
            del document.body[lineArg : beginPlain + 1]
            document.body[i + 4 : i + 4] = (
                ["\\begin_layout FamilyName"] + Arg2 + ["\\end_layout"] + [""]
            )


def revert_moderncv_2(document):
    "Reverts the phone inset of moderncv to the obsoleted mobile or fax"

    if document.textclass != "moderncv":
        return
    i = 0
    j = 0
    lineArg = 0
    while True:
        # \phone
        i = find_token(document.body, "\\begin_layout Phone", i)
        if i == -1:
            return
        j = find_end_of_layout(document.body, i)
        if j == -1:
            document.warning("Malformed LyX document: Can't find end of Phone layout")
            i += 1
            return
        lineArg = find_token(document.body, "\\begin_inset Argument 1", i)
        if lineArg > j and j != 0:
            i += 1
            continue
        if lineArg != -1:
            beginPlain = find_token(document.body, "\\begin_layout Plain Layout", lineArg)
            # we have to assure that no other inset is in the Argument
            beginInset = find_token(document.body, "\\begin_inset", beginPlain)
            endInset = find_token(document.body, "\\end_inset", beginPlain)
            k = beginPlain + 1
            l = k
            while beginInset < endInset and beginInset != -1:
                beginInset = find_token(document.body, "\\begin_inset", k)
                endInset = find_token(document.body, "\\end_inset", l)
                k = beginInset + 1
                l = endInset + 1
            Arg = document.body[beginPlain + 1 : beginPlain + 2]
            # rename the style
            if Arg[0] == "mobile":
                document.body[i : i + 1] = ["\\begin_layout Mobile"]
            if Arg[0] == "fax":
                document.body[i : i + 1] = ["\\begin_layout Fax"]
            # delete the Argument inset
            del document.body[endInset - 2 : endInset + 1]
            del document.body[lineArg : beginPlain + 3]
        i += 1


def convert_moderncv_phone(document):
    "Convert the Fax and Mobile inset of moderncv to the new phone inset"

    if document.textclass != "moderncv":
        return
    i = 0
    j = 0
    lineArg = 0

    phone_dict = {
        "Mobile": "mobile",
        "Fax": "fax",
    }

    rx = re.compile(r"^\\begin_layout (\S+)$")
    while True:
        # substitute \fax and \mobile by \phone[fax] and \phone[mobile], respectively
        i = find_token(document.body, "\\begin_layout", i)
        if i == -1:
            return

        m = rx.match(document.body[i])
        val = ""
        if m:
            val = m.group(1)
        if val not in list(phone_dict.keys()):
            i += 1
            continue
        j = find_end_of_layout(document.body, i)
        if j == -1:
            document.warning("Malformed LyX document: Can't find end of Mobile layout")
            i += 1
            return

        document.body[i : i + 1] = [
            "\\begin_layout Phone",
            "\\begin_inset Argument 1",
            "status open",
            "",
            "\\begin_layout Plain Layout",
            phone_dict[val],
            "\\end_layout",
            "",
            "\\end_inset",
            "",
        ]


def convert_moderncv_name(document):
    "Convert the FirstName and LastName layout of moderncv to the general Name layout"

    if document.textclass != "moderncv":
        return

    fnb = 0  # Begin of FirstName inset
    fne = 0  # End of FirstName inset
    lnb = 0  # Begin of LastName (FamilyName) inset
    lne = 0  # End of LastName (FamilyName) inset
    nb = 0  # Begin of substituting Name inset
    ne = 0  # End of substituting Name inset
    FirstName = []  # FirstName content
    FamilyName = []  # LastName content

    while True:
        # locate FirstName
        fnb = find_token(document.body, "\\begin_layout FirstName", fnb)
        if fnb != -1:
            fne = find_end_of_layout(document.body, fnb)
            if fne == -1:
                document.warning("Malformed LyX document: Can't find end of FirstName layout")
                return
            FirstName = document.body[fnb + 1 : fne]
        # locate FamilyName
        lnb = find_token(document.body, "\\begin_layout FamilyName", lnb)
        if lnb != -1:
            lne = find_end_of_layout(document.body, lnb)
            if lne == -1:
                document.warning("Malformed LyX document: Can't find end of FamilyName layout")
                return
            FamilyName = document.body[lnb + 1 : lne]
        # Determine the region for the substituting Name layout
        if fnb == -1 and lnb == -1:  # Neither FirstName nor FamilyName exists -> Do nothing
            return
        elif fnb == -1:  # Only FamilyName exists -> New Name insets replaces that
            nb = lnb
            ne = lne
        elif lnb == -1:  # Only FirstName exists -> New Name insets replaces that
            nb = fnb
            ne = fne
        elif fne > lne:  # FirstName position before FamilyName -> New Name insets spans
            nb = lnb  #     from FamilyName begin
            ne = fne  #     to FirstName end
        else:  #           FirstName position before FamilyName -> New Name insets spans
            nb = fnb  #     from FirstName begin
            ne = lne  #     to FamilyName end

        # Insert the substituting layout now. If FirstName exists, use an otpional argument.
        if FirstName == []:
            document.body[nb : ne + 1] = (
                ["\\begin_layout Name"] + FamilyName + ["\\end_layout", ""]
            )
        else:
            document.body[nb : ne + 1] = (
                [
                    "\\begin_layout Name",
                    "\\begin_inset Argument 1",
                    "status open",
                    "",
                    "\\begin_layout Plain Layout",
                ]
                + FirstName
                + ["\\end_layout", "", "\\end_inset", ""]
                + FamilyName
                + ["\\end_layout", ""]
            )


def revert_achemso(document):
    "Reverts the flex inset Latin to TeX code"

    if document.textclass != "achemso":
        return
    i = 0
    j = 0
    while True:
        i = find_token(document.body, "\\begin_inset Flex Latin", i)
        if i != -1:
            j = find_end_of_inset(document.body, i)
        else:
            return
        if j != -1:
            beginPlain = find_token(document.body, "\\begin_layout Plain Layout", i)
            endPlain = find_end_of_layout(document.body, beginPlain)
            content = lyx2latex(document, document.body[beginPlain:endPlain])
            document.body[i : j + 1] = put_cmd_in_ert("\\latin{" + content + "}")
        else:
            document.warning("Malformed LyX document: Can't find end of flex inset Latin")
            return
        i += 1


fontsettings = [
    "\\font_roman",
    "\\font_sans",
    "\\font_typewriter",
    "\\font_math",
    "\\font_sf_scale",
    "\\font_tt_scale",
]
fontdefaults = ["default", "default", "default", "auto", "100", "100"]
fontquotes = [True, True, True, True, False, False]


def convert_fontsettings(document):
    "Duplicate font settings"

    i = find_token(document.header, "\\use_non_tex_fonts ", 0)
    if i == -1:
        document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
        use_non_tex_fonts = "false"
    else:
        use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
    j = 0
    for f in fontsettings:
        i = find_token(document.header, f + " ", 0)
        if i == -1:
            document.warning("Malformed LyX document: No " + f + "!")
            # we can fix that
            # note that with i = -1, this will insert at the end
            # of the header
            value = fontdefaults[j]
        else:
            value = document.header[i][len(f) :].strip()
        if fontquotes[j]:
            if use_non_tex_fonts == "true":
                document.header[i : i + 1] = [f + ' "' + fontdefaults[j] + '" "' + value + '"']
            else:
                document.header[i : i + 1] = [f + ' "' + value + '" "' + fontdefaults[j] + '"']
        else:
            if use_non_tex_fonts == "true":
                document.header[i : i + 1] = [f + " " + fontdefaults[j] + " " + value]
            else:
                document.header[i : i + 1] = [f + " " + value + " " + fontdefaults[j]]
        j = j + 1


def revert_fontsettings(document):
    "Merge font settings"

    i = find_token(document.header, "\\use_non_tex_fonts ", 0)
    if i == -1:
        document.warning("Malformed LyX document: No \\use_non_tex_fonts!")
        use_non_tex_fonts = "false"
    else:
        use_non_tex_fonts = get_value(document.header, "\\use_non_tex_fonts", i)
    j = 0
    for f in fontsettings:
        i = find_token(document.header, f + " ", 0)
        if i == -1:
            document.warning("Malformed LyX document: No " + f + "!")
            j = j + 1
            continue
        line = get_value(document.header, f, i)
        if fontquotes[j]:
            q1 = line.find('"')
            q2 = line.find('"', q1 + 1)
            q3 = line.find('"', q2 + 1)
            q4 = line.find('"', q3 + 1)
            if q1 == -1 or q2 == -1 or q3 == -1 or q4 == -1:
                document.warning("Malformed LyX document: Missing quotes!")
                j = j + 1
                continue
            if use_non_tex_fonts == "true":
                document.header[i : i + 1] = [f + " " + line[q3 + 1 : q4]]
            else:
                document.header[i : i + 1] = [f + " " + line[q1 + 1 : q2]]
        else:
            if use_non_tex_fonts == "true":
                document.header[i : i + 1] = [f + " " + line.split()[1]]
            else:
                document.header[i : i + 1] = [f + " " + line.split()[0]]
        j = j + 1


def revert_solution(document):
    "Reverts the solution environment of the theorem module to TeX code"

    # Do we use one of the modules that provides Solution?
    have_mod = False
    mods = document.get_module_list()
    for mod in mods:
        if (
            mod == "theorems-std"
            or mod == "theorems-bytype"
            or mod == "theorems-ams"
            or mod == "theorems-ams-bytype"
        ):
            have_mod = True
            break
    if not have_mod:
        return

    consecutive = False
    is_starred = False
    i = 0
    while True:
        i = find_token(document.body, "\\begin_layout Solution", i)
        if i == -1:
            return

        is_starred = document.body[i].startswith("\\begin_layout Solution*")
        if is_starred == True:
            LaTeXName = "sol*"
            LyXName = "Solution*"
            theoremName = "newtheorem*"
        else:
            LaTeXName = "sol"
            LyXName = "Solution"
            theoremName = "newtheorem"

        j = find_end_of_layout(document.body, i)
        if j == -1:
            document.warning("Malformed LyX document: Can't find end of " + LyXName + " layout")
            i += 1
            continue

        # if this is not a consecutive env, add start command
        begcmd = []
        if not consecutive:
            begcmd = put_cmd_in_ert("\\begin{%s}" % (LaTeXName))

        # has this a consecutive theorem of same type?
        consecutive = document.body[j + 2] == "\\begin_layout " + LyXName

        # if this is not followed by a consecutive env, add end command
        if not consecutive:
            document.body[j : j + 1] = put_cmd_in_ert("\\end{%s}" % (LaTeXName)) + [
                "\\end_layout"
            ]

        document.body[i : i + 1] = ["\\begin_layout Standard", ""] + begcmd

        add_to_preamble(document, "\\theoremstyle{definition}")
        if is_starred or mod == "theorems-bytype" or mod == "theorems-ams-bytype":
            add_to_preamble(
                document, f"\\{theoremName}{{{LaTeXName}}}{{\\protect\\solutionname}}"
            )
        else:  # mod == "theorems-std" or mod == "theorems-ams" and not is_starred
            add_to_preamble(
                document,
                f"\\{theoremName}{{{LaTeXName}}}[thm]{{\\protect\\solutionname}}",
            )

        add_to_preamble(document, "\\providecommand{\\solutionname}{Solution}")
        i = j


def revert_verbatim_star(document):
    from lyx_2_1 import revert_verbatim

    revert_verbatim(document, True)


def convert_save_props(document):
    "Add save_transient_properties parameter."
    i = find_token(document.header, "\\begin_header", 0)
    if i == -1:
        document.warning("Malformed lyx document: Missing '\\begin_header'.")
        return
    document.header.insert(i + 1, "\\save_transient_properties true")


def revert_save_props(document):
    "Remove save_transient_properties parameter."
    i = find_token(document.header, "\\save_transient_properties", 0)
    if i == -1:
        return
    del document.header[i]


def convert_info_tabular_feature(document):
    def f(arg):
        return arg.replace("inset-modify tabular", "tabular-feature")

    convert_info_insets(document, "shortcut(s)?|icon", f)


def revert_info_tabular_feature(document):
    def f(arg):
        return arg.replace("tabular-feature", "inset-modify tabular")

    convert_info_insets(document, "shortcut(s)?|icon", f)


##
# Conversion hub
#

supported_versions = ["2.2.0", "2.2"]
convert = [
    [475, [convert_separator]],
    # nothing to do for 476: We consider it a bug that older versions
    # did not load amsmath automatically for these commands, and do not
    # want to hardcode amsmath off.
    [476, []],
    [477, []],
    [478, []],
    [479, []],
    [480, []],
    [481, [convert_dashes]],
    [482, [convert_phrases]],
    [483, [convert_specialchar]],
    [484, []],
    [485, []],
    [486, []],
    [487, []],
    [488, [convert_newgloss]],
    [489, [convert_BoxFeatures]],
    [490, [convert_origin]],
    [491, []],
    [492, [convert_colorbox]],
    [493, []],
    [494, []],
    [495, [convert_subref]],
    [496, [convert_nounzip]],
    [497, [convert_external_bbox]],
    [498, []],
    [499, [convert_moderncv_phone, convert_moderncv_name]],
    [500, []],
    [501, [convert_fontsettings]],
    [502, []],
    [503, []],
    [504, [convert_save_props]],
    [505, []],
    [506, [convert_info_tabular_feature]],
    [507, [convert_longtable_label]],
    [508, [convert_parbreak]],
]

revert = [
    [507, [revert_parbreak]],
    [506, [revert_longtable_label]],
    [505, [revert_info_tabular_feature]],
    [504, []],
    [503, [revert_save_props]],
    [502, [revert_verbatim_star]],
    [501, [revert_solution]],
    [500, [revert_fontsettings]],
    [499, [revert_achemso]],
    [498, [revert_moderncv_1, revert_moderncv_2]],
    [
        497,
        [
            revert_tcolorbox_1,
            revert_tcolorbox_2,
            revert_tcolorbox_3,
            revert_tcolorbox_4,
            revert_tcolorbox_5,
            revert_tcolorbox_6,
            revert_tcolorbox_7,
            revert_tcolorbox_8,
        ],
    ],
    [496, [revert_external_bbox]],
    [495, []],  # nothing to do since the noUnzip parameter was optional
    [494, [revert_subref]],
    [493, [revert_jss]],
    [492, [revert_mathmulticol]],
    [491, [revert_colorbox]],
    [490, [revert_textcolor]],
    [489, [revert_origin]],
    [488, [revert_BoxFeatures]],
    [487, [revert_newgloss, revert_glossgroup]],
    [486, [revert_forest]],
    [485, [revert_ex_itemargs]],
    [484, [revert_sigplan_doi]],
    [483, [revert_georgian]],
    [482, [revert_specialchar]],
    [481, [revert_phrases]],
    [480, [revert_dashes]],
    [479, [revert_question_env]],
    [478, [revert_beamer_lemma]],
    [477, [revert_xarrow]],
    [476, [revert_swissgerman]],
    [475, [revert_smash]],
    [474, [revert_separator]],
]


if __name__ == "__main__":
    pass
