# OBSAH SOUBORU:
# - funkce pro tvorbu vystupnich retezcu

import re
from collections import Counter
from typing import Union

import legend_entries
from classes import CCell, CRow, CCol, CCellDO
from enums import EFontSize, EFontFlags, EHAlign, EVAlign, EColor, EConvertedState, EMergedCell, EBorder, EBorderStyle, \
    ELtxTableType
import utils
import settings


def make_ltx_begin():
    """Funkce tvori zacatek celeho Latex retezce, tj. nadefinuje potrebna prostredi pomoci \\begin{env}. Potrebna
    prostredi se lisi v zavislosti na typu vystupni tabulky (otocena, dlouha apod.)."""

    # float nastaveni
    if settings.ltx.table_hbt:
        hbt = f'[{settings.ltx.table_hbt}]'
    else:
        hbt = ''

    # pozice titulku

    # normalni
    if settings.ltx.table_type == ELtxTableType.NORMAL:
        if settings.ltx.caption_top:
            begin_str = '\n\\begin{table}' + hbt \
                        + '\n\t\\centering\n' \
                        + f'\t\\caption{{{settings.ltx.caption}}}\n' \
                        + '\t\\begin{tblr}{\n'
        else:
            begin_str = '\n\\begin{table}' + hbt \
                        + '\n\t\\centering\n' \
                          '\t\\begin{tblr}{\n'

        settings.TABS = '\t\t'
        settings.warn.rotated_text = False

    # otocena (table -> sidewaystable)
    elif settings.ltx.table_type == ELtxTableType.ROTATED:
        if settings.ltx.caption_top:
            begin_str = '\n\\begin{sidewaystable}' + hbt \
                        + '\n\t\\centering\n' \
                        + f'\t\\caption{{{settings.ltx.caption}}}\n' \
                        + '\t\\begin{tblr}{\n'
        else:
            begin_str = '\n\\begin{sidewaystable}' + hbt \
                        + '\n\t\\centering\n' \
                          '\t\\begin{tblr}{\n'

        settings.warn.rotated_text = True
        settings.TABS = '\t\t'

    # dlouha (odebrat prostedi 'table', misto tblr dat longtblr, odebrat tabulator)
    elif settings.ltx.table_type == ELtxTableType.LONG:
        begin_str = longtblr_cont()

        begin_str += '\n\\begin{longtblr}[\n' + \
                        f'\tcaption = {{{settings.ltx.caption}}}, \n' + \
                        '\t% label = {upravME},\n' \
                     ']{\n'
        settings.TABS = '\t'
        settings.warn.rotated_text = False

    # otocena pres rotatebox (tblr se da do rotatebox -> pridat vsude o tabulator vic),
    else:
        settings.TABS = '\t\t\t'
        settings.warn.rotated_text = True
        if settings.ltx.rotate_ccw:
            rot = '90'
        else:
            rot = '-90'

        if settings.ltx.caption_top:
            begin_str = '\n\\begin{table}' + hbt + \
                              '\n\t\\centering\n' + \
                              f'\t\\caption = {{{settings.ltx.caption}}}, \n' + \
                              '\t\\rotatebox{' + rot + '}{%' \
                                  '\n\t\t\\begin{tblr}{\n'
        else:
            begin_str = '\n\\begin{table}' + hbt + \
                        '\n\t\\centering\n' \
                        '\t\\rotatebox{' + rot + '}{%' \
                                                 '\n\t\t\\begin{tblr}{\n'
    return begin_str


def make_ltx_end():
    """Funkce tvori konec celeho Latex retezce, tj. ukonci pouzita prostredi pomoci \\end{env}"""
    if settings.ltx.table_type == ELtxTableType.NORMAL:
        if settings.ltx.caption_top:
            end_str =     '\t\\end{tblr}\n' \
                      '\\end{table}\n'
        else:
            end_str =     '\t\\end{tblr}\n' \
                          '\t\\caption{' + settings.ltx.caption + '}' \
                      '\n\\end{table}\n'

    elif settings.ltx.table_type == ELtxTableType.ROTATED:
        if settings.ltx.caption_top:
            end_str =     '\t\\end{tblr}\n' \
                      '\\end{sidewaystable}\n'
        else:
            end_str =     '\t\\end{tblr}\n' \
                          '\t\\caption{' + settings.ltx.caption + '}' \
                      '\n\\end{sidewaystable}\n'

    elif settings.ltx.table_type == ELtxTableType.LONG:
        end_str = '\\end{longtblr}\n'

    else:
        if settings.ltx.caption_top:
            end_str =           '\t\t\\end{tblr}\n' \
                            '\t}\n' \
                            '\t\\caption{' + settings.ltx.caption + '}' \
                      '\n\\end{table}\n'
        else:
            end_str =        '\t\t\\end{tblr}\n' \
                          '\t}\n' \
                       '\\end{table}\n'

    return end_str


def longtblr_head():
    """Funkce tvori dva dodatecne parametry hlavicky vyskytujici se pouze u dlouhych tabulek. Lze jich tvorit mnohem
    vice ale tyhle jsou fakt zasadni."""
    long_head_str = ''

    # pocet radku (shora) ktere vzdy zobrazit
    if settings.ltx.longt.head_row_count > 0:
        long_head_str += f'\trowhead = {settings.ltx.longt.head_row_count}, \n'

    # pocet radku (zdola) ktere vzdy zobrazit
    if settings.ltx.longt.total_row_count > 0:
        long_head_str += f'\trowfoot = {settings.ltx.longt.total_row_count}, \n'

    return long_head_str


def longtblr_cont():
    """Funkce vytvori napisy pro rozdelene casti dlouhych tabulek:
        - 'pokracuje na dalsi strane',
        - '(pokracovani)', apod.
    Prvni cast je ta zobrazena v miste rozdeleni tabulky (dole na strane), cast druha bude nakonci titulku
    rozdelenych casti"""
    cont_text = ''

    if settings.ltx.longt.contfoot_text:
        cont_text += r'\DefTblrTemplate{contfoot-text}{default}{' + settings.ltx.longt.contfoot_text + '}\n'
    if settings.ltx.longt.conthead_text:
        cont_text += r'\DefTblrTemplate{conthead-text}{default}{' + settings.ltx.longt.conthead_text + '}\n'

    return cont_text


