"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 21.10.21

"""
from django.core.files.temp import NamedTemporaryFile
from openpyxl import load_workbook


class TempExcelFile:
    """ A temporary excel sheet which will not be saved on the hard drive permanently.

    Using a template file and a template_map dictionary, this class can be used to fill in automatically
    predefined values into certain cells.

    Can be used to create excel files from data and sending it as a response like
        _file = TempExcelFile()
        response = HttpResponse(
            content=file.stream,
            content_type="application/ms-excel",
        )
        response['Content-Disposition'] = 'attachment; filename=my_file.xlsx'
        return response

    """
    stream = None
    _template_file_path = None
    _template_map = {}
    _data_obj = None

    def __init__(self, template_file_path: str = None, template_map: dict = None):
        self._template_map = template_map or {}
        self._template_file_path = template_file_path
        self._workbook = load_workbook(template_file_path)
        self._file = NamedTemporaryFile()

        self._replace_template_placeholders()

    def _replace_template_placeholders(self, start_row: int = 0):
        """ Replaces all placeholder inside the template according to _template_map

        Args:
            start_row (int): Defines where to start

        Returns:

        """
        sheets = self._workbook.worksheets
        for sheet in sheets:
            ws = sheet
            # Always activate sheet protection
            ws.protection.sheet = True
            ws.protection.enable()
            _rows = ws.iter_rows(start_row)
            for row in _rows:
                for cell in row:
                    val = cell.value
                    if val in self._template_map:
                        attr = self._template_map[val]
                        # If keyword '_iter' can be found inside the placeholder value it's an iterable and we
                        # need to process it differently
                        if isinstance(attr, dict):
                            # Read the iterable object and related attributes from the dict
                            _iter_obj = attr.get("iterable", None)
                            _attrs = attr.get("attrs", [])
                            self._add_cells_from_iterable(ws, cell, _iter_obj, _attrs)
                            # Since the sheet length did change now, we need to rerun this function starting with the new
                            # row counter
                            self._replace_template_placeholders(start_row=cell.row + len(_iter_obj))
                        else:
                            cell.value = attr
        self._workbook.save(self._file.name)
        self._file.seek(0)
        self.stream = self._file.read()

    def _add_cells_from_iterable(self, ws, start_cell, _iter_obj: iter, _attrs: list):
        """
        Adds iterable data defined by _template_map like
            ...
            "some_placeholder_iter": {
                "iterable": iterable_object,
                "attrs": [
                    "attr1",
                    "attr2",
                    "attr3",
                    ...
                ]
            },
            ...

        Args:
            ws (Workbook): The active workbook
            _iter_obj (dict): Iterable definitions from template_map

        Returns:

        """
        # Save border style
        border_style = start_cell.border.copy()
        # Drop current row, since it is just placeholder
        ws.delete_rows(start_cell.row)
        # Add enoug empty rows for the data
        ws.insert_rows(start_cell.row, len(_iter_obj))

        i = 0
        for _iter_entry in _iter_obj:
            j = 0
            for _iter_attr in _attrs:
                _new_cell = ws.cell(start_cell.row + i, start_cell.column + j, getattr(_iter_entry, _iter_attr))
                _new_cell.border = border_style
                j += 1
            i += 1