"""This module contain the ``ResultExporter`` class"""

import io
import re

import pandas as pd
import pandas.io.formats.excel  # pylint: disable=unused-import

from django.utils.text import Truncator

from .constants import (
    EXCEL_HEADERS_HEIGHT,
    EXCEL_MAXIMUM_COLUMN_LENGTH,
    EXCEL_STYLE_CHOICES
)


class ExcelDataExporter:
    """Used to create an excel files from a list of ``pandas.DataFrame``

    Parameters
    ----------
    path: str
        The full path to save the created excel file
        If None, the file will not be written on disk but outputted as a ``io.BytesIO``.

    Examples
    --------
    >>> data_frame = pd.DataFrame()
    >>> excel_exporter = ExcelDataExporter()
    >>> excel_exporter.add_data_sheet(data_frame)
    >>> # Add other dataFrame here...
    >>> excel_file_data = excel_exporter.export()

    See Also
    --------
    http://xlsxwriter.readthedocs.io/index.html
    http://xlsxwriter.readthedocs.io/working_with_pandas.html

    """

    def __init__(self, path=None):
        """Initialize the excel writer"""

        # Create the output handle and the excel workbook
        self.output = path or io.BytesIO()
        self.excel_writer = pd.ExcelWriter(self.output, engine='xlsxwriter')
        self.workbook = self.excel_writer.book

        # Add the available "formats" to the workbook
        self.formats = dict()
        for styles, format_name in EXCEL_STYLE_CHOICES:
            self.formats[format_name] = self.workbook.add_format(dict(styles))

        # Finally, reset the default pandas excel styles
        pd.io.formats.excel.header_style = None

    def save(self):
        """Close the workbook and save the excel file

        Returns
        -------
        str or io.BytesIO
            ``str`` The path of the created excel file
                If the exporter was initialized with the ``path`` option.
            ``io.BytesIO`` The raw data of the excel file
                If the exporter was initialized without the ``path`` option.

        """

        self.excel_writer.save()
        self.excel_writer.close()

        return self.output

    def add_sheet(self, sheet_name, data, header_styles, freeze_panes=None):
        """Add a ``Sheet`` to the current excel file

        Parameters
        ----------
        sheet_name: str
            The excel sheet name
        data: DataFrame
            A panda DataFrame containing the data to create the sheet from
        header_styles: list(EXCEL_STYLE_CHOICES.choice)
            A list of ``EXCEL_STYLE_CHOICES.choice`` to apply to the headers
        freeze_panes: list(int, int) or None
            A list of column, row indexes to "freeze"
            Or None if we don't want to "freeze" any column / row

        See Also
        --------
        http://xlsxwriter.readthedocs.io/worksheet.html#worksheet-freeze-panes

        """

        # Check that the length of the styles match the length of the data frame
        assert len(header_styles) == len(data.columns), \
            'The length of the styles must match the length of the data frame columns'

        # Remove illegal characters in the sheet name
        regex = '[' + re.escape('\/*[]:?') + ']'  # pylint: disable=anomalous-backslash-in-string
        sheet_name = re.sub(regex, '', sheet_name)

        # Excel accept sheet names up to 31 chars
        truncated_sheet_name = Truncator(sheet_name).chars(31, '…')

        # Export the data frame to an excel sheet
        data.to_excel(
            self.excel_writer,
            sheet_name=truncated_sheet_name
        )

        worksheet = self.excel_writer.sheets[truncated_sheet_name]

        if freeze_panes:
            worksheet.freeze_panes(*freeze_panes)

        # Format the first column (the indexes)
        # Need to check first if we get any records, this will fail if it's not the case
        if len(data.index):
            worksheet.set_column(
                firstcol=0,
                lastcol=0,
                width=data.index.str.len().max()  # Max length of the strings of the first column
            )

        # Set the height of the first row (the labels) following the ``EXCEL_HEADERS_HEIGHT``
        worksheet.set_row(
            row=0,
            height=EXCEL_HEADERS_HEIGHT,
            cell_format=self.formats[EXCEL_STYLE_CHOICES.HEADER.display]
        )

        # The columns are 1 based index (excluding the indexes from the data frame)
        for index, column in enumerate(data.columns.values, start=1):

            # Set the column length accordingly to the column header length (max 50 chars)
            column_length = len(column)  # Number of chars of the column header
            if column_length > EXCEL_MAXIMUM_COLUMN_LENGTH:
                column_length = EXCEL_MAXIMUM_COLUMN_LENGTH

            worksheet.set_column(
                firstcol=index,
                lastcol=index,
                width=column_length
            )

            # Get the styles for this column (0-based index)
            column_style = self.formats[header_styles[index-1].display]

            # Set the format (the style) for each column headers accordingly to the provided styles
            # We use "conditional_format" and not "format_row" / "format_column" since it will
            # format the entire column / row, which is not what we want
            worksheet.conditional_format(
                first_row=0,
                last_row=0,
                first_col=index,
                last_col=index,
                options={
                    'format': column_style,
                    'type': 'no_errors'
                }
            )



