# -*- coding: utf-8 -*-

import datetime as dt
from decimal import Decimal

import six

from . import BIFFRecords, ExcelFormula, Style
from .Cell import (
    BlankCell, BooleanCell, ErrorCell, FormulaCell, MulBlankCell, NumberCell,
    StrCell, _get_cells_biff_data_mul,
)
from .Formatting import Font


class Row(object):
    __slots__ = [# private variables
                 "__idx",
                 "__parent",
                 "__parent_wb",
                 "__cells",
                 "__min_col_idx",
                 "__max_col_idx",
                 "__xf_index",
                 "__has_default_xf_index",
                 "__height_in_pixels",
                 # public variables
                 "height",
                 "has_default_height",
                 "height_mismatch",
                 "level",
                 "collapse",
                 "hidden",
                 "space_above",
                 "space_below"]

    def __init__(self, rowx, parent_sheet):
        if not (isinstance(rowx, six.integer_types) and 0 <= rowx <= 65535):
            raise ValueError("row index was %r, not allowed by .xls format" % rowx)
        self.__idx = rowx
        self.__parent = parent_sheet
        self.__parent_wb = parent_sheet.get_parent()
        self.__cells = {}
        self.__min_col_idx = 0
        self.__max_col_idx = 0
        self.__xf_index = 0x0F
        self.__has_default_xf_index = 0
        self.__height_in_pixels = 0x11

        self.height = 0x00FF
        self.has_default_height = 0x00
        self.height_mismatch = 0
        self.level = 0
        self.collapse = 0
        self.hidden = 0
        self.space_above = 0
        self.space_below = 0


    def __adjust_height(self, style):
        twips = style.font.height
        points = float(twips)/20.0
        # Cell height in pixels can be calcuted by following approx. formula:
        # cell height in pixels = font height in points * 83/50 + 2/5
        # It works when screen resolution is 96 dpi
        pix = int(round(points*83.0/50.0 + 2.0/5.0))
        if pix > self.__height_in_pixels:
            self.__height_in_pixels = pix


    def __adjust_bound_col_idx(self, *args):
        for arg in args:
            iarg = int(arg)
            if not ((0 <= iarg <= 255) and arg == iarg):
                raise ValueError("column index (%r) not an int in range(256)" % arg)
            sheet = self.__parent
            if iarg < self.__min_col_idx:
                self.__min_col_idx = iarg
            if iarg > self.__max_col_idx:
                self.__max_col_idx = iarg
            if iarg < sheet.first_used_col:
                sheet.first_used_col = iarg
            if iarg > sheet.last_used_col:
                sheet.last_used_col = iarg

    def __excel_date_dt(self, date):
        adj = False
        if isinstance(date, dt.date):
            if self.__parent_wb.dates_1904:
                epoch_tuple = (1904, 1, 1)
            else:
                epoch_tuple = (1899, 12, 31)
                adj = True
            if isinstance(date, dt.datetime):
                epoch = dt.datetime(*epoch_tuple)
            else:
                epoch = dt.date(*epoch_tuple)
        else: # it's a datetime.time instance
            date = dt.datetime.combine(dt.datetime(1900, 1, 1), date)
            epoch = dt.datetime(1900, 1, 1)
        delta = date - epoch
        xldate = delta.days + delta.seconds / 86400.0
        # Add a day for Excel's missing leap day in 1900
        if adj and xldate > 59:
            xldate += 1
        return xldate

    def get_height_in_pixels(self):
        return self.__height_in_pixels


    def set_style(self, style):
        self.__adjust_height(style)
        self.__xf_index = self.__parent_wb.add_style(style)
        self.__has_default_xf_index = 1


    def get_xf_index(self):
        return self.__xf_index


    def get_cells_count(self):
        return len(self.__cells)


    def get_min_col(self):
        return self.__min_col_idx


    def get_max_col(self):
        return self.__max_col_idx


    def get_row_biff_data(self):
        height_options = (self.height & 0x07FFF)
        height_options |= (self.has_default_height & 0x01) << 15

        options =  (self.level & 0x07) << 0
        options |= (self.collapse & 0x01) << 4
        options |= (self.hidden & 0x01) << 5
        options |= (self.height_mismatch & 0x01) << 6
        options |= (self.__has_default_xf_index & 0x01) << 7
        options |= (0x01 & 0x01) << 8
        options |= (self.__xf_index & 0x0FFF) << 16
        options |= (self.space_above & 1) << 28
        options |= (self.space_below & 1) << 29

        return BIFFRecords.RowRecord(self.__idx, self.__min_col_idx,
            self.__max_col_idx, height_options, options).get()

    def insert_cell(self, col_index, cell_obj):
        if col_index in self.__cells:
            if not self.__parent._cell_overwrite_ok:
                msg = "Attempt to overwrite cell: sheetname=%r rowx=%d colx=%d" \
                    % (self.__parent.name, self.__idx, col_index)
                raise Exception(msg)
            prev_cell_obj = self.__cells[col_index]
            sst_idx = getattr(prev_cell_obj, 'sst_idx', None)
            if sst_idx is not None:
                self.__parent_wb.del_str(sst_idx)
        self.__cells[col_index] = cell_obj

    def insert_mulcells(self, colx1, colx2, cell_obj):
        self.insert_cell(colx1, cell_obj)
        for col_index in range(colx1+1, colx2+1):
            self.insert_cell(col_index, None)

    def get_cells_biff_data(self):
        cell_items = [item for item in self.__cells.items() if item[1] is not None]
        cell_items.sort() # in column order
        return _get_cells_biff_data_mul(self.__idx, cell_items)
        # previously:
        # return ''.join([cell.get_biff_data() for colx, cell in cell_items])

    def get_index(self):
        return self.__idx

    def set_cell_text(self, colx, value, style=Style.default_style):
        self.__adjust_height(style)
        self.__adjust_bound_col_idx(colx)
        xf_index = self.__parent_wb.add_style(style)
        self.insert_cell(colx, StrCell(self.__idx, colx, xf_index, self.__parent_wb.add_str(value)))

    def set_cell_blank(self, colx, style=Style.default_style):
        self.__adjust_height(style)
        self.__adjust_bound_col_idx(colx)
        xf_index = self.__parent_wb.add_style(style)
        self.insert_cell(colx, BlankCell(self.__idx, colx, xf_index))

    def set_cell_mulblanks(self, first_colx, last_colx, style=Style.default_style):
        assert 0 <= first_colx <= last_colx <= 255
        self.__adjust_height(style)
        self.__adjust_bound_col_idx(first_colx, last_colx)
        xf_index = self.__parent_wb.add_style(style)
        # ncols = last_colx - first_colx + 1
        self.insert_mulcells(first_colx, last_colx, MulBlankCell(self.__idx, first_colx, last_colx, xf_index))

    def set_cell_number(self, colx, number, style=Style.default_style):
        self.__adjust_height(style)
        self.__adjust_bound_col_idx(colx)
        xf_index = self.__parent_wb.add_style(style)
        self.insert_cell(colx, NumberCell(self.__idx, colx, xf_index, number))

    def set_cell_date(self, colx, datetime_obj, style=Style.default_style):
        self.__adjust_height(style)
        self.__adjust_bound_col_idx(colx)
        xf_index = self.__parent_wb.add_style(style)
        self.insert_cell(colx,
            NumberCell(self.__idx, colx, xf_index, self.__excel_date_dt(datetime_obj)))

    def set_cell_formula(self, colx, formula, style=Style.default_style, calc_flags=0):
        self.__adjust_height(style)
        self.__adjust_bound_col_idx(colx)
        xf_index = self.__parent_wb.add_style(style)
        self.__parent_wb.add_sheet_reference(formula)
        self.insert_cell(colx, FormulaCell(self.__idx, colx, xf_index, formula, calc_flags=0))

    def set_cell_boolean(self, colx, value, style=Style.default_style):
        self.__adjust_height(style)
        self.__adjust_bound_col_idx(colx)
        xf_index = self.__parent_wb.add_style(style)
        self.insert_cell(colx, BooleanCell(self.__idx, colx, xf_index, bool(value)))

    def set_cell_error(self, colx, error_string_or_code, style=Style.default_style):
        self.__adjust_height(style)
        self.__adjust_bound_col_idx(colx)
        xf_index = self.__parent_wb.add_style(style)
        self.insert_cell(colx, ErrorCell(self.__idx, colx, xf_index, error_string_or_code))

    def write(self, col, label, style=Style.default_style):
        self.__adjust_height(style)
        self.__adjust_bound_col_idx(col)
        style_index = self.__parent_wb.add_style(style)
        if isinstance(label, six.string_types):
            if len(label) > 0:
                self.insert_cell(col,
                    StrCell(self.__idx, col, style_index, self.__parent_wb.add_str(label))
                    )
            else:
                self.insert_cell(col, BlankCell(self.__idx, col, style_index))
        elif isinstance(label, bool): # bool is subclass of int; test bool first
            self.insert_cell(col, BooleanCell(self.__idx, col, style_index, label))
        elif isinstance(label, six.integer_types+(float, Decimal)):
            self.insert_cell(col, NumberCell(self.__idx, col, style_index, label))
        elif isinstance(label, (dt.datetime, dt.date, dt.time)):
            date_number = self.__excel_date_dt(label)
            self.insert_cell(col, NumberCell(self.__idx, col, style_index, date_number))
        elif label is None:
            self.insert_cell(col, BlankCell(self.__idx, col, style_index))
        elif isinstance(label, ExcelFormula.Formula):
            self.__parent_wb.add_sheet_reference(label)
            self.insert_cell(col, FormulaCell(self.__idx, col, style_index, label))
        elif isinstance(label, (list, tuple)):
            self.__rich_text_helper(col, label, style, style_index)
        else:
            raise Exception("Unexpected data type %r" % type(label))

    def set_cell_rich_text(self, col, rich_text_list, style=Style.default_style):
        self.__adjust_height(style)
        self.__adjust_bound_col_idx(col)
        if not isinstance(rich_text_list, (list, tuple)):
            raise Exception("Unexpected data type %r" % type(rich_text_list))
        self.__rich_text_helper(col, rich_text_list, style)

    def __rich_text_helper(self, col, rich_text_list, style, style_index=None):
        if style_index is None:
            style_index = self.__parent_wb.add_style(style)
        default_font = None
        rt = []
        for data in rich_text_list:
            if isinstance(data, six.string_types):
                s = data
                font = default_font
            elif isinstance(data, (list, tuple)):
                if not isinstance(data[0], six.string_types) or not isinstance(data[1], Font):
                    raise Exception ("Unexpected data type %r, %r" % (type(data[0]), type(data[1])))
                s = data[0]
                font = self.__parent_wb.add_font(data[1])
            else:
                raise Exception ("Unexpected data type %r" % type(data))
            if s:
                rt.append((s, font))
                if default_font is None:
                    default_font = self.__parent_wb.add_font(style.font)
        if rt:
            self.insert_cell(col, StrCell(self.__idx, col, style_index, self.__parent_wb.add_rt(rt)))
        else:
            self.insert_cell(col, BlankCell(self.__idx, col, style_index))

    write_blanks = set_cell_mulblanks
    write_rich_text = set_cell_rich_text
