# AUTOGENERATED! DO NOT EDIT! File to edit: xlsx.ipynb (unless otherwise specified).

__all__ = ['to_xlsx', 'column_format', 'default_formats', 'column_formats', 'format_sheet']

# Internal Cell
import openpyxl
from openpyxl.styles import Alignment
from openpyxl.worksheet.table import Table, TableStyleInfo
import re
from pathlib import Path
import toolz.curried as tz
from fnmatch import fnmatch

from datetime import date
from dateutil.relativedelta import relativedelta as delta
from random import choice, randint, random
from typing import NewType, Dict, List, Iterable, Any, Optional, Union

names = (
    "Joe Nancy Harry Gus David Max Matt Nicole Tracy Stacy "
    "Kristi Kelly Mike James"
)
texts = (
    "Univeristy Franklin End_Game Tower New_York_Mets Last_Dance Movies Donald_Trump "
    "Nation_Captial COVID-19 Honesty Some_Really_Long_Text The_Lord_of_the_Rings"
)
flag = lambda: True if randint(1, 10) < 10 else False
text = lambda: choice([val.replace("_", " ") for val in texts.split()]) if flag() else None
name = lambda: choice(names.split())
dt = lambda: date.today() + delta(days=randint(-30, 30)) if flag() else None
amt = lambda: randint(150_000, 350_000) * random()

gen_data = lambda n=100: [
    {
        "Order Date": dt(),
        "Close Date": dt(),
        "Name": name(),
        "Dept": text(),
        "Balance": amt(),
        "Loan": amt(),
    }
    for row in range(n)
]

XLSXData = Iterable[Dict[Any,Any]]

XLSXSheetName = NewType("XLSXSheetName", str)
def to_xlsx(data: XLSXData, file: Path, sheet_name: XLSXSheetName,
    over_write: bool=True) -> Path:
    """Allows data to be appended to a .xlsx file as a new sheet."""
    if not over_write and file.exists():
        wb = openpyxl.load_workbook(file)
        ws = wb.create_sheet(sheet_name)
    else:
        wb = openpyxl.Workbook()
        ws = wb.active
        ws.title = sheet_name
    for row_num, row in enumerate(data):
        if row_num == 0:
            ws.append([k for k in row])
        ws.append(v for v in row.values())
    wb.save(str(file))
    wb.close()
    return file

def make_test_file() -> Path:
    """Makes a test .xlsx file used for testing."""
    file = Path.cwd() / "tests/test.xlsx"
    sheet_name = "Test 1"
    over_write = True
    data = gen_data(100)
    return to_xlsx(data, file, sheet_name, over_write)

XLSXColumn = NewType("XLSXColumn", str)
XLSXFormatAttr = NewType("XLSXFormatAttr", str)
XLSXFormat = Dict[XLSXFormatAttr,Union[str, int, Alignment]]

def column_format(number_format: str="", width: int=15, vertical: str="top",
    horizontal: str="left", wrap_text: bool=True, **kwargs) -> XLSXFormat:
    """Returns a basic format allowing for text alignment, number_format,
    and column width to be set.
    """
    return {
        "number_format": number_format,
        "alignment": Alignment(vertical=vertical, horizontal=horizontal, wrap_text=wrap_text, **kwargs),
        "width": width,
    }

def default_formats(file: Path, sheet_name: XLSXSheetName,
    rows_to_review: int=100) -> Dict[XLSXColumn, XLSXFormat]:
    """Analyzes and existing sheet and provides default text alignment,
    column width, and number_format options based upon how the .xlsx
    file has stored the data.
    """
    wb = openpyxl.load_workbook(str(file), read_only=True)
    ws = wb[sheet_name]
    rows = ws.rows
    header = next(rows)
    # 1st row contains column headers
    names = [cell.value for cell in header]
    letters = [cell.column_letter for cell in header]
    return tz.pipe(
        # Loops over rows in sheet
        rows,
        tz.take(rows_to_review),
        # For each row, create a dict usng names has keys
        tz.map(lambda row: dict(zip(names, row))),
        # Get the .xlsx data_type for each cell
        tz.map(tz.valmap(tz.flip(getattr, "data_type"))),
        # Combine cells into a list per column
        tz.merge_with(list),
        # Count the cells for each data type in the column
        tz.valmap(tz.frequencies),
        lambda row: {
            k: (
                # If any cells have a date value
                column_format("YYYY-MM-DD", horizontal="right") if "d" in v else
                # If any cells have a string value
                column_format() if "s" in v else
                # Default data type in .xslx is "n" for number
                column_format("#,##0.00", horizontal="right")
            )
            for k, v in row.items()
        },
    )