def make_colspec(all_cols: 'list[CCol]'):
    """Funkce vytvari tzv. colspec vystupni tabulky, tj. specifikaci o horinzontalnim zarovnani a relativni sirce
    textu VSECH sloupcu.
    Colspec je vzdy tvoren v pripade ze je zapnuto 'Horizontalni zarovnani' v rozhrani konvertoru, ne vzdy ale obsahuje
    uzitecne informace. Behem prevodu mohou byt informace o zarovnani ulozeny do objektu typu CRow a z bunek smazany,
    cimz vznikne colspec obsahujuci sama 'l', prestoze skutecne zarovnani muze byt odlisne."""
    if not settings.conv.halign:
        return ''

    final_colspec = ''

    # uvazuje se relativni sirka
    if settings.conv.width:
        for col in all_cols:
            if col.shared_text_halign != EHAlign.NONE:
                # relativni sirka nemusi byt 0-1, tabularray si poradi s jakymkoli pomerem
                final_colspec += f'X[{round(col.hsize, 1)}, {col.shared_text_halign.value}] '
                col.shared_text_halign = EHAlign.NONE
            else:
                final_colspec += f'X[{round(col.hsize, 1)}] '

        settings.legend._colspec = final_colspec != ''
        return final_colspec

    # chceme jen zarovnani
    for col in all_cols:
        if col.shared_text_halign != EHAlign.NONE:
            final_colspec += f'{col.shared_text_halign.value}'
            col.shared_text_halign = EHAlign.NONE
        else:
            # zjistim dominatni format v ramci sloupce.
            # doufam ze to nebude 65465465 iteraci k tomu abych dostal 'l'
            # zajimaji me jen 2 nejcastejsi zarovnani (pro pripad ze nejcastejsi by bylo NONE)
            col_haligns = [x.dxf.text_halign for x in col.members]
            sorted_haligns = Counter(col_haligns).most_common(2)
            dominant_halign = sorted_haligns[0][0]

            if dominant_halign == EHAlign.NONE and len(sorted_haligns) > 1:
                dominant_halign = sorted_haligns[1][0]

            if dominant_halign != EHAlign.NONE:
                final_colspec += dominant_halign.value
            # pripradit tam neco musim, protoze nevim jestli jsou vsechny none nebo ne. uvnitr
            # colspec musi byt format vsech radku, nema jak uhodnout ktery jsem zrovna myslel.
            # Q je obecny typ sloupce, ze ktereho jsou vsechny dalsi typy odvozeny, prakticky
            # tim rikam "delej si co chces"
            else:
                final_colspec += 'Q'

    # obsahuje jen same Q (tj. je k nicemu)
    if re.search(r'^Q+$', final_colspec) is not None:
        final_colspec = ''

    settings.legend._colspec = final_colspec != ''
    return final_colspec


def make_rowspec(all_rows: 'list[CRow]'):
    """Funkce vytvari tzv. rowspec, tj specifikaci o zarovnani a vysce VSECH radku vystupni tabulky. Prakticky ma vyznam
    jej tvorit jen v pripade, ze pocet radku obsahujuci informace POUZE o vert. zarovnani je vetsi nez 2, v tom pripade
    bude usetreno alespon nejake misto ve vystupu."""

    if not settings.conv.valign:
        return ''

    # ma smysl ho tvorit?
    maybe_worth = 0
    for row in all_rows:
        has_halign_only, has_valign_only = row.has_align_only()
        if has_valign_only:
            maybe_worth += 1

        if maybe_worth > 1:
            break

    # nema
    if maybe_worth < 2:
        return ''

    # ma
    rowspec = ''
    for row in all_rows:
        # zarovnany cely radek a povoleno zarovnani
        if settings.conv.valign and settings.conv.height and row.shared_text_valign != EVAlign.NONE:
            rowspec += f'{row.shared_text_valign.value}{{{row.vsize:.1f}pt}} '
            row.shared_text_valign = EVAlign.NONE
        # povoleno zarovnani ale radek neni vsude stejne zarovnan
        elif settings.conv.valign and settings.conv.height:
            row_valigns = [x.dxf.text_valign for x in row.members]
            sorted_valigns= Counter(row_valigns).most_common(2)
            dominant_valign = sorted_valigns[0][0]

            if dominant_valign == EVAlign.NONE and len(sorted_valigns) > 1:
                dominant_valign = sorted_valigns[1][0]

            if dominant_valign != EVAlign.NONE:
                rowspec += f'{dominant_valign.value}{{{row.vsize:.1f}pt}}'
        # povolena jen vyska radku
        elif settings.conv.height:
            rowspec += f'Q[{row.vsize:.1f}pt] '
        # radek nema zadne info v sobe (nebo je vse vypnuto)
        else:
            rowspec += 'Q'

    # obsahuje jen same Q
    if re.search(r'^Q+$', rowspec) is not None:
        rowspec = ''

    settings.legend._rowspec = rowspec != ''
    return rowspec


