package xls

import (
	"errors"
	"fmt"
	"math"
	"strconv"
	"time"
)

var ErrIsInt = errors.New("is int")

/* Data types */
const TYPE_STRING2 = 1
const TYPE_STRING = 2
const TYPE_FORMULA = 3
const TYPE_NUMERIC = 4
const TYPE_BOOL = 5
const TYPE_NULL = 6
const TYPE_INLINE = 7
const TYPE_ERROR = 8
const TYPE_DATETIME = 9
const TYPE_PERCENTAGE = 10
const TYPE_CURRENCY = 11

//content type
type contentHandler interface {
	Debug(wb *WorkBook)
	String(*WorkBook) []string
	FirstCol() uint16
	LastCol() uint16
}

type Col struct {
	RowB      uint16
	FirstColB uint16
}

type Coler interface {
	Row() uint16
}

func (c *Col) Debug(wb *WorkBook) {
	fmt.Printf("col dump:%#+v\n", c)
}

func (c *Col) Row() uint16 {
	return c.RowB
}

func (c *Col) FirstCol() uint16 {
	return c.FirstColB
}

func (c *Col) LastCol() uint16 {
	return c.FirstColB
}

func (c *Col) String(wb *WorkBook) []string {
	return []string{"default"}
}

type RK uint32

func (rk RK) Debug(wb *WorkBook) {
	fmt.Printf("rk dump:%#+v\n", rk)
}

func (rk RK) number() (intNum int64, floatNum float64, isFloat bool) {
	multiplied := rk & 1
	isInt := rk & 2
	val := rk >> 2
	if isInt == 0 {
		isFloat = true
		floatNum = math.Float64frombits(uint64(val) << 34)
		if multiplied != 0 {
			floatNum = floatNum / 100
		}
		return
	}
	//+++ add lines from here
	if multiplied != 0 {
		isFloat = true
		floatNum = float64(val) / 100
		return
	}
	//+++end
	return int64(val), 0, false
}

func (rk RK) float() float64 {
	var i, f, isFloat = rk.number()
	if !isFloat {
		f = float64(i)
	}

	return f
}

func (rk RK) String(wb *WorkBook) string {
	i, f, isFloat := rk.number()
	if isFloat {
		return strconv.FormatFloat(f, 'f', -1, 64)
	}

	return strconv.FormatInt(i, 10)
}

type XfRk struct {
	Index uint16
	Rk    RK
}

func (xf *XfRk) Debug(wb *WorkBook) {
	fmt.Printf("xfrk dump:%#+v\n", wb.Xfs[xf.Index])
	xf.Rk.Debug(wb)
}

func (xf *XfRk) String(wb *WorkBook) string {
	if val, ok := wb.Format(xf.Index, xf.Rk.float()); ok {
		return val
	}

	return xf.Rk.String(wb)
}

type MulrkCol struct {
	Col
	Xfrks    []XfRk
	LastColB uint16
}

func (c *MulrkCol) Debug(wb *WorkBook) {
	fmt.Printf("mulrk dump:%#+v\n", c)

	for _, v := range c.Xfrks {
		v.Debug(wb)
	}
}

func (c *MulrkCol) LastCol() uint16 {
	return c.LastColB
}

func (c *MulrkCol) String(wb *WorkBook) []string {
	var res = make([]string, len(c.Xfrks))
	for i, v := range c.Xfrks {
		res[i] = v.String(wb)
	}

	return res
}

type MulBlankCol struct {
	Col
	Xfs      []uint16
	LastColB uint16
}

func (c *MulBlankCol) Debug(wb *WorkBook) {
	fmt.Printf("mul blank dump:%#+v\n", c)
}

func (c *MulBlankCol) LastCol() uint16 {
	return c.LastColB
}

func (c *MulBlankCol) String(wb *WorkBook) []string {
	return make([]string, len(c.Xfs))
}

type NumberCol struct {
	Col
	Index uint16
	Float float64
}

func (c *NumberCol) Debug(wb *WorkBook) {
	fmt.Printf("number col dump:%#+v\n", c)
}

