from struct import unpack, pack
from . import BIFFRecords

class StrCell(object):
    __slots__ = ["rowx", "colx", "xf_idx", "sst_idx"]

    def __init__(self, rowx, colx, xf_idx, sst_idx):
        self.rowx = rowx
        self.colx = colx
        self.xf_idx = xf_idx
        self.sst_idx = sst_idx

    def get_biff_data(self):
        return pack('<5HL', 0x00FD, 10, self.rowx, self.colx, self.xf_idx, self.sst_idx)

class BlankCell(object):
    __slots__ = ["rowx", "colx", "xf_idx"]

    def __init__(self, rowx, colx, xf_idx):
        self.rowx = rowx
        self.colx = colx
        self.xf_idx = xf_idx

    def get_biff_data(self):
        return pack('<5H', 0x0201, 6, self.rowx, self.colx, self.xf_idx)

class MulBlankCell(object):
    __slots__ = ["rowx", "colx1", "colx2", "xf_idx"]

    def __init__(self, rowx, colx1, colx2, xf_idx):
        self.rowx = rowx
        self.colx1 = colx1
        self.colx2 = colx2
        self.xf_idx = xf_idx

    def get_biff_data(self):
        return BIFFRecords.MulBlankRecord(self.rowx,
            self.colx1, self.colx2, self.xf_idx).get()

class NumberCell(object):
    __slots__ = ["rowx", "colx", "xf_idx", "number"]

    def __init__(self, rowx, colx, xf_idx, number):
        self.rowx = rowx
        self.colx = colx
        self.xf_idx = xf_idx
        self.number = float(number)

    def get_encoded_data(self):
        rk_encoded = 0
        num = self.number

        # The four possible kinds of RK encoding are *not* mutually exclusive.
        # The 30-bit integer variety picks up the most.
        # In the code below, the four varieties are checked in descending order
        # of bangs per buck, or not at all.
        # SJM 2007-10-01

        if -0x20000000 <= num < 0x20000000: # fits in 30-bit *signed* int
            inum = int(num)
            if inum == num: # survives round-trip
                rk_encoded = 2 | (inum << 2)
                return 1, rk_encoded

        temp = num * 100

        if -0x20000000 <= temp < 0x20000000:
            # That was step 1: the coded value will fit in
            # a 30-bit signed integer.
            itemp = int(round(temp, 0))
            # That was step 2: "itemp" is the best candidate coded value.
            # Now for step 3: simulate the decoding,
            # to check for round-trip correctness.
            if itemp / 100.0 == num:
                rk_encoded = 3 | (itemp << 2)
                return 1, rk_encoded

        if 0: # Cost of extra pack+unpack not justified by tiny yield.
            packed = pack('<d', num)
            w01, w23 = unpack('<2i', packed)
            if not w01 and not(w23 & 3):
                return 1, w23

            packed100 = pack('<d', temp)
            w01, w23 = unpack('<2i', packed100)
            if not w01 and not(w23 & 3):
                return 1, w23 | 1

        return 0, pack('<5Hd', 0x0203, 14, self.rowx, self.colx, self.xf_idx, num)

    def get_biff_data(self):
        isRK, value = self.get_encoded_data()
        if isRK:
            return pack('<5Hi', 0x27E, 10, self.rowx, self.colx, self.xf_idx, value)
        return value # NUMBER record already packed

class BooleanCell(object):
    __slots__ = ["rowx", "colx", "xf_idx", "number"]

    def __init__(self, rowx, colx, xf_idx, number):
        self.rowx = rowx
        self.colx = colx
        self.xf_idx = xf_idx
        self.number = number

    def get_biff_data(self):
        return BIFFRecords.BoolErrRecord(self.rowx,
            self.colx, self.xf_idx, self.number, 0).get()

error_code_map = {
    0x00:  0, # Intersection of two cell ranges is empty
    0x07:  7, # Division by zero
    0x0F: 15, # Wrong type of operand
    0x17: 23, # Illegal or deleted cell reference
    0x1D: 29, # Wrong function or range name
    0x24: 36, # Value range overflow
    0x2A: 42, # Argument or function not available
    '#NULL!' :  0, # Intersection of two cell ranges is empty
    '#DIV/0!':  7, # Division by zero
    '#VALUE!': 36, # Wrong type of operand
    '#REF!'  : 23, # Illegal or deleted cell reference
    '#NAME?' : 29, # Wrong function or range name
    '#NUM!'  : 36, # Value range overflow
    '#N/A!'  : 42, # Argument or function not available
}

