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


import xlrd
import xlwt
import six
import datetime
from decimal import Decimal
from openpyxl.utils.datetime import to_excel

TIME_TYPES = (datetime.datetime, datetime.date, datetime.time, datetime.timedelta)
NUMERIC_TYPES = (int, float, Decimal)
STRING_TYPES = (six.string_types, six.text_type)
BOOL_TYPE = bool

def get_type(value):
    if isinstance(value, NUMERIC_TYPES):
        dt = xlrd.XL_CELL_NUMBER
    elif isinstance(value, STRING_TYPES):
        dt = xlrd.XL_CELL_TEXT
    elif isinstance(value, TIME_TYPES):
        dt = xlrd.XL_CELL_DATE
        value = to_excel(value)
    elif isinstance(value, BOOL_TYPE):
        dt = xlrd.XL_CELL_BOOLEAN
    else:
        return '', xlrd.XL_CELL_TEXT
    return value, dt



class WriterBase():

    def __init__(self, fname):
        self.load(fname)

    def load(self, fname):
        self.rdbook = rdbook = xlrd.open_workbook(fname, formatting_info=True)
        self.style_list = []
        self.font_map = {}
        for rdxf in rdbook.xf_list:
            wtxf = xlwt.Style.XFStyle()
            #
            # number format
            #
            wtxf.num_format_str = rdbook.format_map[rdxf.format_key].format_str
            #
            # font
            #
            wtf = wtxf.font
            rdf = rdbook.font_list[rdxf.font_index]
            wtf.height = rdf.height
            wtf.italic = rdf.italic
            wtf.struck_out = rdf.struck_out
            wtf.outline = rdf.outline
            wtf.shadow = rdf.outline
            wtf.colour_index = rdf.colour_index
            wtf.bold = rdf.bold  #### This attribute is redundant, should be driven by weight
            wtf._weight = rdf.weight  #### Why "private"?
            wtf.escapement = rdf.escapement
            wtf.underline = rdf.underline_type  ####
            # wtf.???? = rdf.underline #### redundant attribute, set on the fly when writing
            wtf.family = rdf.family
            wtf.charset = rdf.character_set
            wtf.name = rdf.name
            self.font_map[rdxf.font_index] = wtf
            #
            # protection
            #
            wtp = wtxf.protection
            rdp = rdxf.protection
            wtp.cell_locked = rdp.cell_locked
            wtp.formula_hidden = rdp.formula_hidden
            #
            # border(s) (rename ????)
            #
            wtb = wtxf.borders
            rdb = rdxf.border
            wtb.left = rdb.left_line_style
            wtb.right = rdb.right_line_style
            wtb.top = rdb.top_line_style
            wtb.bottom = rdb.bottom_line_style
            wtb.diag = rdb.diag_line_style
            wtb.left_colour = rdb.left_colour_index
            wtb.right_colour = rdb.right_colour_index
            wtb.top_colour = rdb.top_colour_index
            wtb.bottom_colour = rdb.bottom_colour_index
            wtb.diag_colour = rdb.diag_colour_index
            wtb.need_diag1 = rdb.diag_down
            wtb.need_diag2 = rdb.diag_up
            #
            # background / pattern (rename???)
            #
            wtpat = wtxf.pattern
            rdbg = rdxf.background
            wtpat.pattern = rdbg.fill_pattern
            wtpat.pattern_fore_colour = rdbg.pattern_colour_index
            wtpat.pattern_back_colour = rdbg.background_colour_index
            #
            # alignment
            #
            wta = wtxf.alignment
            rda = rdxf.alignment
            wta.horz = rda.hor_align
            wta.vert = rda.vert_align
            wta.dire = rda.text_direction
            # wta.orie # orientation doesn't occur in BIFF8! Superceded by rotation ("rota").
            wta.rota = rda.rotation
            wta.wrap = rda.text_wrapped
            wta.shri = rda.shrink_to_fit
            wta.inde = rda.indent_level
            # wta.merg = ????
            #
            self.style_list.append(wtxf)

    def get_sheet_mc_map(self, sheet):
        mc_map = {}
        mc_nfa = {}
        for crange in sheet.merged_cells:
            rlo, rhi, clo, chi = crange
            mc_map[(rlo, clo)] = crange
            for rowx in range(rlo, rhi):
                for colx in range(clo, chi):
                    mc_nfa[(rowx, colx)] = (rlo, clo)
        sheet.mc_top_left_map = mc_map
        sheet.mc_already_set = mc_nfa

    def create_workbook(self):

        self.wtbook = xlwt.Workbook(style_compression=2)
        self.wtbook.dates_1904 = self.rdbook.datemode
        self.wtbook.wtsheet_names = set()

        # Set the default style and the default font
        idx = self.wtbook.add_style(None)
        if idx == 0x10:
            return
        for idx, wtxf in enumerate(self.style_list):
            self.wtbook.add_style(wtxf)
            if idx == 15:
                return
        wtxf = self.style_list[0]
        for _ in range(15 - idx):
            self.wtbook.add_style(wtxf)
        return self.wtbook

    def _get_font(self, index):
        wtf = self.font_map.get(index)
        if wtf:
            return wtf
        else:
            wtf = xlwt.Font()
            rdf = self.rdbook.font_list[index]
            wtf.height = rdf.height
            wtf.italic = rdf.italic
            wtf.struck_out = rdf.struck_out
            wtf.outline = rdf.outline
            wtf.shadow = rdf.outline
            wtf.colour_index = rdf.colour_index
            wtf.bold = rdf.bold
            wtf._weight = rdf.weight
            wtf.escapement = rdf.escapement
            wtf.underline = rdf.underline_type
            wtf.family = rdf.family
            wtf.charset = rdf.character_set
            wtf.name = rdf.name
            self.font_map[index] = wtf
            return wtf

    def get_font(self, sheet, rowx, colx):
        xf = self.rdbook.xf_list[sheet.cell_xf_index(rowx, colx)]
        return self._get_font(xf.font_index)