func (c *NumberCol) String(wb *WorkBook) []string {
	if wb.Debug {
		fmt.Printf("number col dump:%#+v\n", c)
	}

	if v, ok := wb.Format(c.Index, c.Float); ok {
		return []string{v}
	}

	return []string{strconv.FormatFloat(c.Float, 'f', -1, 64)}
}

type FormulaColHeader struct {
	Col
	IndexXf uint16
	Result  [8]byte
	Flags   uint16
	_       uint32
}

// Value formula header value
func (f *FormulaColHeader) Value() float64 {
	var rknumhigh = ByteToUint32(f.Result[4:8])
	var rknumlow = ByteToUint32(f.Result[0:4])
	var sign = (rknumhigh & 0x80000000) >> 31
	var exp = float64(((int32(rknumhigh) & 0x7ff00000) >> 20) - 1023)
	var mantissa = (0x100000 | (rknumhigh & 0x000fffff))
	var mantissalow1 = (rknumlow & 0x80000000) >> 31
	var mantissalow2 = (rknumlow & 0x7fffffff)
	var value = float64(mantissa) / math.Pow(2, 20-exp)

	if mantissalow1 != 0 {
		value += 1 / math.Pow(2, 21-exp)
	}

	value += float64(mantissalow2) / math.Pow(2, 52-exp)
	if 0 != sign {
		value *= -1
	}

	return value
}

// IsPart part of shared formula check
// WARNING:
// We can apparently not rely on $isPartOfSharedFormula. Even when $isPartOfSharedFormula = true
// the formula data may be ordinary formula data, therefore we need to check
// explicitly for the tExp token (0x01)
func (f *FormulaColHeader) IsPart() bool {
	return 0 != (0x0008 & ByteToUint16(f.Result[6:8]))
}

type FormulaCol struct {
	parsed bool
	Code   uint16
	Btl    uint16
	Btc    uint16
	Bts    []byte
	Header *FormulaColHeader
	ws     int
	vType  int
	value  string
}

func (c *FormulaCol) Debug(wb *WorkBook) {
	fmt.Printf("formula col dump:%#+v\n", c)
}

func (c *FormulaCol) Row() uint16 {
	return c.Header.Col.RowB
}

func (c *FormulaCol) FirstCol() uint16 {
	return c.Header.Col.FirstColB
}

func (c *FormulaCol) LastCol() uint16 {
	return c.Header.Col.FirstColB
}

func (c *FormulaCol) String(wb *WorkBook) []string {
	if !c.parsed {
		c.parse(wb, true)
	}

	if wb.Debug {
		fmt.Printf("formula col dump:%#+v\n", c)
	}

	return []string{c.value}
}