def export_head(all_rows: 'list[CRow]', ltx_rows: 'dict[int, str]', ltx_cols: 'dict[int, str]',
                ltx_hlines: 'list[str]', ltx_vlines: 'list[str]', colspec: str, rowspec: str):
    """Funkce hromadne vytiskne vse co jsme dosud prevadeli a co se ma vyskytovat v hlavicce tabulky.

       Patri sem:
           - spolecne vlastnosti radku a sloupcu (row{i} = , column{i} = ) (vzdy)
           - colspec = , rowspec = , hline{i} = , vline{i} = , cell{i}{j} =  (pokud jsou zapnuty)"""

    # TODO: PRIDAT LEGENDU, nejspis tady nebo kousek dal zkoumat co v ni bude muset byt
    output_str = ''
    tabs = settings.TABS
    tab_len = settings.TABS.count('\t') * 4

    # legenda zvlast
    if settings.legend.on_top:
        if settings.legend._colspec:
            output_str += legend_entries.make_legend_entry('colspec')

        if settings.legend._rowspec:
            output_str += legend_entries.make_legend_entry('rowspec')

        if settings.legend._row:
            output_str += legend_entries.make_legend_entry('row')

        if settings.legend._rows:
            output_str += legend_entries.make_legend_entry('rows')

        if settings.legend._column:
            output_str += legend_entries.make_legend_entry('column')

        if settings.legend._columns:
            output_str += legend_entries.make_legend_entry('columns')

        if settings.legend._hline:
            output_str += legend_entries.make_legend_entry('hline')

        if settings.legend._hlines:
            output_str += legend_entries.make_legend_entry('hlines')

        if settings.legend._vline:
            output_str += legend_entries.make_legend_entry('vline')

        if settings.legend._vlines:
            output_str += legend_entries.make_legend_entry('vlines')

        if not settings.ltx.use_setcell:
            if settings.legend._cell:
                output_str += legend_entries.make_legend_entry('cell')

    # syntaxe a hodnoty pro prikaz 'row' v hlavicce
    if not settings.legend.on_top and settings.legend._row:
        output_str += legend_entries.make_legend_entry('row')

    if not settings.legend.on_top and settings.legend._rows:
        output_str += legend_entries.make_legend_entry('rows')

    for fmt in ltx_rows.values():
        if fmt is not None:
            output_str += f'{fmt}, \n'

    if not settings.legend.on_top and settings.legend._column:
        output_str += legend_entries.make_legend_entry('column')

    if not settings.legend.on_top and settings.legend._columns:
        output_str += legend_entries.make_legend_entry('columns')

    for fmt in ltx_cols.values():
        if fmt is not None:
            output_str += f'{fmt}, \n'

    if not settings.legend.on_top and settings.legend._hline:
        output_str += legend_entries.make_legend_entry('hline')

    if not settings.legend.on_top and settings.legend._hlines:
        output_str += legend_entries.make_legend_entry('hlines')

    for hline in ltx_hlines:
        output_str += f'{hline}, \n'

    if not settings.legend.on_top and settings.legend._vline:
        output_str += legend_entries.make_legend_entry('vline')

    if not settings.legend.on_top and settings.legend._vlines:
        output_str += legend_entries.make_legend_entry('vlines')

    for vline in ltx_vlines:
        output_str += f'{vline}, \n'

    if not settings.ltx.use_setcell:
        if not settings.legend.on_top and settings.legend._cell:
            output_str += legend_entries.make_legend_entry('cell')

        for row in all_rows:
            row_str = ''

            # slouzi k zalomeni prilis dlouhych radku. max delka radku jde nastavit
            # lze praktikovat jen pro cele prikazy
            row_char_count = tab_len
            for my_cell in row.members:
                converted = c_cell_to_ltx(my_cell, colspec, rowspec)
                if converted not in [None, '']:
                    row_char_count += len(converted) + 2

                    if row_char_count < settings.ltx.max_char_width:
                        row_str += f'{converted}, '
                    else:
                        row_str += f'\n{tabs}{converted}, '
                        row_char_count = tab_len + len(converted) + 2

            if row_str != '':
                output_str += f'{tabs}{row_str}\n'

    return output_str


def export_head_dataonly(all_cells: 'list[list[CCellDO]]', custom_head: str = ''):
    # tabulace
    output_str = ''
    tabs = settings.TABS
    tab_len = settings.TABS.count('\t') * 4

    if custom_head != legend_entries.custom_head_intro:
        custom_head_lines = custom_head.splitlines(False)

        for line in custom_head_lines:
            output_str += f'{tabs}{line}\n'

    # pridani sloucenych bunek
    if not settings.ltx.use_setcell:
        for row in all_cells:
            row_str = ''

            # slouzi k zalomeni prilis dlouhych radku. max delka radku jde nastavit.
            # lze praktikovat jen pro cele prikazy
            row_char_count = tab_len
            for my_cell in row:
                converted = c_celldo_to_ltx(my_cell)
                if converted not in [None, '']:
                    row_char_count += len(converted) + 2

                    if row_char_count < settings.ltx.max_char_width:
                        row_str += f'{converted}, '
                    else:
                        row_str += f'\n{tabs}{converted}, '
                        row_char_count = tab_len + len(converted) + 2

            if row_str != '':
                output_str += f'{tabs}{row_str}\n'

    if not settings.legend.on_top and settings.legend._cell:
        output_str = legend_entries.make_legend_entry('cell') + output_str

    return output_str