class ErrorCell(object):
    __slots__ = ["rowx", "colx", "xf_idx", "number"]

    def __init__(self, rowx, colx, xf_idx, error_string_or_code):
        self.rowx = rowx
        self.colx = colx
        self.xf_idx = xf_idx
        try:
            self.number = error_code_map[error_string_or_code]
        except KeyError:
            raise Exception('Illegal error value (%r)' % error_string_or_code)

    def get_biff_data(self):
        return BIFFRecords.BoolErrRecord(self.rowx,
            self.colx, self.xf_idx, self.number, 1).get()

class FormulaCell(object):
    __slots__ = ["rowx", "colx", "xf_idx", "frmla", "calc_flags"]

    def __init__(self, rowx, colx, xf_idx, frmla, calc_flags=0):
        self.rowx = rowx
        self.colx = colx
        self.xf_idx = xf_idx
        self.frmla = frmla
        self.calc_flags = calc_flags

    def get_biff_data(self):
        return BIFFRecords.FormulaRecord(self.rowx,
            self.colx, self.xf_idx, self.frmla.rpn(), self.calc_flags).get()

# module-level function for *internal* use by the Row module

def _get_cells_biff_data_mul(rowx, cell_items):
    # Return the BIFF data for all cell records in the row.
    # Adjacent BLANK|RK records are combined into MUL(BLANK|RK) records.
    pieces = []
    nitems = len(cell_items)
    i = 0
    while i < nitems:
        icolx, icell = cell_items[i]
        if isinstance(icell, NumberCell):
            isRK, value = icell.get_encoded_data()
            if not isRK:
                pieces.append(value) # pre-packed NUMBER record
                i += 1
                continue
            muldata = [(value, icell.xf_idx)]
            target = NumberCell
        elif isinstance(icell, BlankCell):
            muldata = [icell.xf_idx]
            target = BlankCell
        else:
            pieces.append(icell.get_biff_data())
            i += 1
            continue
        lastcolx = icolx
        j = i

        packed_record = b'' # (to_py3): 'b' binary data
        for j in range(i+1, nitems):
            jcolx, jcell = cell_items[j]
            if jcolx != lastcolx + 1:
                nexti = j
                break
            if not isinstance(jcell, target):
                nexti = j
                break
            if target == NumberCell:
                isRK, value = jcell.get_encoded_data()
                if not isRK:
                    packed_record = value
                    nexti = j + 1
                    break
                muldata.append((value, jcell.xf_idx))
            else:
                muldata.append(jcell.xf_idx)
            lastcolx = jcolx
        else:
            nexti = j + 1
        if target == NumberCell:
            if lastcolx == icolx:
                # RK record
                value, xf_idx = muldata[0]
                pieces.append(pack('<5Hi', 0x027E, 10, rowx, icolx, xf_idx, value))
            else:
                # MULRK record
                nc = lastcolx - icolx + 1
                pieces.append(pack('<4H', 0x00BD, 6 * nc + 6, rowx, icolx))
                # (to_py3): 'b' binary data
                pieces.append(b''.join([pack('<Hi', xf_idx, value) for value, xf_idx in muldata]))
                pieces.append(pack('<H', lastcolx))
        else:
            if lastcolx == icolx:
                # BLANK record
                xf_idx = muldata[0]
                pieces.append(pack('<5H', 0x0201, 6, rowx, icolx, xf_idx))
            else:
                # MULBLANK record
                nc = lastcolx - icolx + 1
                pieces.append(pack('<4H', 0x00BE, 2 * nc + 6, rowx, icolx))
                # (to_py3): 'b' binary data
                pieces.append(b''.join([pack('<H', xf_idx) for xf_idx in muldata]))
                pieces.append(pack('<H', lastcolx))
        if packed_record:
            pieces.append(packed_record)
        i = nexti
    return b''.join(pieces) # (to_py3): 'b' binary data
