from dataclasses import dataclass
import datetime
from decimal import Decimal
import zipfile
import struct
from typing import  Tuple, Iterable
import io
import itertools

@dataclass
class FilterData:
    sheet_index: int = 0
    start_column: int = 0
    end_column: int = 0  
    start_row: int = 0
    end_row: int = 0


class XlsbWriter:
    def __init__(self, filename: str, compressionLevel:int = 4):
        """
        Initializes an XlsbWriter object to create an .xlsb file.

        Args:
            filename (str): The path to the output .xlsb file.
            compressionLevel (int): The compression level for the zip archive.
        """
        self.filename = filename
        self._worksheet_data: list[Tuple[str, Iterable[list[any]],bool]] = []
        self._shared_strings: list[str] = []
        self._shared_strings_dict: dict[str, int] = {}
        self._sheet_count = 0
        self._sst_unique_count = 0
        self._sst_all_count = 0
        self._filtered_data_list: list[FilterData] = []
        self._sheetCnt = 1
        self._compressionLevel = compressionLevel
        self._zf: zipfile.ZipFile = None

    def __enter__(self):
        """Enter the runtime context for the XlsbWriter."""
        # Initialize the zip file if it's not already created
        if self._zf is None:
            self._zf = zipfile.ZipFile(self.filename, 'w', zipfile.ZIP_DEFLATED, compresslevel=self._compressionLevel)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        Exit the runtime context for the XlsbWriter.
        
        Args:
            exc_type: Exception type
            exc_val: Exception value
            exc_tb: Exception traceback
            
        Returns:
            bool: False to propagate any exceptions, True to suppress them
        """
        try:
            if self._zf is not None:
                # Save the file content (you might want to add this logic)
                self.save()
        finally:
            # Always close the zip file
            if self._zf is not None:
                self._zf.close()
                self._zf = None
        
        return False  # Don't suppress exceptions

    def add_sheet(self, sheet_name: str, hidden : bool = False):
        self._sheet_count += 1
        # Add a placeholder for the data iterable
        self._worksheet_data.append((sheet_name, iter([]),hidden))

    def write_sheet(self, data: Iterable[list[any]]):
        """
        Adds a new worksheet to the workbook.
        A subsequent call to write_sheet() is expected to provide the data.

        Args:
            sheet_name (str): The name of the sheet.
        """
        if not self._worksheet_data:
            self.add_sheet("Sheet1")
        

        sheet_name,_, hidden = self._worksheet_data[self._sheet_count - 1]
        self._worksheet_data[self._sheet_count - 1] = (sheet_name, data, hidden)

        sheet_id = self._sheet_count
        with self._zf.open(f"xl/worksheets/sheet{sheet_id}.bin", 'w') as sheet_file:
            self._write_worksheet_bin(sheet_file, data, self._sheet_count - 1)
        
        self._zf.writestr(f"xl/worksheets/_rels/sheet{sheet_id}.bin.rels", self._create_worksheet_rels(sheet_id))
        self._zf.writestr(f"xl/worksheets/binaryIndex{sheet_id}.bin", self._binaryIndexBin)

    def save(self):
        """Save all content to the zip file."""
        if self._zf is None:
            raise RuntimeError("Zip file not initialized. Use context manager or initialize manually.")
            
        self._zf.writestr("[Content_Types].xml", self._create_content_types())
        self._zf.writestr("_rels/.rels", self._create_root_rels())
        self._zf.writestr("xl/workbook.bin", self._create_workbook_bin())
        self._zf.writestr("xl/styles.bin", self._stylesBin)
        self._zf.writestr("xl/_rels/workbook.bin.rels", self._create_workbook_rels())

        # After all worksheets are processed, the shared strings table is complete.
        # Now, write it to the zip file in a streaming fashion.
        with self._zf.open("xl/sharedStrings.bin", 'w') as sst_file:
            self._write_shared_strings_bin(sst_file)
        
    def close(self):
        """Explicitly close the writer."""
        if self._zf is not None:
            self._zf.close()
            self._zf = None

    def _create_content_types(self) -> str:
        parts = "".join(
            f'<Override PartName="/xl/worksheets/sheet{i + 1}.bin" ContentType="application/vnd.ms-excel.worksheet"/>'
            f'<Override PartName="/xl/worksheets/binaryIndex{i + 1}.bin" ContentType="application/vnd.ms-excel.binIndexWs"/>'
            for i in range(self._sheet_count)
        )

        return f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="bin" ContentType="application/vnd.ms-excel.sheet.binary.macroEnabled.main"/>
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/xl/workbook.bin" ContentType="application/vnd.ms-excel.sheet.binary.macroEnabled.main"/>
{parts}
<Override PartName="/xl/styles.bin" ContentType="application/vnd.ms-excel.styles"/>
<Override PartName="/xl/sharedStrings.bin" ContentType="application/vnd.ms-excel.sharedStrings"/>
</Types>'''

    def _create_root_rels(self) -> str:
        return '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.bin"/>
</Relationships>'''

    def _create_workbook_rels(self) -> str:
        relationships = [
            f'<Relationship Id="rId{i + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet{i + 1}.bin"/>'
            for i in range(self._sheet_count)
        ]
        
        style_rid = self._sheet_count + 1
        shared_strings_rid = self._sheet_count + 2
        relationships.append(f'<Relationship Id="rId{style_rid}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.bin"/>')
        relationships.append(f'<Relationship Id="rId{shared_strings_rid}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.bin"/>')

        return f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
{"".join(relationships)}
</Relationships>'''

    def _create_worksheet_rels(self, sheet_id) -> str:
        return f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.microsoft.com/office/2006/relationships/xlBinaryIndex" Target="binaryIndex{sheet_id}.bin"/>
</Relationships>'''

    def _create_workbook_bin(self) -> bytes:
        data = bytearray(self._workbookBinStart)
        for i, (sheet_name, _, hidden) in enumerate(self._worksheet_data):
            sheet_id = i + 1
            r_id = f"rId{sheet_id}"
            
            record_length = 4 + 3 * 4 + len(sheet_name) * 2 + len(r_id) * 2
            
            data.extend(b'\x9C\x01')
            data.extend(self._write_custom_variable_int(record_length))
            
            if hidden:
                data.extend(struct.pack('<I', 1))
            else:
                data.extend(struct.pack('<I', 0)) 

            data.extend(struct.pack('<I', sheet_id))
            data.extend(struct.pack('<I', len(r_id)))
            data.extend(r_id.encode('utf-16-le'))
            data.extend(struct.pack('<I', len(sheet_name)))
            data.extend(sheet_name.encode('utf-16-le'))

        data.extend(self._workbookBinMiddle)
        self._write_filter_defined_names(data)
        data.extend(self._workbookBinEnd)
        return bytes(data)

    def _write_custom_variable_int(self, value: int) -> bytes:
        if value < 0:
            raise ValueError("Value must be non-negative")
        
        buffer = bytearray()
        temp_val = value
        while True:
            byte = temp_val & 0x7F
            temp_val >>= 7
            if temp_val > 0:
                byte |= 0x80
                buffer.append(byte)
            else:
                buffer.append(byte)
                break
        return bytes(buffer)

    def _write_shared_strings_bin(self, sst_file: io.BufferedWriter):
        buffer = io.BytesIO()
        BUFFER_SIZE = 65536

        buffer.write(b'\x9F\x01\x08')
        buffer.write(struct.pack('<II', self._sst_all_count, self._sst_unique_count))
        
        for s in self._shared_strings:
            str_len = len(s)
            if str_len > 32767: 
                str_len = 32767
                s = s[:str_len]
            
            record_length = 1 + 4 + 2 * str_len
            
            buffer.write(b'\x13')
            buffer.write(self._write_custom_variable_int(record_length))
            buffer.write(b'\x00')
            buffer.write(struct.pack('<I', str_len))
            buffer.write(s.encode('utf-16-le'))

            if buffer.tell() > BUFFER_SIZE:
                sst_file.write(buffer.getvalue())
                buffer.seek(0)
                buffer.truncate()
            
        buffer.write(b'\xA0\x01\x00')
        
        if buffer.tell() > 0:
            sst_file.write(buffer.getvalue())

    def _write_cols_width(self, buffer: io.BytesIO, start_col: int, end_col: int, col_widths_array: list[float]):
        """
        Write column width information to the buffer.
        
        Args:
            buffer (io.BytesIO): The buffer to write data into.
            start_col (int): Starting column index (inclusive).
            end_col (int): Ending column index (exclusive).
            col_widths_array (list[float]): List of column widths in Excel units.
            
        Note:
            Each column width is written with specific binary formatting that matches
            the XLSB file format structure for column definitions.
        """
        # Write start marker for column width section
        buffer.write(b'\x86\x03')  # Magic number indicating column width data
        
        # Process each column in the specified range
        for i in range(start_col, end_col):
            # Start of column definition block
            buffer.write(b'\x00\x3C\x12')  # Fixed bytes for column definition header
            
            # Column min and max (both set to current column index)
            buffer.write(struct.pack('<I', i))  # Little-endian 32-bit unsigned int
            buffer.write(struct.pack('<I', i))  # Little-endian 32-bit unsigned int
            
            # Width information - write as single byte with pixel conversion
            buffer.write(b'\x00')  # Reserved or padding byte
            width_byte = struct.pack('<B', int(col_widths_array[i - start_col]))  # Convert to byte
            buffer.write(width_byte)
            buffer.write(b'\x00\x00')  # Additional padding
            
            # Placeholder for extended properties (usually zero-filled)
            buffer.write(b'\x00\x00\x00\x00')
            
            # Column properties: normal, visible column
            buffer.write(b'\x02')  # Normal column type

        # Write end marker for column width section
        buffer.write(b'\x00\x87\x03\x00')  # Magic number indicating end of column width data

    def _write_worksheet_bin(self, sheet_file: io.BufferedWriter, worksheet_data: Iterable[list[any]], worksheet_index:int):
        buffer = io.BytesIO()
        BUFFER_SIZE = 65536

        buffer.write(self._sheet1Bytes[:54])
        if self._sheetCnt != 1:
            buffer.write(b'\x9C')
        else:
            buffer.write(b'\xDC')
        self._sheetCnt += 1

        buffer.write(self._sheet1Bytes[55:84])

        buffer.write(self._stickHeaderA1bytes)

        # Calculate column information by reading first 100 rows for better width estimation
        data_iterator = iter(worksheet_data)
        rows_to_analyze = []
        max_cols = 1
        
        # Read up to 100 rows for analysis
        for _ in range(100):
            try:
                row = next(data_iterator)
                rows_to_analyze.append(row)
                max_cols = max(max_cols, len(row) if row else 0)
            except StopIteration:
                break  # No more rows available
            
        if max_cols == 0:
            max_cols = 1
            
        start_col = 0
        end_col = max_cols
        
        # Create column widths based on analyzed rows content
        col_widths_array = []
        for i in range(max_cols):
            max_width = 0
            for row in rows_to_analyze:
                cell = row[i]
                if i < len(row) and cell is not None:
                    if isinstance(cell, Decimal):
                        cell = float(cell)
                    if isinstance(cell, datetime.datetime):
                        temp_width = 18
                    else:
                        lenn = len(str(cell))
                        temp_width = 1.25 * lenn + 4
                    max_width = max(max_width, temp_width)
            
            if max_width == 0:
                max_width = 1.25 * 0 + 4  # Empty column
            col_widths_array.append(max_width)
        
        # Create iterator that starts with analyzed rows, then continues with the rest
        if rows_to_analyze:
            data_iterator = itertools.chain(rows_to_analyze, data_iterator)
        else:
            data_iterator = iter([])

        buffer.write(self._sheet1Bytes[84:159])
        
        # Write column widths
        self._write_cols_width(buffer, start_col, end_col, col_widths_array)
        
        buffer.write(self._sheet1Bytes[159:175])
        buffer.write(b'\x26\x00')
        buffer.write(b'\x91\x01\x00')

        shared_strings_dict = self._shared_strings_dict
        shared_strings_append = self._shared_strings.append

        row_idx = -1  
        for row_idx, row in enumerate(data_iterator):
            last_col = len(row) - 1 if row else 0
            
            buffer.write(b'\x00\x19')
            buffer.write(struct.pack('<I', row_idx))
            buffer.write(b'\x00\x00\x00\x00')
            buffer.write(struct.pack('<I', 0x12c))
            buffer.write(b'\x00\x01\x00\x00\x00')
            buffer.write(struct.pack('<II', 0, last_col))

            for col_idx, cell in enumerate(row):
                if cell is None:
                    continue
                if isinstance(cell, str):       
                    self._write_str(buffer, cell, shared_strings_dict, shared_strings_append, row_idx, col_idx)
                elif isinstance(cell, bool):
                    buffer.write(b'\x04\x09')
                    buffer.write(struct.pack('<IIB', col_idx, 0, 1 if cell else 0))
                elif isinstance(cell, int):
                    if -536870912 <= cell <= 536870911:
                        rk_val = (cell << 2) | 0b10
                        buffer.write(b'\x02\x0C')
                        buffer.write(struct.pack('<IIi', col_idx, 0, rk_val))
                    else:
                        buffer.write(b'\x05\x10')
                        buffer.write(struct.pack('<IId', col_idx, 0, float(cell)))
                elif isinstance(cell, (float, Decimal)): 
                    buffer.write(b'\x05\x10')
                    buffer.write(struct.pack('<IId', col_idx, 0, float(cell)))
                elif isinstance(cell, datetime.datetime):
                    if cell.year < 1900 or cell.year > 9999:
                        self._write_str(buffer, str(cell), shared_strings_dict, shared_strings_append, row_idx, col_idx)
                    else:
                        excel_date = (cell - datetime.datetime(1899, 12, 30, tzinfo=cell.tzinfo)).total_seconds() / 86400.0
                        buffer.write(b'\x05\x10')
                        buffer.write(struct.pack('<IId', col_idx, 1, excel_date))
                elif isinstance(cell, datetime.date):
                    if cell.year < 1900 or cell.year > 9999:
                        self._write_str(buffer, str(cell), shared_strings_dict, shared_strings_append, row_idx, col_idx)
                    else:
                        excel_date = (datetime.datetime.combine(cell, datetime.time()) - datetime.datetime(1899, 12, 30)).total_seconds() / 86400.0
                        buffer.write(b'\x05\x10')
                        buffer.write(struct.pack('<IId', col_idx, 2, excel_date))
                else:
                    self._write_str(buffer, str(cell), shared_strings_dict, shared_strings_append, row_idx, col_idx)
            
            if buffer.tell() > BUFFER_SIZE:
                sheet_file.write(buffer.getvalue())
                buffer.seek(0)
                buffer.truncate()
        
        
        buffer.write(self._sheet1Bytes[218:290])

        last_row_idx = row_idx if row_idx > -1 else 0 

        # doAutofilter
        if last_row_idx > 0:   
            buffer.write(self._autoFilterStartBytes)
            buffer.write(struct.pack('<I', 0))
        buffer.write(struct.pack('<I', last_row_idx))
        buffer.write(struct.pack('<I', 0))
        buffer.write(struct.pack('<I', 0 + end_col - 1))
        buffer.write(self._autoFilterEndBytes)
        self._filtered_data_list.append(
            FilterData(
            sheet_index=worksheet_index,
            start_column=0,
            end_column=end_col - 1,
            start_row=0,
            end_row=last_row_idx
        ))

        buffer.write(self._sheet1Bytes[290:])
        if buffer.tell() > 0:
            sheet_file.write(buffer.getvalue())

    def _write_str(self, buffer, cell, shared_strings_dict, shared_strings_append, row_idx, col_idx):
        self._sst_all_count += 1
        if cell not in shared_strings_dict:
            string_index = self._sst_unique_count
            shared_strings_dict[cell] = string_index
            shared_strings_append(cell)
            self._sst_unique_count += 1
        else:
            string_index = shared_strings_dict[cell]
        style_ref = 3 if row_idx == 0 else 0
        buffer.write(b'\x07\x0C')
        buffer.write(struct.pack('<III', col_idx, style_ref, string_index))

    def _write_filter_defined_names(self, sw: bytearray) -> None:
        filtered_dict_items_cnt = len(self._filtered_data_list) if self._filtered_data_list else 0
        
        # Temporary fix for https://github.com/KrzysztofDusko/SpreadSheetTasks/issues/2
        if (filtered_dict_items_cnt > 0 and 
            (0x80 + (filtered_dict_items_cnt - 21) * 0x0c) <= 255):
            
            sw.extend(self._magicFilterExcel2016Fix0)
            
            if filtered_dict_items_cnt <= 10:
                # !!! ? for cnt <=10
                sw.extend(bytes([0x10 + (filtered_dict_items_cnt - 1) * 0x0c, 
                               filtered_dict_items_cnt]))
                sw.extend(bytes([0x00, 0x00, 0x00]))
                
            elif filtered_dict_items_cnt <= 20:
                sw.extend(bytes([0x10 + (filtered_dict_items_cnt - 1) * 0x0c,
                               (filtered_dict_items_cnt - 1) // 10,
                               filtered_dict_items_cnt]))
                sw.extend(bytes([0x00, 0x00, 0x00]))
                
            else:  # ???
                sw.extend(bytes([0x80 + (filtered_dict_items_cnt - 21) * 0x0c,
                               (filtered_dict_items_cnt - 1) // 10,
                               filtered_dict_items_cnt]))
                sw.extend(bytes([0x00, 0x00, 0x00]))
            
            # Write filter items
            for nm in range(filtered_dict_items_cnt):
                sw.extend(bytes([0x00, 0x00, 0x00, 0x00]))
                sw.extend(bytes([self._filtered_data_list[nm].sheet_index, 0x00, 0x00, 0x00]))
                sw.extend(bytes([self._filtered_data_list[nm].sheet_index, 0x00, 0x00, 0x00]))
            
            sw.extend(bytes([0xE2, 0x02, 0x00]))
            
            # Write sheet data
            for sheet_num in range(len(self._filtered_data_list)):
                start_column = self._filtered_data_list[sheet_num].start_column
                end_column = self._filtered_data_list[sheet_num].end_column
                start_row = self._filtered_data_list[sheet_num].start_row
                end_row = self._filtered_data_list[sheet_num].end_row
                sheet_index = self._filtered_data_list[sheet_num].sheet_index
                
                # Modify magic bytes
                magic_filter_fix = self._magicFilterExcel2016Fix1.copy()
                magic_filter_fix[7] = sheet_index
                magic_filter_fix[-2] = sheet_num
                sw.extend(magic_filter_fix)


                # Write binary data using struct.pack for proper byte conversion
                sw.extend(struct.pack('<I', start_row))  # Int32 little-endian
                sw.extend(struct.pack('<I', end_row))    # Int32 little-endian
                sw.extend(struct.pack('<H', start_column))  # Int16 little-endian
                sw.extend(struct.pack('<H', end_column))    # Int16 little-endian
                
                sw.extend(self._magicFilterExcel2016Fix2)


    _autoFilterStartBytes = bytes([0xA1, 0x01, 0x10])
    _sheet1Bytes = bytes([
        0x81,0x01,0x00,0x93,0x01,0x17,0xCB,0x04,0x02,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x94,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x85,0x01,0x00,0x89,0x01,0x1E,0xDC,0x03,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x64,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x98,0x01,0x24,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x8A,0x01,0x00,0x86,0x01,0x00,0x25,0x06,0x01,0x00,0x02,0x0E,0x00,0x80,0x95,0x08,0x02,
        0x05,0x00,0x26,0x00,0xE5,0x03,0x0C,0xFF,0xFF,0xFF,0xFF,0x08,0x00,0x2C,0x01,0x00,0x00,0x00,0x00,0x91,
        0x01,0x00,0x25,0x06,0x01,0x00,0x02,0x0E,0x00,0x80,0x80,0x08,0x02,0x05,0x00,0x26,0x00,0x00,0x19,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2C,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x07,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x92,0x01,
        0x00,0x97,0x04,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0xDD,0x03,0x02,0x10,0x00,0xDC,0x03,0x30,0x66,0x66,
        0x66,0x66,0x66,0x66,0xE6,0x3F,0x66,0x66,0x66,0x66,0x66,0x66,0xE6,0x3F,0x00,0x00,0x00,0x00,0x00,0x00,
        0xE8,0x3F,0x00,0x00,0x00,0x00,0x00,0x00,0xE8,0x3F,0x33,0x33,0x33,0x33,0x33,0x33,0xD3,0x3F,0x33,0x33,
        0x33,0x33,0x33,0x33,0xD3,0x3F,0x25,0x06,0x01,0x00,0x00,0x10,0x00,0x80,0x80,0x18,0x10,0x00,0x00,0x00,
        0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x26,0x00,0x82,0x01,0x00
    ])
    _stylesBin = bytes([
        0x96,0x02,0x00,0xE7,0x04,0x04,0x02,
        0x00,0x00,0x00,0x2C,0x2C,0xA4,0x00,0x13,
        0x00,0x00,0x00,0x79,0x00,0x79,0x00,0x79,
        0x00,0x79,0x00,0x5C,0x00,0x2D,0x00,0x6D,
        0x00,0x6D,0x00,0x5C,0x00,0x2D,0x00,0x64,
        0x00,0x64,0x00,0x5C,0x00,0x20,0x00,0x68,
        0x00,0x68,0x00,0x3A,0x00,0x6D,0x00,0x6D,
        0x00,0x2C,0x1E,0xA6,0x00,0x0C,0x00,0x00,
        0x00,0x79,0x00,0x79,0x00,0x79,0x00,0x79,
        0x00,0x5C,0x00,0x2D,0x00,0x6D,0x00,0x6D,
        0x00,0x5C,0x00,0x2D,0x00,0x64,0x00,0x64,
        0x00,0xE8,0x04,0x00,0xE3,0x04,0x04,0x01,
        0x00,0x00,0x00,
        0x2B,0x27,0xDC,0x00,0x00,0x00,0x90,0x01,
        0x00,0x00,0x00,0x02,0x00,0x00,0x07,0x01,
        0x00,0x00,0x00,0x00,0x00,0xFF,0x02,0x07,
        0x00,0x00,0x00,0x43,0x00,0x61,0x00,0x6C,
        0x00,0x69,0x00,0x62,0x00,0x72,0x00,0x69,
        0x00,
        0x2B,0x27,0xDC,0x00,0x01,0x00,0xBC,0x02,
        0x00,0x00,0x00,0x02,0xEE,0x00,0x07,0x01,
        0x00,0x00,0x00,0x00,0x00,0xFF,0x02,0x07,
        0x00,0x00,0x00,0x43,0x00,0x61,0x00,0x6C,
        0x00,0x69,0x00,0x62,0x00,0x72,0x00,0x69,
        0x00,
        0x25,0x06,0x01,0x00,0x02,0x0E,0x00,0x80,0x81,0x08,0x00,0x26,0x00,0xE4,0x04,0x00,0xDB,0x04,0x04,0x02,0x00,0x00,0x00,
        0x2D,0x44,0x00,0x00,0x00,0x00,0x03,0x40,0x00,0x00,0x00,0x00,0x00,0xFF,0x03,0x41,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x2D,0x44,0x11,0x00,0x00,0x00,0x03,0x40,0x00,0x00,0x00,0x00,0x00,0xFF,0x03,0x41,0x00,0x00,0xFF,0xFF,0xFF,0xFF,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0xDC,0x04,0x00,0xE5,0x04,0x04,0x01,0x00,0x00,0x00,0x2E,0x33,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE6,0x04,0x00,0xF2,
        0x04,0x04,0x01,0x00,0x00,0x00,0x2F,0x10,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x10,0x00,
        0x00,0xF3,0x04,0x00,0xE9,0x04,0x04,
        0x04,
        0x00,0x00,0x00,
        0x2F,0x10,0x00,0x00,0x00,0x00,    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x10,0x00,0x00,
        0x2F,0x10,0x00,0x00,0xA4,0x00,    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x10,0x01,0x00,
        0x2F,0x10,0x00,0x00,0xA6,0x00,    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x10,0x01,0x00,
        0x2F,0x10,0x00,0x00,0x00,0x01,    0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x10,0x00,0x00,
        0xEA,0x04,0x00,0xEB,0x04,0x04,0x01,0x00,
        0x00,0x00,0x25,0x06,0x01,0x00,0x02,0x11,0x00,0x80,0x80,0x18,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x26,0x00,0x30,0x1C,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x4E,
        0x00,0x6F,0x00,0x72,0x00,0x6D,0x00,0x61,0x00,0x6C,0x00,0x6E,0x00,0x79,0x00,0xEC,0x04,0x00,0xF9,0x03,0x04,0x00,0x00,
        0x00,0x00,0xFA,0x03,0x00,0xFC,0x03,0x50,0x00,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x54,0x00,0x61,0x00,0x62,0x00,0x6C,
        0x00,0x65,0x00,0x53,0x00,0x74,0x00,0x79,0x00,0x6C,0x00,0x65,0x00,0x4D,0x00,0x65,0x00,0x64,0x00,0x69,0x00,0x75,0x00,
        0x6D,0x00,0x32,0x00,0x11,0x00,0x00,0x00,0x50,0x00,0x69,0x00,0x76,0x00,0x6F,0x00,0x74,0x00,0x53,0x00,0x74,0x00,0x79,
        0x00,0x6C,0x00,0x65,0x00,0x4C,0x00,0x69,0x00,0x67,0x00,0x68,0x00,0x74,0x00,0x31,0x00,0x36,0x00,0xFD,0x03,0x00,0x23,
        0x04,0x02,0x0E,0x00,0x00,0xEB,0x08,0x00,0xF6,0x08,0x2A,0x00,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x53,0x00,0x6C,0x00,
        0x69,0x00,0x63,0x00,0x65,0x00,0x72,0x00,0x53,0x00,0x74,0x00,0x79,0x00,0x6C,0x00,0x65,0x00,0x4C,0x00,0x69,0x00,0x67,
        0x00,0x68,0x00,0x74,0x00,0x31,0x00,0xF7,0x08,0x00,0xEC,0x08,0x00,0x24,0x00,0x23,0x04,0x03,0x0F,0x00,0x00,0xB0,0x10,
        0x00,0xB2,0x10,0x32,0x00,0x00,0x00,0x00,0x15,0x00,0x00,0x00,0x54,0x00,0x69,0x00,0x6D,0x00,0x65,0x00,0x53,0x00,0x6C,
        0x00,0x69,0x00,0x63,0x00,0x65,0x00,0x72,0x00,0x53,0x00,0x74,0x00,0x79,0x00,0x6C,0x00,0x65,0x00,0x4C,0x00,0x69,0x00,
        0x67,0x00,0x68,0x00,0x74,0x00,0x31,0x00,0xB3,0x10,0x00,0xB1,0x10,0x00,0x24,0x00,0x97,0x02,0x00
    ])
    _workbookBinStart = bytes([
        0x83,0x01,0x00,0x80,0x01,0x32,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x02,0x00,0x00,0x00,0x78,0x00,0x6C,0x00,0x01,0x00,0x00,0x00,0x37,0x00,0x01,0x00,0x00,0x00,
        0x36,0x00,0x05,0x00,0x00,0x00,0x32,0x00,0x34,0x00,0x33,0x00,0x32,0x00,0x36,0x00,0x99,0x01,0x0C,0x20,
        0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x25,0x06,0x01,0x00,0x03,0x0F,0x00,0x80,0x97,
        0x10,0x34,0x18,0x00,0x00,0x00,0x43,0x00,0x3A,0x00,0x5C,0x00,0x73,0x00,0x71,0x00,0x6C,0x00,0x73,0x00,
        0x5C,0x00,0x54,0x00,0x65,0x00,0x73,0x00,0x74,0x00,0x79,0x00,0x5A,0x00,0x61,0x00,0x70,0x00,0x69,0x00,
        0x73,0x00,0x75,0x00,0x58,0x00,0x6C,0x00,0x73,0x00,0x62,0x00,0x5C,0x00,0x26,0x00,0x25,0x06,0x01,0x00,
        0x00,0x10,0x00,0x80,0x81,0x18,0x82,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2F,0x00,0x00,0x00,
        0x31,0x00,0x33,0x00,0x5F,0x00,0x6E,0x00,0x63,0x00,0x72,0x00,0x3A,0x00,0x31,0x00,0x5F,0x00,0x7B,0x00,
        0x31,0x00,0x36,0x00,0x35,0x00,0x30,0x00,0x38,0x00,0x44,0x00,0x36,0x00,0x39,0x00,0x2D,0x00,0x43,0x00,
        0x46,0x00,0x38,0x00,0x37,0x00,0x2D,0x00,0x34,0x00,0x37,0x00,0x36,0x00,0x39,0x00,0x2D,0x00,0x38,0x00,
        0x34,0x00,0x35,0x00,0x36,0x00,0x2D,0x00,0x44,0x00,0x34,0x00,0x41,0x00,0x34,0x00,0x30,0x00,0x31,0x00,
        0x31,0x00,0x33,0x00,0x31,0x00,0x35,0x00,0x36,0x00,0x37,0x00,0x7D,0x00,0x2F,0x00,0x00,0x00,0x2F,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x26,0x00,
        0x87,0x01,0x00,0x25,0x06,0x01,0x00,0x02,0x10,0x00,0x80,0x80,0x18,0x10,0x00,0x00,0x00,0x00,0x0D,0x00,
        0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x26,0x00,0x9E,0x01,0x1D,0x00,0x00,0x00,0x00,0x9E,
        0x16,0x00,0x00,0xB4,0x69,0x00,0x00,0xE8,0x26,0x00,0x00,0x58,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x78,0x88,0x01,0x00,0x8F,0x01,0x00
    ])
    _workbookBinMiddle = bytes([0x90,0x01,0x00])
    _workbookBinEnd = bytes([
        0x9D,0x01,0x1A,0x35,0xEA,0x02,0x00,0x01,0x00,0x00,0x00,0x64,0x00,0x00,0x00,0xFC,0xA9,0xF1,0xD2,0x4D,
        0x62,0x50,0x3F,0x01,0x00,0x00,0x00,0x6A,0x00,0x9B,0x01,0x01,0x00,0x23,0x04,0x03,0x0F,0x00,0x00,0xAB,
        0x10,0x01,0x01,0x24,0x00,0x84,0x01,0x00
    ])
    _binaryIndexBin = bytes([
        0x2A,0x18,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x95,0x02,0x00
    ])
    _stickHeaderA1bytes =bytes([
        0x97,0x01,0x1D,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0xF0,0x3F,0x01,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x03,
    ])
    _autoFilterEndBytes = bytes([0xA2, 0x01, 0x00])
    _magicFilterExcel2016Fix0 = bytes([0xE1, 0x02, 0x00, 0xE5, 0x02, 0x00, 0xEA, 0x02])
    _magicFilterExcel2016Fix1 = bytearray([
            0x27,
            0x46,
            0x21,
            0x00,
            0x00,
            0x00,
            0x00,
            255,# -> (byte)sheetIndex,
            0x00,
            0x00,
            0x00,
            0x0F,
            0x00,
            0x00,
            0x00,
            0x5F,
            0x00,   # _0, // FilterDatabase (UTF16) - starts
            0x46,
            0x00,
            0x69,
            0x00,
            0x6C,
            0x00,
            0x74,
            0x00,
            0x65,
            0x00,
            0x72,
            0x00,
            0x44,
            0x00,
            0x61,
            0x00,
            0x74,
            0x00,
            0x61,
            0x00,
            0x62,
            0x00,
            0x61,
            0x00,
            0x73,
            0x00,
            0x65,
            0x00,# FilterDatabase (UTF16) - ends
            0x0F,
            0x00,
            0x00,
            0x00,
            0x3B,
            255,#->(byte)sheetNum,
            0x00
        ])
    _magicFilterExcel2016Fix2 = bytes([0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF])