def export_body(all_rows: 'list[CRow]', colspec: str = '', rowspec: str = ''):
    """Funkce hromadne vytiskne vse co jsme dosud prevadeli a co se ma vyskytovat v tele tabulky. Snazi se to tisknout,
    tak, aby byl vystup zarovnan podle '&'.

    Patri sem:
        - hodnoty bunek (vzdy)
        - prikazy \\SetCell (pokud jsou zapnuty)"""

    max_col_char_widths = {}
    all_cells_ltx = []
    output_str = ''
    tabs = settings.TABS
    tab_len = settings.TABS.count('\t') * 4

    if settings.ltx.use_setcell:
        if not settings.legend.on_top and settings.legend._cell:
            output_str += legend_entries.make_legend_entry('setcell')

    if settings.ltx.align:
        # vytvorit vystup v latexu, ulozit si ho, zjistit kolik znaku ma aby sel pekne vytisknout
        for row in all_rows:
            cur_row = []
            cur_col = 1

            for my_cell in row.members:
                converted = ''
                if settings.ltx.use_setcell:
                    ccc = c_cell_to_ltx(my_cell, colspec, rowspec)
                    if ccc is not None:
                        converted += ccc + f' {my_cell.disp_value}'
                    else:
                        converted += f'{my_cell.disp_value}'

                else:
                    if my_cell.is_merged == EMergedCell.MERGED_EMPTY:
                        converted += ''
                    else:
                        converted += f'{my_cell.disp_value}'

                cur_row.append(converted)
                cur_len = len(converted)

                if cur_col in max_col_char_widths:
                    if cur_len > max_col_char_widths[cur_col]:
                        max_col_char_widths[cur_col] = cur_len
                else:
                    max_col_char_widths[cur_col] = cur_len

                cur_col += 1

            all_cells_ltx.append(cur_row)

        # druhy pruchod, ktery to udela fancy a vytiskne
        for row in all_cells_ltx:
            row_str = ''
            cur_col = 1
            row_char_count = tab_len

            for cell in row:
                if cur_col in max_col_char_widths:
                    cell_str = f'{cell:<{max_col_char_widths[cur_col]}} & '
                else:
                    cell_str = f' & '

                cur_col += 1
                row_char_count += len(cell_str)

                if row_char_count < settings.ltx.max_char_width:
                    row_str += cell_str
                else:
                    row_str += f'\n{tabs}{cell_str}'
                    row_char_count = tab_len + len(cell_str) + 2

            if len(row_str) > 2:
                output_str += f'{tabs}{row_str[:-2]}\\\\\n'

    else:
        for row in all_rows:
            row_str = ''
            row_char_count = tab_len

            for my_cell in row.members:
                converted = ''
                if settings.ltx.use_setcell:
                    ccc = c_cell_to_ltx(my_cell, colspec, rowspec)
                    if ccc is not None:
                        converted += ccc + f' {my_cell.disp_value}'
                    else:
                        converted += f'{my_cell.disp_value} & '

                else:
                    if my_cell.is_merged == EMergedCell.MERGED_EMPTY:
                        converted += ' & '
                    else:
                        converted += f'{my_cell.disp_value} & '

                row_char_count += len(converted)
                if row_char_count < settings.ltx.max_char_width:
                    row_str += converted
                else:
                    row_str += f'\n{tabs}{converted}'
                    row_char_count = tab_len + len(converted) + 2

            if len(row_str) > 2:
                output_str += f'{tabs}{row_str[:-2]}\\\\\n'

    return output_str


def export_body_data_only(all_cells: 'list[list[CCellDO]]'):
    max_col_char_widths = {}
    all_cells_ltx = []
    output_str = ''
    tabs = settings.TABS
    tab_len = settings.TABS.count('\t') * 4

    if not settings.legend.on_top and settings.legend._setcell:
        output_str += legend_entries.make_legend_entry('setcell')

    if settings.ltx.align:
        for row in all_cells:
            cur_row = []
            cur_col = 1

            for my_cell in row:
                converted = ''
                if settings.ltx.use_setcell:
                    ccc = c_celldo_to_ltx(my_cell)
                    if ccc is not None:
                        converted += ccc + f' {my_cell.disp_value}'
                    else:
                        converted += f'{my_cell.disp_value}'

                else:
                    if my_cell.is_merged == EMergedCell.MERGED_EMPTY:
                        converted += ''
                    else:
                        converted += f'{my_cell.disp_value}'

                cur_row.append(converted)
                cur_len = len(converted)

                if cur_col in max_col_char_widths:
                    if cur_len > max_col_char_widths[cur_col]:
                        max_col_char_widths[cur_col] = cur_len
                else:
                    max_col_char_widths[cur_col] = cur_len

                cur_col += 1

            all_cells_ltx.append(cur_row)

        # druhy pruchod, ktery to udela fancy :-)
        for row in all_cells_ltx:
            row_str = ''
            cur_col = 1
            row_char_count = tab_len

            for cell in row:
                if cur_col in max_col_char_widths:
                    cell_str = f'{cell:<{max_col_char_widths[cur_col]}} & '
                else:
                    cell_str = f' & '

                cur_col += 1
                row_char_count += len(cell_str)
                if row_char_count < settings.ltx.max_char_width:
                    row_str += cell_str
                else:
                    row_str += f'\n{tabs}{cell_str}'
                    row_char_count = tab_len + len(cell_str) + 2

            if len(row_str) > 2:
                output_str += f'{tabs}{row_str[:-2]}\\\\\n'
    else:
        for row in all_cells:
            row_str = ''
            row_char_count = tab_len

            for my_cell in row:
                converted = ''
                if settings.ltx.use_setcell:
                    ccc = c_celldo_to_ltx(my_cell)
                    if ccc is not None:
                        converted += ccc + f' {my_cell.disp_value}'
                    else:
                        converted += f'{my_cell.disp_value} & '

                else:
                    if my_cell.is_merged == EMergedCell.MERGED_EMPTY:
                        converted += ' & '
                    else:
                        converted += f'{my_cell.disp_value} & '

                row_char_count += len(converted)
                if row_char_count < settings.ltx.max_char_width:
                    row_str += converted
                else:
                    row_str += f'\n{tabs}{converted}'
                    row_char_count = tab_len

            if len(row_str) > 2:
                output_str += f'{tabs}{row_str[:-2]}\\\\\n'

    return output_str


def c_celldo_to_ltx(my_cell: CCellDO):
    size_str = ''

    if my_cell.is_merged != EMergedCell.NOT_MERGED:
        if settings.ltx.use_setcell:
            if my_cell.merged_size[0] == 1:
                if my_cell.merged_size[1] != 1:
                    size_str = f'\\SetCell[r={my_cell.merged_size[1]}]'
                    settings.legend._setcell = True
            else:
                settings.legend._setcell = True
                if my_cell.merged_size[1] == 1:
                    size_str = f'\\SetCell[c={my_cell.merged_size[0]}]'
                else:
                    size_str = f'\\SetCell[r={my_cell.merged_size[1]}, c={my_cell.merged_size[0]}]'

        else:
            if my_cell.merged_size[0] == 1:
                if my_cell.merged_size[1] != 1:
                    size_str = f'cell{{{my_cell.y}}}{{{my_cell.x}}} = {{r={my_cell.merged_size[1]}}}'
                    settings.legend._cell = True
            else:
                settings.legend._cell = True
                if my_cell.merged_size[1] == 1:
                    size_str = f'cell{{{my_cell.y}}}{{{my_cell.x}}} = {{c={my_cell.merged_size[0]}}}'
                else:
                    size_str = f'cell{{{my_cell.y}}}{{{my_cell.x}}} = ' \
                               f'{{r={my_cell.merged_size[1]}, c={my_cell.merged_size[0]}}}'

    if size_str == '':
        return ''

    return f'{size_str}{{}}'