func (c *FormulaCol) parse(wb *WorkBook, ref bool) {
	c.parsed = true

	if 0 == c.Header.Result[0] && 255 == c.Header.Result[6] && 255 == c.Header.Result[7] {
		// String formula. Result follows in appended STRING record
		c.vType = TYPE_STRING
	} else if 1 == c.Header.Result[0] && 255 == c.Header.Result[6] && 255 == c.Header.Result[7] {
		// Boolean formula. Result is in +2; 0=false, 1=true
		c.vType = TYPE_BOOL
		if 0 == c.Header.Result[3] {
			c.value = "false"
		} else {
			c.value = "true"
		}
	} else if 2 == c.Header.Result[0] && 255 == c.Header.Result[6] && 255 == c.Header.Result[7] {
		// Error formula. Error code is in +2
		c.vType = TYPE_ERROR
		switch c.Header.Result[3] {
		case 0x00:
			c.value = "#NULL!"
		case 0x07:
			c.value = "#DIV/0"
		case 0x0F:
			c.value = "#VALUE!"
		case 0x17:
			c.value = "#REF!"
		case 0x1D:
			c.value = "#NAME?"
		case 0x24:
			c.value = "#NUM!"
		case 0x2A:
			c.value = "#N/A"
		}
	} else if 3 == c.Header.Result[0] && 255 == c.Header.Result[6] && 255 == c.Header.Result[7] {
		// Formula result is a null string
		c.vType = TYPE_NULL
		c.value = ""
	} else {
		// formula result is a number, first 14 bytes like _NUMBER record
		c.vType = TYPE_NUMERIC

		var flag bool
		if c.isGetCurTime() {
			// if date time format is not support, use time.RFC3339
			if c.value, flag = wb.Format(c.Header.IndexXf, 0); !flag {
				c.value = parseTime(0, time.RFC3339)
			}
		} else if c.isRef() {
			if ref {
				var ws = -1
				var find bool
				var rIdx uint16
				var cIdx uint16

				if 0x07 == c.Bts[0] {
					var exi = ByteToUint16(c.Bts[3:5])
					rIdx = ByteToUint16(c.Bts[5:7])
					cIdx = 0x00FF & ByteToUint16(c.Bts[7:9])
					if exi <= wb.ref.Num {
						ws = int(wb.ref.Info[int(exi)].FirstSheetIndex)
					}
				} else {
					ws = c.ws
					rIdx = ByteToUint16(c.Bts[3:5])
					cIdx = 0x00FF & ByteToUint16(c.Bts[5:7])
				}

				if ws < len(wb.sheets) {
					if row := wb.GetSheet(ws).Row(int(rIdx)); nil != row {
						find = true
						c.value = row.Col(int(cIdx))
					}
				}
				if !find {
					c.value = "#REF!"
				}
			} else {
				c.parsed = false
			}
		} else {
			c.value, flag = wb.Format(c.Header.IndexXf, c.Header.Value())
			if !flag {
				c.value = strconv.FormatFloat(c.Header.Value(), 'f', -1, 64)
			}
		}
	}
}

// isRef return cell is reference to other cell
func (c *FormulaCol) isRef() bool {
	if 0x05 == c.Bts[0] && (0x24 == c.Bts[2] || 0x44 == c.Bts[2] || 0x64 == c.Bts[2]) {
		return true
	} else if 0x07 == c.Bts[0] && (0x3A == c.Bts[2] || 0x5A == c.Bts[2] || 0x7A == c.Bts[2]) {
		return true
	}

	return false
}

// isGetCurTime return cell value is get current date or datetime flag
func (c *FormulaCol) isGetCurTime() bool {
	var ret bool
	var next byte

	if 0x19 == c.Bts[2] && (0x21 == c.Bts[6] || 0x41 == c.Bts[6] || 0x61 == c.Bts[6]) {
		next = c.Bts[7]
	} else if 0x21 == c.Bts[2] || 0x41 == c.Bts[2] || 0x61 == c.Bts[2] {
		next = c.Bts[3]
	}

	if 0x4A == next || 0xDD == next {
		ret = true
	}

	return ret
}

type RkCol struct {
	Col
	Xfrk XfRk
}

func (c *RkCol) Debug(wb *WorkBook) {
	fmt.Printf("rk col dump:%#+v\n", c)
}

func (c *RkCol) String(wb *WorkBook) []string {
	return []string{c.Xfrk.String(wb)}
}

type LabelsstCol struct {
	Col
	Xf  uint16
	Sst uint32
}

func (c *LabelsstCol) Debug(wb *WorkBook) {
	fmt.Printf("label sst col dump:%#+v\n", c)
}

func (c *LabelsstCol) String(wb *WorkBook) []string {
	if wb.Debug {
		fmt.Println("metlabel sst col dump:", c.Sst, wb.sst[int(c.Sst)])
	}

	return []string{wb.sst[int(c.Sst)]}
}

type labelCol struct {
	BlankCol
	Str string
}

func (c *labelCol) Debug(wb *WorkBook) {
	fmt.Printf("label col dump:%#+v\n", c)
}

func (c *labelCol) String(wb *WorkBook) []string {
	return []string{c.Str}
}

type BlankCol struct {
	Col
	Xf uint16
}

func (c *BlankCol) Debug(wb *WorkBook) {
	fmt.Printf("blank col dump:%#+v\n", c)
}

func (c *BlankCol) String(wb *WorkBook) []string {
	return []string{""}
}