class SheetWriter():

    def __init__(self, rdbook, wtbook, rdsheet, wtsheet_name):
        self.rdbook = rdbook
        self.wtbook = wtbook
        self.style_list = rdbook.style_list
        self.create_worksheet(rdsheet, wtsheet_name)
        self.copy_col_dimisions()

    def create_worksheet(self, rdsheet, wtsheet_name):

        # these checks should really be done by xlwt!
        if not wtsheet_name:
            raise ValueError('Empty sheet name will result in invalid Excel file!')
        l_wtsheet_name = wtsheet_name.lower()
        if l_wtsheet_name in self.wtbook.wtsheet_names:
            raise ValueError('A sheet named %r has already been added!' % l_wtsheet_name)
        self.wtbook.wtsheet_names.add(l_wtsheet_name)
        l_wtsheet_name = len(wtsheet_name)
        if len(wtsheet_name) > 31:
            raise ValueError('Sheet name cannot be more than 31 characters long, '
                             'supplied name was %i characters long!' % l_wtsheet_name)

        self.rdsheet = rdsheet
        self.wtsheet_name = wtsheet_name
        self.wtsheet = wtsheet = self.wtbook.add_sheet(wtsheet_name, cell_overwrite_ok=True)
        wtsheet.mc_ranges = {}

        # default column width: STANDARDWIDTH, DEFCOLWIDTH
        #
        if rdsheet.standardwidth is not None:
            # STANDARDWIDTH is expressed in units of 1/256 of a
            # character-width, but DEFCOLWIDTH is expressed in units of
            # character-width; we lose precision by rounding to
            # the higher whole number of characters.
            #### XXXX TODO: implement STANDARDWIDTH record in xlwt.
            wtsheet.col_default_width = \
                (rdsheet.standardwidth + 255) // 256
        elif rdsheet.defcolwidth is not None:
            wtsheet.col_default_width = rdsheet.defcolwidth
        #
        # WINDOW2
        #
        wtsheet.show_formulas = rdsheet.show_formulas
        wtsheet.show_grid = rdsheet.show_grid_lines
        wtsheet.show_headers = rdsheet.show_sheet_headers
        wtsheet.panes_frozen = rdsheet.panes_are_frozen
        wtsheet.show_zero_values = rdsheet.show_zero_values
        wtsheet.auto_colour_grid = rdsheet.automatic_grid_line_colour
        wtsheet.cols_right_to_left = rdsheet.columns_from_right_to_left
        wtsheet.show_outline = rdsheet.show_outline_symbols
        wtsheet.remove_splits = rdsheet.remove_splits_if_pane_freeze_is_removed
        wtsheet.selected = rdsheet.sheet_selected
        # xlrd doesn't read WINDOW1 records, so we have to make a best
        # guess at which the active sheet should be:
        # (at a guess, only one sheet should be marked as visible)
        """
        if not self.sheet_visible and rdsheet.sheet_visible:
            self.wtbook.active_sheet = self.wtsheet_index
            wtsheet.sheet_visible = 1
        self.wtsheet_index += 1
        """

        wtsheet.page_preview = rdsheet.show_in_page_break_preview
        wtsheet.first_visible_row = rdsheet.first_visible_rowx
        wtsheet.first_visible_col = rdsheet.first_visible_colx
        wtsheet.grid_colour = rdsheet.gridline_colour_index
        wtsheet.preview_magn = rdsheet.cooked_page_break_preview_mag_factor
        wtsheet.normal_magn = rdsheet.cooked_normal_view_mag_factor
        #
        # DEFAULTROWHEIGHT
        #
        if rdsheet.default_row_height is not None:
            wtsheet.row_default_height = rdsheet.default_row_height
        wtsheet.row_default_height_mismatch = rdsheet.default_row_height_mismatch
        wtsheet.row_default_hidden = rdsheet.default_row_hidden
        wtsheet.row_default_space_above = rdsheet.default_additional_space_above
        wtsheet.row_default_space_below = rdsheet.default_additional_space_below
        #
        # BOUNDSHEET
        #
        wtsheet.visibility = rdsheet.visibility

        #
        # PANE
        #
        if rdsheet.has_pane_record:
            wtsheet.split_position_units_are_twips = True
            wtsheet.active_pane = rdsheet.split_active_pane
            wtsheet.horz_split_pos = rdsheet.horz_split_pos
            wtsheet.horz_split_first_visible = rdsheet.horz_split_first_visible
            wtsheet.vert_split_pos = rdsheet.vert_split_pos
            wtsheet.vert_split_first_visible = rdsheet.vert_split_first_visible

        # print settings
        if hasattr(rdsheet, 'print_headers'):
            wtsheet.print_headers = rdsheet.print_headers
            wtsheet.print_grid = rdsheet.print_grid
            wtsheet.vert_page_breaks = rdsheet.vertical_page_breaks
            wtsheet.horz_page_breaks = rdsheet.horizontal_page_breaks
            wtsheet.header_str = rdsheet.header_str
            wtsheet.footer_str = rdsheet.footer_str
            wtsheet.print_centered_vert = rdsheet.print_centered_vert
            wtsheet.print_centered_horz = rdsheet.print_centered_horz
            wtsheet.left_margin = rdsheet.left_margin
            wtsheet.right_margin = rdsheet.right_margin
            wtsheet.top_margin = rdsheet.top_margin
            wtsheet.bottom_margin = rdsheet.bottom_margin
            wtsheet.paper_size_code = rdsheet.paper_size_code
            wtsheet.print_scaling = rdsheet.print_scaling
            wtsheet.start_page_number = rdsheet.start_page_number
            wtsheet.fit_width_to_pages = rdsheet.fit_width_to_pages
            wtsheet.fit_height_to_pages = rdsheet.fit_height_to_pages
            wtsheet.print_in_rows = rdsheet.print_in_rows
            wtsheet.portrait = rdsheet.portrait
            wtsheet.print_colour = not rdsheet.print_not_colour
            wtsheet.print_draft = rdsheet.print_draft
            wtsheet.print_notes = rdsheet.print_notes
            wtsheet.print_notes_at_end = rdsheet.print_notes_at_end
            wtsheet.print_omit_errors = rdsheet.print_omit_errors
            wtsheet.print_hres = rdsheet.print_hres
            wtsheet.print_vres = rdsheet.print_vres
            wtsheet.header_margin = rdsheet.header_margin
            wtsheet.footer_margin = rdsheet.footer_margin
            wtsheet.copies_num = rdsheet.copies_num

    def row(self, rdrowx, wtrowx):
        """
        This should be called every time processing of a new
        row in the current sheet starts.

        :param rdrowx: the index of the row in the current sheet from which
                 information for the row to be written will be
                 copied.

        :param wtrowx: the index of the row in sheet to be written to which
                 information will be written for the row being read.
        """
        wtrow = self.wtsheet.row(wtrowx)
        # empty rows may not have a rowinfo record
        rdrow = self.rdsheet.rowinfo_map.get(rdrowx)
        if rdrow:
            wtrow.height = rdrow.height
            wtrow.has_default_height = rdrow.has_default_height
            wtrow.height_mismatch = rdrow.height_mismatch
            wtrow.level = rdrow.outline_level
            wtrow.collapse = rdrow.outline_group_starts_ends  # No kiddin'
            wtrow.hidden = rdrow.hidden
            wtrow.space_above = rdrow.additional_space_above
            wtrow.space_below = rdrow.additional_space_below
            if rdrow.has_default_xf_index:
                wtrow.set_style(self.style_list[rdrow.xf_index])

    def copy_col_dimisions(self ):
        for colx,rdcol in self.rdsheet.colinfo_map.items():
            #rdcol = self.rdsheet.colinfo_map[colx]
            wtcol = self.wtsheet.col(colx)
            wtcol.width = rdcol.width
            wtcol.set_style(self.style_list[rdcol.xf_index])
            wtcol.hidden = rdcol.hidden
            wtcol.level = rdcol.outline_level
            wtcol.collapsed = rdcol.collapsed

    def set_mc_ranges(self):
        for key, crange in self.wtsheet.mc_ranges.items():
            self.wtsheet.merged_ranges.append(crange)
        #for key, crange in self.wtsheet.mc_ranges_already_set.items():
        #    self.wtsheet.merged_ranges.append(crange)


    def cell(self, rdrowx, rdcolx, wtrowx, wtcolx, value, cty):
        cell = self.rdsheet.cell(rdrowx, rdcolx)
        if cty is None:
            value, cty = get_type(value)

        if cty == xlrd.XL_CELL_EMPTY:
            return
        if cell.xf_index is not None:
            style = self.style_list[cell.xf_index]
        else:
            style = self.style_list[0]
        rdcoords2d = (rdrowx, rdcolx)
        if rdcoords2d in self.rdsheet.mc_top_left_map:
            if self.wtsheet.mc_ranges.get(rdcoords2d):
                rlo, rhi, clo, chi = self.wtsheet.mc_ranges.get(rdcoords2d)
                self.wtsheet.merged_ranges.append((rlo, rhi, clo, chi))
                #self.wtsheet.mc_ranges_already_set[(rlo, clo)] = (rlo, rhi, clo, chi)
            self.wtsheet.mc_ranges[rdcoords2d] = (wtrowx, wtrowx, wtcolx, wtcolx)
        else:
            mc_top_left = self.rdsheet.mc_already_set.get(rdcoords2d)
            if mc_top_left:
                rlo, rhi, clo, chi = self.wtsheet.mc_ranges.get(mc_top_left)
                self.wtsheet.mc_ranges[mc_top_left] = (rlo, max(rhi, wtrowx), clo, max(chi, wtcolx))

        wtrow = self.wtsheet.row(wtrowx)
        if cty == xlrd.XL_CELL_TEXT:
            if isinstance(value, (list, tuple)):
                wtrow.set_cell_rich_text(wtcolx, value, style)
            else:
                wtrow.set_cell_text(wtcolx, value, style)
        elif cty == xlrd.XL_CELL_NUMBER or cty == xlrd.XL_CELL_DATE:
            wtrow.set_cell_number(wtcolx, value, style)
        elif cty == xlrd.XL_CELL_BLANK:
            wtrow.set_cell_blank(wtcolx, style)
        elif cty == xlrd.XL_CELL_BOOLEAN:
            wtrow.set_cell_boolean(wtcolx, value, style)
        elif cty == xlrd.XL_CELL_ERROR:
            wtrow.set_cell_error(wtcolx, value, style)
        else:
            raise Exception(
                "Unknown xlrd cell type %r with value %r at (sheet=%r,rowx=%r,colx=%r)" \
                % (cty, value, self.rdsheet.name, rdrowx, rdcolx)
            )