def c_cell_to_ltx(my_cell: CCell, colspec: str, rowspec: str, only_fmt=False):
    """Funkce prevadi formaty bunek, ktere nebyly v ramci radku ani slopce sdilene, na format vhodny pro tabularray.
    Take se postara o spravny tabularray format sloucenych bunek."""

    fmt_str = ''
    size_str = ''

    if my_cell.is_converted == (EConvertedState.CONV_COL | EConvertedState.CONV_ROW):
        return None

    if my_cell.is_merged == EMergedCell.MERGED_EMPTY:
        return ''

    if settings.conv.color.enable and settings.conv.color.fg and my_cell.dxf.fgcolor != EColor.NOT_SET:
        fmt_str += f'fg={my_cell.dxf.fgcolor.value}, '
        settings.legend._fg = True

    if settings.conv.color.enable and settings.conv.color.bg and my_cell.dxf.bgcolor not in [EColor.NOT_SET, EColor.WHITE]:
        fmt_str += f'bg={my_cell.dxf.bgcolor.value}, '
        settings.legend._bg = True

    if my_cell.dxf.text_halign != EHAlign.NONE:
        # nebudeme tisknout zarovnani bunky pokud je totozne se zarovnanim v colspec
        if colspec:
            try:
                colspec_align = colspec[my_cell.x - 1]
                if colspec_align != my_cell.dxf.text_halign.value:
                    fmt_str += f'{my_cell.dxf.text_halign.value}, '
                    settings.legend._halign = True
            except IndexError:
                # nemelo by se stat, proto tedy neznama chyba. tvorba colspec vychazi z toho co je ulozeno
                # v seznamu vsech bunek (resp. seznamu vsech sloupcu, ktere ale obsahuji vsechny bunky),
                # takze tam nemuze byt min prvku. maximalne se muze stat ze colspec == ''
                raise RuntimeError('Neznámá chyba.')
        else:
            fmt_str += f'{my_cell.dxf.text_halign.value}, '
            settings.legend._halign = True

    if my_cell.dxf.text_valign != EVAlign.NONE:
        if rowspec:
            try:
                rowspec_align = rowspec[my_cell.y - 1]
                if rowspec_align != my_cell.dxf.text_valign.value:
                    fmt_str += f'{my_cell.dxf.text_valign.value}, '
                    settings.legend._valign = True
            except IndexError:
                # nemelo by se stat, proto tedy neznama chyba. tvorba colspec vychazi z toho co je ulozeno
                # v seznamu vsech bunek (resp. seznamu vsech sloupcu, ktere ale obsahuji vsechny bunky),
                # takze tam nemuze byt min prvku. maximalne se muze stat ze colspec == ''
                raise RuntimeError('Neznámá chyba.')

        fmt_str += f'{my_cell.dxf.text_valign.value}, '
        settings.legend._valign = True

    if settings.conv.text.enable and \
            settings.conv.text.flags and \
            my_cell.dxf.font_flags & EFontFlags.UNDER:
        fmt_str += r'cmd={\underline}, '
        settings.legend._latex_cmd = True

    if settings.conv.text.enable and settings.conv.text.flags and my_cell.dxf.font_flags & EFontFlags.DOUBLE_UNDER:
        fmt_str += r'cmd={\uuline}, '
        settings.legend._latex_cmd = True
        settings.warn.uuline = True

    font_str = 'font='
    if settings.conv.text.enable and settings.conv.text.flags:
        if my_cell.dxf.font_flags & EFontFlags.BOLD:
            font_str += r'\bfseries'
        if my_cell.dxf.font_flags & EFontFlags.ITALIC:
            font_str += r'\slshape' if settings.ltx.use_slshape else r'\itshape'

    if settings.conv.text.enable and settings.conv.text.textsize and my_cell.dxf.font_size != EFontSize.NORMAL:
        font_str += my_cell.dxf.font_size.value

    if font_str != 'font=':
        fmt_str += f'{font_str}, '
        settings.legend._font_cmds = True

    if len(fmt_str) > 2:
        fmt_str = fmt_str[:-2]

    if only_fmt:
        return fmt_str

    if fmt_str or my_cell.is_merged == EMergedCell.MERGED:
        if settings.ltx.use_setcell:
            settings.legend._setcell = True

            if my_cell.merged_size[0] == 1:
                if my_cell.merged_size[1] == 1:             # 1 1
                    size_str = '\\SetCell'
                else:
                    size_str = f'\\SetCell[r={my_cell.merged_size[1]}]' # 1 j
            else:
                if my_cell.merged_size[1] == 1:
                    size_str = f'\\SetCell[c={my_cell.merged_size[0]}]' # i 1
                else:
                    size_str = f'\\SetCell[r={my_cell.merged_size[1]}, c={my_cell.merged_size[0]}]' # i j

        else:
            settings.legend._cell = True

            if my_cell.merged_size[0] == 1:
                if my_cell.merged_size[1] == 1:
                    size_str = f'cell{{{my_cell.y}}}{{{my_cell.x}}} = '
                else:
                    size_str = f'cell{{{my_cell.y}}}{{{my_cell.x}}} = {{r={my_cell.merged_size[1]}}}'
            else:
                if my_cell.merged_size[1] == 1:
                    size_str = f'cell{{{my_cell.y}}}{{{my_cell.x}}} = {{c={my_cell.merged_size[0]}}}'
                else:
                    size_str = f'cell{{{my_cell.y}}}{{{my_cell.x}}} = ' \
                               f'{{r={my_cell.merged_size[1]}, c={my_cell.merged_size[0]}}}'

    if size_str == '' and fmt_str == '':
        return ''

    return f'{size_str}{{{fmt_str}}}'