UnixWildcard = NewType("UnixWildCard", str)
Pattern = Dict[UnixWildcard, XLSXFormat]

def column_formats(file: Path, sheet_name: XLSXSheetName, patterns=None):
    """Returns a column format for each column in the sheet using either
    provided formats or default formats.

    Provided formats for the 'patterns' argument use a Unix shell-style
    wildcard as the key and the value is an XLSXFormat. The XLSXFormat is
    typically created by using the column_format function.

    Unix shell-style pattern
    ╒═══════════╤══════════════════════════════════╕
    │ Pattern   │ Meaning                          │
    ╞═══════════╪══════════════════════════════════╡
    │ *         │ matches everything               │
    ├───────────┼──────────────────────────────────┤
    │ ?         │ matches any single character     │
    ├───────────┼──────────────────────────────────┤
    │ [seq]     │ matches any character in seq     │
    ├───────────┼──────────────────────────────────┤
    │ [!seq]    │ matches any character not in seq │
    ╘═══════════╧══════════════════════════════════╛
    """
    defaults = default_formats(file, sheet_name)
    if patterns is None:
        return defaults

    results = {}
    for col in defaults:
        for pat, fmt in patterns.items():
            if fnmatch(col, pat) and col not in results:
                results[col] = fmt
        if col not in results:
            results[col] = defaults[col]
    return results

XLSXCellReference = NewType("XLSXCellReference", str)
XLSXTableName = NewType("XLSXTableName", str)

def format_sheet(file: Path, sheet_name: XLSXSheetName,
    format_patterns: Optional[Pattern]=None,
    table_name: Optional[XLSXTableName]=None,
    freeze_panes: XLSXCellReference="A2") -> Path:
    """Formats a sheet and optionally adds the sheet as a
    Worksheet table with a default table format.

    Provided formats for the 'patterns' argument use a Unix shell-style
    wildcard as the key and the value is an XLSXFormat. The XLSXFormat is
    typically created by using the column_format function.

    Unix shell-style pattern
    ╒═══════════╤══════════════════════════════════╕
    │ Pattern   │ Meaning                          │
    ╞═══════════╪══════════════════════════════════╡
    │ *         │ matches everything               │
    ├───────────┼──────────────────────────────────┤
    │ ?         │ matches any single character     │
    ├───────────┼──────────────────────────────────┤
    │ [seq]     │ matches any character in seq     │
    ├───────────┼──────────────────────────────────┤
    │ [!seq]    │ matches any character not in seq │
    ╘═══════════╧══════════════════════════════════╛
    """
    # Load file
    wb = openpyxl.load_workbook(file)
    ws = wb[sheet_name]

    # Get column names and calc formats
    rows = ws.rows
    column_names = [cell.value for cell in next(rows)]
    formats = column_formats(file, sheet_name, format_patterns)

    # Loop by column and format column
    for col in ws.iter_cols():
        header = col[0]
        name = header.value
        letter = header.column_letter

        # Set width first so that text wrap works
        ws.column_dimensions[letter].width = formats[name]["width"]

        # Add alignment and number format
        for cell in col:
            cell.alignment = formats[name]["alignment"]
            cell.number_format = formats[name]["number_format"]

    # Freeze panes
    ws.freeze_panes = ws[freeze_panes]

    # Add table
    table_name = table_name or sheet_name
    table_name = table_name.replace(" ", "_")
    table = Table(displayName=table_name, ref=ws.dimensions)
    style = TableStyleInfo(name="TableStyleLight1", showFirstColumn=False, showColumnStripes=False, showRowStripes=True)
    table.tableStyleInfo = style
    ws.add_table(table)

    # Save file
    wb.save(str(file))
    wb.close()

    return file