def c_row_to_ltx(my_row: Union[CRow, CCol], index: str, rowspec_exists: bool,
                 colspec_exists: bool, only_fmt: bool = False):
    """Funkce prevadi spolecne vlastnosti radku na str vystup vhodny do latex. Funguje i pro sloupce.
    Returns:
        str ve tvaru 'row{indexy} = {spolecne_formaty}' nebo 'column{indexy} = {spolecne_formaty}'"""

    fmt_str = ''
    is_col = False
    tabs = settings.TABS

    if isinstance(my_row, CRow):
        row_or_col = 'row'
        if my_row.vsize > 0 and not rowspec_exists:
            fmt_str += f'ht={{{my_row.vsize:.1f}pt}}, '
            settings.legend._ht = True
    else:
        row_or_col = 'column'
        is_col = True
        if settings.conv.width and my_row.hsize > 0 and not colspec_exists:
            fmt_str += f'wd={{{my_row.hsize:.1f}pt}}, '
            settings.legend._wd = True

    if settings.conv.color.enable and settings.conv.color.fg and my_row.shared_fgcolor != EColor.NOT_SET:
        fmt_str += f'fg={my_row.shared_fgcolor.value}, '
        settings.legend._fg = True

    if settings.conv.color.enable and settings.conv.color.bg and my_row.shared_bgcolor not in [EColor.NOT_SET, EColor.WHITE]:
        fmt_str += f'bg={my_row.shared_bgcolor.value}, '
        settings.legend._bg = True

    if my_row.shared_text_halign != EHAlign.NONE:
        if (is_col and not colspec_exists) or (not is_col and not rowspec_exists):
            fmt_str += f'{my_row.shared_text_halign.value}, '
            settings.legend._halign = True

    if my_row.shared_text_valign != EVAlign.NONE:
        if (is_col and not colspec_exists) or (not is_col and not rowspec_exists):
            fmt_str += f'{my_row.shared_text_valign.value}, '
            settings.legend._valign = True

    if settings.conv.text.enable and settings.conv.text.flags and my_row.shared_font_flags & EFontFlags.UNDER:
        fmt_str += r'cmd={\underline}, '
        settings.legend._latex_cmd = True

    if settings.conv.text.enable and settings.conv.text.flags and my_row.shared_font_flags & EFontFlags.DOUBLE_UNDER:
        fmt_str += r'cmd={\uuline}, '
        settings.legend._latex_cmd = True
        settings.warn.uuline = True

    font_str = 'font='
    if settings.conv.text.enable and settings.conv.text.flags:
        if my_row.shared_font_flags & EFontFlags.BOLD:
            font_str += r'\bfseries'
        if my_row.shared_font_flags & EFontFlags.ITALIC:
            font_str += r'\slshape' if settings.ltx.use_slshape else r'\itshape'

        # radek/sloupec musi mit spolecny jak styl, tak i velikost. nelze nastavit jen cast.
        if settings.conv.text.enable and settings.conv.text.textsize and my_row.shared_font_size != EFontSize.NORMAL:
            font_str += my_row.shared_font_size.value

    if font_str != 'font=':
        fmt_str += f'{font_str}, '
        settings.legend._font_cmds = True

    if fmt_str != '':
        fmt_str = fmt_str[:-2]

        if only_fmt:
            return fmt_str

        if index == 'all':
            if row_or_col == 'row':
                settings.legend._rows = True
            else:
                settings.legend._columns = True

            return f'{tabs}{row_or_col}s = {{{fmt_str}}}'

        if row_or_col == 'row':
            settings.legend._row = True
        else:
            settings.legend._column = True

        return f'{tabs}{row_or_col}{{{index}}} = {{{fmt_str}}}'

    return None


def border_to_ltx(my_row: Union[CRow, CCol], segmented: bool):
    """Fuknce ziska typ a barvu horizontalnich okraju v ramci jednoho radku. Protoze kazda bunka muze teoreticky mit
    jedinecny dolni okraj, udela se z toho list, vsech stylu ktere se v radku nachazi. Funguje i pro svisle okraje,
    ty ale zkouma pouze v ramci 1 sloupce.

    Returns:
        potrebujeme-li ve finale 'hline{indexy} = {cols}{fmt}, pak tato funkce tvori 'cols' a 'fmt' jako list[str]"""

    if my_row is None:
        return []

    if not my_row.members:
        return []

    single_hline = []
    bottom_or_right = EBorder.BOTTOM if isinstance(my_row, CRow) else EBorder.RIGHT
    idx = 0 if isinstance(my_row, CRow) else 1

    if not segmented:
        settings.legend._typ_okraje = True

        if my_row.whole_borders != EBorderStyle.DOUBLE:
            if my_row.whole_borders_clr != EColor.NOT_SET and my_row.whole_borders != EBorderStyle.THIN:
                return [f'{{{my_row.whole_borders.value}, {my_row.whole_borders_clr.value}}}']

            if my_row.whole_borders_clr == EColor.NOT_SET:
                if my_row.whole_borders != EBorderStyle.THIN:
                    return [f'{{{my_row.whole_borders.value}}}']

                return [f'{{}}']

            if my_row.whole_borders == EBorderStyle.THIN:
                return [f'{{{my_row.whole_borders_clr.value}}}']
        else:
            if my_row.whole_borders_clr != EColor.NOT_SET:
                return [f'{{1}}{{-}}{{{my_row.whole_borders_clr.value}}}'
                        f'_CUT{{2}}{{-}}{{{my_row.whole_borders_clr.value}}}']

            else:
                return [f'{{1}}{{-}}{{}}_CUT{{2}}{{-}}{{}}']

    border_styles_in_row: dict[tuple[EBorderStyle, EColor], Union[list[int], str]] = {}
    cur_col = 1

    # zjistim si jake formaty okraju se v jednom radku vyskytuji a kde
    for my_cell in my_row.members:
        if not my_cell.dxf.borders & bottom_or_right:
            cur_col += 1
            continue

        border_style = (my_cell.dxf.border_styles[idx], my_cell.dxf.border_colors[idx])

        if border_style[0] != EBorderStyle.NONE:
            if border_style not in border_styles_in_row:
                border_styles_in_row[border_style] = [cur_col]
            else:
                border_styles_in_row[border_style].append(cur_col)

        cur_col += 1

    # pak mista kde se vyskytuji zjednodusim (napr. pro 11 sloupcu: [1, 3, 5, 6, 7, 9, 11} -> 'odd, 6')
    for style in border_styles_in_row:
        cols_with_same_border = utils.make_ranges(border_styles_in_row[style])
        all_even, other = utils.has_even_nums_upto_n(border_styles_in_row[style], [x for x in range(1, cur_col)])
        all_odd, other2 = utils.has_odd_nums_upto_n(border_styles_in_row[style], [x for x in range(1, cur_col)])

        # vsechny stejne pro pripad ze z nejakeho duvodu by to nebylo ulozene v CRow
        if all_odd and all_even:
            border_styles_in_row[style] = '-'
            continue

        # jde jen ciste odd nebo ciste even, kombinace s nimi zde nejsou (zatim) mozne
        if all_even and not other:
            border_styles_in_row[style] = 'even'
            continue

        if all_odd and not other2:
            # col_str = 'odd'
            # if other:
            #    for k in other:
            #        col_str = f'{col_str}, {k}'

            border_styles_in_row[style] = 'odd'
            continue

        # napr. 1-5, 7-9, 11
        border_styles_in_row[style] = cols_with_same_border

    for style in border_styles_in_row:
        if style[0] != EBorderStyle.DOUBLE:
            # barva i styl
            if style[1] != EColor.NOT_SET and style[0] != EBorderStyle.THIN:
                single_hline.append(f'{{{border_styles_in_row[style]}}}'
                                    f'{{{style[0].value}, {style[1].value}}}')
            # styl
            elif style[0] != EBorderStyle.THIN:
                single_hline.append(f'{{{border_styles_in_row[style]}}}{{{style[0].value}}}')
            # barva
            else:
                single_hline.append(f'{{{border_styles_in_row[style]}}}'
                                    f'{{{style[1].value}}}')
        else:
            single_hline.append(f'{{1}}{{{border_styles_in_row[style]}}}{{{style[1].value}}}'
                              f'_CUT{{2}}{{{border_styles_in_row[style]}}}{{{style[1].value}}}')

    settings.legend._typ_okraje = len(single_hline) > 0
    return single_hline


def make_hlines_in_head(all_rows: 'list[Union[CRow, CCol]]'):
    """Funkce pro tvorbu okraju v hlavicce. Funkce najde nejake podobnosti ve vystupech z border_to_ltx. Snazi se
    zjistit zda nektere radky nemaji identicke spodni okraje. Pak z nich vytvori str vhodny pro tabularray.
    Funguje i pro sloupce.

    Returns:
        potrebujeme-li ve finale 'hline{indexy} = {cols}{fmt}, pak tato funkce tvori 'indexy' """

    ltx_hline_dict = {}
    hline_dict = {}
    max_rows = len(all_rows)
    tabs = settings.TABS

    if len(all_rows) < 1:
        return []

    if isinstance(all_rows[0], CRow):
        row_or_col = 'hline'
    else:
        row_or_col = 'vline'

    for row in all_rows:
        if row.whole_borders != EBorderStyle.NONE:
            segmented = False
        else:
            segmented = True

        # zjistim si vse o HORIZONTALNICH okrajich v kazdem radku
        row_hlines = border_to_ltx(row, segmented)

        # pak si jednotlive "typy" okraju ulozim vcetne toho v jakem radku se vyskytovaly
        for hline in row_hlines:
            if hline and hline not in hline_dict:
                hline_dict[hline] = [row.index + 1]
            elif hline != '':
                hline_dict[hline].append(row.index + 1)

    # dale hledam jestli se nekde nejaky "typ" okraje opakuje (stejne sloupce a vzhled, ale jiny radek)
    for hline in hline_dict:
        all_even, other = utils.has_even_nums_upto_n(hline_dict[hline],
                                                     [x for x in range(1, max_rows + 1) if x % 2 == 0])
        all_odd, other2 = utils.has_odd_nums_upto_n(hline_dict[hline],
                                                    [x for x in range(1, max_rows + 1) if x % 2 == 1])

        # napr. even, 1, 5, 11
        if all_even and not other and not all_odd:
            # col_str = 'even'
            # if other:
            #    for k in other:
            #        col_str = f'{col_str}, {k}'

            ltx_hline_dict[hline] = 'even'
            continue

        # napr. odd, 2, 4, 10
        if all_odd and not other2 and not all_even:
            # col_str = 'odd'
            # if other2:
            #     for k in other2:
            #         col_str = f'{col_str}, {k}'

            ltx_hline_dict[hline] = 'odd'
            continue

        if all_odd and all_even:
            ltx_hline_dict[hline] = 'all'
            continue

        # napr. 1-5, 7-9, 11
        rows_with_same_border = utils.make_ranges(hline_dict[hline])
        ltx_hline_dict[hline] = rows_with_same_border

    output_hlines = []
    for hline in ltx_hline_dict:
        if ltx_hline_dict[hline] == 'all':
            if row_or_col == 'hline':
                settings.legend._hlines = True
            else:
                settings.legend._vlines = True

            try:
                # dvojite okraje se v tabularray delaji pomoci 2 prikazu. takze musim dva vytvorit.
                cut_idx = hline.index('_CUT')
                output_hlines.append(f'{tabs}{row_or_col}s = {hline[:cut_idx]}')
                output_hlines.append(f'{tabs}{row_or_col}s = {hline[cut_idx + 4:]}')

            except ValueError:
                output_hlines.append(f'{tabs}{row_or_col}s = {hline}')
        else:
            if row_or_col == 'hline':
                settings.legend._hline = True
            else:
                settings.legend._vline = True

            try:
                # dvojite okraje se v tabularray delaji pomoci 2 prikazu. takze musim dva vytvorit.
                cut_idx = hline.index('_CUT')
                output_hlines.append(f'{tabs}{row_or_col}{{{ltx_hline_dict[hline]}}} = {hline[:cut_idx]}')
                output_hlines.append(f'{tabs}{row_or_col}{{{ltx_hline_dict[hline]}}} = {hline[cut_idx + 4:]}')

            except ValueError:
                output_hlines.append(f'{tabs}{row_or_col}{{{ltx_hline_dict[hline]}}} = {hline}')

    return output_hlines


def make_rows_in_head(all_rows: Union['list[CRow]', 'list[CCol]'], conv_state: EConvertedState, colspec: str,
                      rowspec: str):
    """Funkce pro tvorbu radku/sloupcu v hlavicce latex vystupu. Ze seznamu vsech radku a jejich spolecnych vlastnosti
    udela vystup typu {'1-3': shared_formats_1, '2, 5-8': shared_formats_2}, ktery se pak dale vytiskne. """

    ltx_row_dict = {}
    max_rows = len(all_rows)
    rowspec_exists = rowspec != ''
    colspec_exists = colspec != ''

    if max_rows < 2:
        return {'all': c_row_to_ltx(all_rows[0], 'all', rowspec_exists, colspec_exists)}

    # získání indexů shodných řádků a tvorba latex str
    for i in all_rows:
        # už se s nějakým shodoval, přeskakujeme
        if i.is_converted & conv_state:
            continue

        # zde se ukládají indexy řádků, se kterými se i-tý řadek shoduje.
        # určitě se bude shodovat sám se sebou.
        matches = [i]

        # zde dochází k porovnání i-tého řádku s řádky po něm následujících, tedy v nejhorším případě se porovnává
        # první s druhým, první se třetím, až první s posledním; druhý se třetím, druhý se čtvrtým až
        # druhý s posledním, atd. až do předposledního s posledním.
        for j in all_rows[i.index:]:
            if i == j:
                # pokud jsou shodné, přídá se do seznamu a do tohoto řádku se uloží informace o tom, že se shoduje.
                # díky tomu lze ušetřit alespoň nějaké množství iterací, protože seznam řádků, se kterými by se shodoval
                # by byl podmnožinou tohoto seznamu.
                matches.append(j)
                j.is_converted |= conv_state

        matches = sorted(matches, key=lambda x: x.index)
        mtc_indexes = sorted([j.index for j in matches])

        # vsechny radky jsou stejne, tj. mame shodu v indexech 1 az n
        if len(mtc_indexes) - 1 == max_rows - 1:
            return {'all': c_row_to_ltx(all_rows[0], 'all', rowspec_exists, colspec_exists)}

        # mame shodu jen v nejakych radcich. chceme zjistit jestli nejdou nejak po sobe, zkratka najit nejaky pattern.
        # v tento okamzik ale neni jasne, jakym zpusobem bude lepsi zjednoduseni provest. nekdy je kratsi to, jindy ono.
        # (pr. mame celkove 10 radku a radky 1 2 4 6 8 9 10 jsou shodne ->
        # vytvorit str '1-2, 4, 6, 8-10' NEBO 'even, 1, 9' ?)
        if mtc_indexes and mtc_indexes != [i.index]:
            # zjistime, jestli je to lepsi prvnim zpusobem, nebo pomoci odd a even (-> even, 1, 9)
            # kdyby tam nebyly vsechny sude az do n, posefim to kousek dale.
            maybe_simpl_idx = utils.make_ranges(mtc_indexes)
            all_even, other = utils.has_even_nums_upto_n(mtc_indexes,
                                                         [x for x in range(1, max_rows + 1) if x % 2 == 0])
            all_odd, other2 = utils.has_odd_nums_upto_n(mtc_indexes,
                                                        [x for x in range(1, max_rows + 1) if x % 2 != 0])

            num_ranges = maybe_simpl_idx.count('-')
            num_elems = maybe_simpl_idx.count(',')

            use_oddeven = False
            simpl_index = ''

            # odd, 2, 8
            if all_even:
                simpl_index = 'even'
                if other and len(other) < num_elems:
                    for k in other:
                        simpl_index = f'{simpl_index}, {matches[k].index}'
                    if len(simpl_index) < len(maybe_simpl_idx):
                        use_oddeven = True

                elif not other:
                    use_oddeven = True

            # even, 1, 7
            elif all_odd:
                simpl_index = 'odd'
                if other2 and len(other2) < num_elems:
                    for k in other2:
                        simpl_index = f'{simpl_index}, {matches[k].index}'

                    if len(simpl_index) < len(maybe_simpl_idx):
                        use_oddeven = True

                elif not other2:
                    use_oddeven = True

            # prvni varianta zabere nejmin mista (pr. n == 10: 1 2 4 8 9 10 -> 1-2, 4, 8-10)
            if not use_oddeven:
                ltx_row_dict[maybe_simpl_idx] = c_row_to_ltx(i, maybe_simpl_idx, rowspec_exists, colspec_exists)
                i.is_converted |= conv_state
            else:
                if simpl_index and simpl_index[0] == 'o':
                    ltx_row_dict[simpl_index] = c_row_to_ltx(all_rows[0], simpl_index, rowspec_exists, colspec_exists)
                    i.is_converted |= conv_state
                elif simpl_index:
                    ltx_row_dict[simpl_index] = c_row_to_ltx(all_rows[1], simpl_index, rowspec_exists, colspec_exists)
                    i.is_converted |= conv_state

    # vse, co nema nic spolecneho
    for i in all_rows:
        if not i.is_converted & conv_state:
            ltx_row_dict[f'{i.index}'] = c_row_to_ltx(i, f'{i.index}', rowspec_exists, colspec_exists)
            i.is_converted |= conv_state

    return ltx_row_dict


def make_cols_in_head(all_cols: 'list[CCol]', conv_state: EConvertedState, colspec: str, rowspec: str):
    return make_rows_in_head(all_cols, conv_state, colspec, rowspec)
