package xlsx

import (
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"strconv"
	"strings"
	"unicode/utf8"
)

// Sheet is a high level structure intended to provide user access to
// the contents of a particular sheet within an XLSX file.
type Sheet struct {
	Name            string
	File            *File
	Cols            *ColStore
	MaxRow          int
	MaxCol          int
	Hidden          bool
	Selected        bool
	SheetViews      []SheetView
	SheetFormat     SheetFormat
	AutoFilter      *AutoFilter
	Relations       []Relation
	DataValidations []*xlsxDataValidation
	cellStore       CellStore
	currentRow      *Row
	cellStoreName   string // The first part of the key used in
	// the cellStore.  This name is stable,
	// unlike the Name, which can change
}

// NewSheet constructs a Sheet with the default CellStore and returns
// a pointer to it.
func NewSheet(name string) (*Sheet, error) {
	return NewSheetWithCellStore(name, NewMemoryCellStore)
}

// NewSheetWithCellStore constructs a Sheet, backed by a CellStore,
// for which you must provide the constructor function.
func NewSheetWithCellStore(name string, constructor CellStoreConstructor) (*Sheet, error) {
	if err := IsSaneSheetName(name); err != nil {
		return nil, fmt.Errorf("sheet name is invalid: %w", err)
	}
	sheet := &Sheet{
		Name:          name,
		Cols:          &ColStore{},
		cellStoreName: name,
	}
	var err error
	sheet.cellStore, err = constructor()
	if err != nil {
		return nil, fmt.Errorf("NewSheetWithCellStore: %w", err)
	}
	return sheet, err

}

// Remove Sheet's dependant resources - if you are done with operations on a sheet this should be called to clear down the Sheet's persistent cache.  Note: if you call this, all further read operaton on the sheet will fail - including any attempt to save the file, or dump it's contents to a byte stream.  Therefore only call this *after* you've saved your changes, of when you're done reading a sheet in a file you don't plan to persist.
func (s *Sheet) Close() {
	s.cellStore.Close()
	s.cellStore = nil
}

func (s *Sheet) getState() string {
	if s.Hidden {
		return "hidden"
	}
	return "visible"
}

type SheetView struct {
	Pane *Pane
}

type Pane struct {
	XSplit      float64
	YSplit      float64
	TopLeftCell string
	ActivePane  string
	State       string // Either "split" or "frozen"
}

type SheetFormat struct {
	DefaultColWidth  float64
	DefaultRowHeight float64
	OutlineLevelCol  uint8
	OutlineLevelRow  uint8
}

type AutoFilter struct {
	TopLeftCell     string
	BottomRightCell string
}

type Relation struct {
	Type       RelationshipType
	Target     string
	TargetMode RelationshipTargetMode
}

func (s *Sheet) makeXLSXSheetRelations() *xlsxWorksheetRels {
	relSheet := xlsxWorksheetRels{XMLName: xml.Name{Local: "Relationships"}, Relationships: []xlsxWorksheetRelation{}}
	for id, rel := range s.Relations {
		xRel := xlsxWorksheetRelation{Id: "rId" + strconv.Itoa(id+1), Type: rel.Type, Target: rel.Target, TargetMode: rel.TargetMode}
		relSheet.Relationships = append(relSheet.Relationships, xRel)
	}
	if len(relSheet.Relationships) == 0 {
		return nil
	}
	return &relSheet
}

func (s *Sheet) addRelation(relType RelationshipType, target string, targetMode RelationshipTargetMode) {
	newRel := Relation{Type: relType, Target: target, TargetMode: targetMode}
	for _, rel := range s.Relations {
		if rel == newRel {
			return
		}
	}
	s.Relations = append(s.Relations, newRel)
}

func (s *Sheet) setCurrentRow(r *Row) {
	if r != nil && r == s.currentRow {
		return
	}
	if s.currentRow != nil && s.currentRow.isCustom {
		err := s.cellStore.WriteRow(s.currentRow)
		if err != nil {
			panic(err)
		}
	}
	s.currentRow = r
}

// rowVisitorFlags contains flags that can be set by a RowVisitorOption to affect the behaviour of sheet.ForEachRow
type rowVisitorFlags struct {
	skipEmptyRows bool
}

// RowVisitorOption defines the call signature of functions that can be passed as options to the Sheet.ForEachRow function to affect its behaviour.
type RowVisitorOption func(flags *rowVisitorFlags)

// SkipEmptyRows can be passed to the Sheet.ForEachRow function to
// cause it to skip over empty Rows.
func SkipEmptyRows(flags *rowVisitorFlags) {
	flags.skipEmptyRows = true
}

// A RowVisitor function should be provided by the user when calling
// Sheet.ForEachRow, it will be called once for every Row visited.
type RowVisitor func(r *Row) error

func (s *Sheet) mustBeOpen() {
	if s.cellStore == nil {
		panic("Attempt to iterate over sheet with no cellstore. Perhaps you called Close() on this sheet?")
	}
}

func (s *Sheet) ForEachRow(rv RowVisitor, options ...RowVisitorOption) error {
	s.mustBeOpen()
	flags := &rowVisitorFlags{}
	for _, opt := range options {
		opt(flags)
	}
	if s.currentRow != nil {
		err := s.cellStore.WriteRow(s.currentRow)
		if err != nil {
			return err
		}
	}
	for i := 0; i < s.MaxRow; i++ {
		r, err := s.cellStore.ReadRow(makeRowKey(s, i), s)
		if err != nil {
			if _, ok := err.(*RowNotFoundError); !ok {
				return err

			}
			if flags.skipEmptyRows {
				continue
			}
			r = s.cellStore.MakeRow(s)
			r.num = i
		}
		if r.cellStoreRow.CellCount() == 0 && flags.skipEmptyRows {
			continue
		}
		r.Sheet = s
		s.setCurrentRow(r)
		err = rv(r)
		if err != nil {
			return err
		}
	}
	return nil
}

// Add a new Row to a Sheet
func (s *Sheet) AddRow() *Row {
	s.mustBeOpen()
	// NOTE - this is not safe to use concurrently
	if s.currentRow != nil {
		s.cellStore.WriteRow(s.currentRow)
	}
	row := s.cellStore.MakeRow(s)
	row.num = s.MaxRow
	s.MaxRow++
	s.setCurrentRow(row)
	return row
}

func makeRowKey(s *Sheet, i int) string {
	return fmt.Sprintf("%s:%06d", s.cellStoreName, i)
}

// Add a new Row to a Sheet at a specific index
func (s *Sheet) AddRowAtIndex(index int) (*Row, error) {
	s.mustBeOpen()
	if index < 0 || index > s.MaxRow {
		return nil, errors.New("AddRowAtIndex: index out of bounds")
	}

	if s.currentRow != nil {
		s.cellStore.WriteRow(s.currentRow)
	}

	// We move rows in reverse order to avoid overwriting anyting
	for i := (s.MaxRow - 1); i >= index; i-- {
		nRow, err := s.cellStore.ReadRow(makeRowKey(s, i), s)
		if err != nil {
			continue
		}
		nRow.Sheet = s
		s.setCurrentRow(nRow)
		s.cellStore.MoveRow(nRow, i+1)
	}
	row := s.cellStore.MakeRow(s)
	row.num = index
	s.setCurrentRow(row)
	err := s.cellStore.WriteRow(row)
	if err != nil {
		return nil, err
	}
	s.MaxRow++
	return row, nil
}

// Add a DataValidation to a range of cells
func (s *Sheet) AddDataValidation(dv *xlsxDataValidation) {
	s.mustBeOpen()
	s.DataValidations = append(s.DataValidations, dv)
}

// Removes a row at a specific index
func (s *Sheet) RemoveRowAtIndex(index int) error {
	s.mustBeOpen()
	if index < 0 || index >= s.MaxRow {
		return fmt.Errorf("Cannot remove row: index out of range: %d", index)
	}
	if s.currentRow != nil {
		s.setCurrentRow(nil)
	}
	err := s.cellStore.RemoveRow(makeRowKey(s, index))
	if err != nil {
		return err
	}
	for i := index + 1; i < s.MaxRow; i++ {
		nRow, err := s.cellStore.ReadRow(makeRowKey(s, i), s)
		if err != nil {
			continue
		}
		nRow.Sheet = s
		s.cellStore.MoveRow(nRow, i-1)
	}
	s.MaxRow--
	return nil
}

// Make sure we always have as many Rows as we do cells.
func (s *Sheet) maybeAddRow(rowCount int) {
	s.mustBeOpen()
	if rowCount > s.MaxRow {
		loopCnt := rowCount - s.MaxRow
		for i := 0; i < loopCnt; i++ {
			row := s.cellStore.MakeRow(s)
			row.num = i
			s.setCurrentRow(row)
		}
		s.MaxRow = rowCount
	}
}

// Make sure we always have as many Rows as we do cells.
func (s *Sheet) Row(idx int) (*Row, error) {
	s.mustBeOpen()
	s.maybeAddRow(idx + 1)
	if s.currentRow != nil && idx == s.currentRow.num {
		return s.currentRow, nil
	}
	r, err := s.cellStore.ReadRow(makeRowKey(s, idx), s)
	if err != nil {
		if _, ok := err.(*RowNotFoundError); !ok {
			return nil, err
		}
	}
	if r == nil {
		r = s.cellStore.MakeRow(s)
		r.num = idx
	} else {
		r.Sheet = s
	}
	s.setCurrentRow(r)
	return r, nil
}

// Return the Col that applies to this Column index, or return nil if no such Col exists
// Column numbers start from 1.
func (s *Sheet) Col(idx int) *Col {
	s.mustBeOpen()
	if s.Cols == nil {
		panic("trying to use uninitialised ColStore")
	}
	return s.Cols.FindColByIndex(idx + 1)
}

// Get a Cell by passing it's cartesian coordinates (zero based) as
// row and column integer indexes.
//
// For example:
//
//	cell := sheet.Cell(0,0)
//
// ... would set the variable "cell" to contain a Cell struct
// containing the data from the field "A1" on the spreadsheet.
func (s *Sheet) Cell(row, col int) (*Cell, error) {
	s.mustBeOpen()
	// If the user requests a row beyond what we have, then extend.
	for s.MaxRow <= row {
		s.AddRow()
	}

	r, err := s.Row(row)
	if err != nil {
		return nil, err
	}
	cell := r.GetCell(col)
	cell.Row = r
	return cell, err
}

// Set the parameters of a column.  Parameters are passed as a pointer
// to a Col structure which you much construct yourself.
// Column numbers start from 1.
func (s *Sheet) SetColParameters(col *Col) {
	s.mustBeOpen()
	if s.Cols == nil {
		panic("trying to use uninitialised ColStore")
	}
	s.Cols.Add(col)
}

func (s *Sheet) setCol(min, max int, setter func(col *Col)) {
	s.mustBeOpen()
	if s.Cols == nil {
		panic("trying to use uninitialised ColStore")
	}

	cols := s.Cols.getOrMakeColsForRange(s.Cols.Root, min, max)

	for _, col := range cols {
		switch {
		case col.Min < min && col.Max > max:
			// The column completely envelops the range,
			// so we'll split it into three parts and only
			// set the width on the part within the range.
			// The ColStore will do most of this work for
			// us, we just need to create the new Col
			// based on the old one.
			newCol := col.copyToRange(min, max)
			setter(newCol)
			s.Cols.Add(newCol)
		case col.Min < min:
			// If this column crosses the minimum boundary
			// of the range we must split it and only
			// apply the change within the range.  Again,
			// we can lean on the ColStore to deal with
			// the rest we just need to make the new
			// Col.
			newCol := col.copyToRange(min, col.Max)
			setter(newCol)
			s.Cols.Add(newCol)
		case col.Max > max:
			// Likewise if a col definition crosses the
			// maximum boundary of the range, it must also
			// be split
			newCol := col.copyToRange(col.Min, max)
			setter(newCol)
			s.Cols.Add(newCol)
		default:
			newCol := col.copyToRange(min, max)
			setter(newCol)
			s.Cols.Add(newCol)

		}
	}
	return
}

// Set the width of a range of columns.
// Column numbers start from 1.
func (s *Sheet) SetColWidth(min, max int, width float64) {
	s.mustBeOpen()
	s.setCol(min, max, func(col *Col) {
		col.SetWidth(width)
	})
}

// This can be use as the default scale function for the autowidth.
// It works well with the default font sizes.
func DefaultAutoWidth(s string) float64 {
	return (float64(strings.Count(s, "")) + 3.0) * 1.2
}

// Tries to guess the best width for a column, based on the largest
// cell content. A scale function needs to be provided.
// Column numbers start from 1.
func (s *Sheet) SetColAutoWidth(colIndex int, width func(string) float64) error {
	s.mustBeOpen()
	largestWidth := 0.0
	rowVisitor := func(r *Row) error {
		cell := r.GetCell(colIndex - 1)
		value, err := cell.FormattedValue()
		if err != nil {
			return err
		}

		if width(value) > largestWidth {
			largestWidth = width(value)
		}
		return nil
	}
	err := s.ForEachRow(rowVisitor)

	if err != nil {
		return err
	}

	s.SetColWidth(colIndex, colIndex, largestWidth)

	return nil
}

// Set the outline level for a range of columns.
func (s *Sheet) SetOutlineLevel(minCol, maxCol int, outlineLevel uint8) {
	s.mustBeOpen()
	s.setCol(minCol, maxCol, func(col *Col) {
		col.SetOutlineLevel(outlineLevel)
	})
}

// Set the type for a range of columns.
func (s *Sheet) SetType(minCol, maxCol int, cellType CellType) {
	s.mustBeOpen()
	s.setCol(minCol, maxCol, func(col *Col) {
		col.SetType(cellType)
	})

}

// When merging cells, the cell may be the 'original' or the 'covered'.
// First, figure out which cells are merge starting points. Then create
// the necessary cells underlying the merge area.
// Then go through all the underlying cells and apply the appropriate
// border, based on the original cell.
func (s *Sheet) handleMerged() {
	merged := make(map[string]*Cell)

	s.ForEachRow(func(row *Row) error {
		return row.ForEachCell(func(cell *Cell) error {
			if cell.HMerge > 0 || cell.VMerge > 0 {
				coord := GetCellIDStringFromCoords(cell.num, row.num)
				merged[coord] = cell
			}
			return nil
		}, SkipEmptyCells)

	}, SkipEmptyRows)

	// This loop iterates over all cells that should be merged and applies the correct
	// borders to them depending on their position. If any cells required by the merge
	// are missing, they will be allocated by s.Cell().
	for key, cell := range merged {

		maincol, mainrow, _ := GetCoordsFromCellIDString(key)
		for rownum := 0; rownum <= cell.VMerge; rownum++ {
			for colnum := 0; colnum <= cell.HMerge; colnum++ {
				// make cell
				s.Cell(mainrow+rownum, maincol+colnum)

			}
		}
	}
}

func (s *Sheet) makeSheetView(worksheet *xlsxWorksheet) {
	for index, sheetView := range s.SheetViews {
		if sheetView.Pane != nil {
			worksheet.SheetViews.SheetView[index].Pane = &xlsxPane{
				XSplit:      sheetView.Pane.XSplit,
				YSplit:      sheetView.Pane.YSplit,
				TopLeftCell: sheetView.Pane.TopLeftCell,
				ActivePane:  sheetView.Pane.ActivePane,
				State:       sheetView.Pane.State,
			}

		}
	}
	if s.Selected {
		worksheet.SheetViews.SheetView[0].TabSelected = true
	}

}

func (s *Sheet) makeSheetFormatPr(worksheet *xlsxWorksheet) {
	if s.SheetFormat.DefaultRowHeight != 0 {
		worksheet.SheetFormatPr.DefaultRowHeight = s.SheetFormat.DefaultRowHeight
	}
	worksheet.SheetFormatPr.DefaultColWidth = s.SheetFormat.DefaultColWidth
}

func (s *Sheet) makeCols(worksheet *xlsxWorksheet, styles *xlsxStyleSheet) (maxLevelCol uint8) {
	s.mustBeOpen()
	maxLevelCol = 0
	if s.Cols == nil {
		panic("trying to use uninitialised ColStore")
	}
	s.Cols.ForEach(
		func(c int, col *Col) {
			XfId := 0
			style := col.GetStyle()

			hasNumFmt := len(col.numFmt) > 0
			if hasNumFmt {
				if style == nil {
					style = NewStyle()
				}

				xNumFmt := styles.newNumFmt(col.numFmt)
				XfId = handleStyleForXLSX(style, xNumFmt.NumFmtId, styles)
			} else {
				if style != nil {
					XfId = handleStyleForXLSX(style, 0, styles)
				}
			}
			col.outXfID = XfId

			// When the cols content is empty, the cols flag is not output in the xml file.
			if worksheet.Cols == nil {
				worksheet.Cols = &xlsxCols{Col: []xlsxCol{}}
			}
			worksheet.Cols.Col = append(worksheet.Cols.Col,
				xlsxCol{
					Min:          col.Min,
					Max:          col.Max,
					Hidden:       col.Hidden,
					Width:        col.Width,
					CustomWidth:  col.CustomWidth,
					Collapsed:    col.Collapsed,
					OutlineLevel: col.OutlineLevel,
					Style:        &XfId,
					BestFit:      col.BestFit,
					Phonetic:     col.Phonetic,
				})

			if col.OutlineLevel != nil && *col.OutlineLevel > maxLevelCol {
				maxLevelCol = *col.OutlineLevel
			}
		})

	return maxLevelCol
}

func (s *Sheet) prepSheetForMarshalling(maxLevelCol uint8) {
	s.SheetFormat.OutlineLevelCol = maxLevelCol
}

func (s *Sheet) prepWorksheetFromRows(worksheet *xlsxWorksheet, relations *xlsxWorksheetRels) error {
	s.mustBeOpen()
	var maxCell, maxRow int

	prepRow := func(row *Row) error {
		if row.num > maxRow {
			maxRow = row.num
		}

		prepCell := func(cell *Cell) error {
			if cell.num > maxCell {
				maxCell = cell.num
			}
			cellID := GetCellIDStringFromCoords(cell.num, row.num)
			if nil != cell.DataValidation {
				if nil == worksheet.DataValidations {
					worksheet.DataValidations = &xlsxDataValidations{}
				}
				cell.DataValidation.Sqref = cellID
				worksheet.DataValidations.DataValidation = append(worksheet.DataValidations.DataValidation, cell.DataValidation)
				worksheet.DataValidations.Count = len(worksheet.DataValidations.DataValidation)
			}

			if cell.Hyperlink != (Hyperlink{}) {
				if worksheet.Hyperlinks == nil {
					worksheet.Hyperlinks = &xlsxHyperlinks{HyperLinks: []xlsxHyperlink{}}
				}

				var relId string
				if relations != nil && relations.Relationships != nil {
					for _, rel := range relations.Relationships {
						if rel.Target == cell.Hyperlink.Link {
							relId = rel.Id
						}
					}
				}

				if relId != "" {

					xlsxLink := xlsxHyperlink{
						RelationshipId: relId,
						Reference:      cellID,
						DisplayString:  cell.Hyperlink.DisplayString,
						Tooltip:        cell.Hyperlink.Tooltip}
					worksheet.Hyperlinks.HyperLinks = append(worksheet.Hyperlinks.HyperLinks, xlsxLink)
				}
			}

			if cell.HMerge > 0 || cell.VMerge > 0 {
				mc := xlsxMergeCell{}
				start := fmt.Sprintf("%s%d", ColIndexToLetters(cell.num), row.num+1)
				endcol := cell.num + cell.HMerge
				endrow := row.num + cell.VMerge + 1
				end := fmt.Sprintf("%s%d", ColIndexToLetters(endcol), endrow)
				mc.Ref = start + ":" + end
				if worksheet.MergeCells == nil {
					worksheet.MergeCells = &xlsxMergeCells{}
				}
				worksheet.MergeCells.Cells = append(worksheet.MergeCells.Cells, mc)
				worksheet.MergeCells.addCell(mc)
			}
			return nil
		}

		return row.ForEachCell(prepCell, SkipEmptyCells)
	}

	err := s.ForEachRow(prepRow, SkipEmptyRows)
	if err != nil {
		return err
	}
	worksheet.SheetFormatPr.OutlineLevelCol = s.SheetFormat.OutlineLevelCol
	worksheet.SheetFormatPr.OutlineLevelRow = s.SheetFormat.OutlineLevelRow
	if worksheet.MergeCells != nil {
		worksheet.MergeCells.Count = len(worksheet.MergeCells.Cells)
	}

	if s.AutoFilter != nil {
		worksheet.AutoFilter = &xlsxAutoFilter{Ref: fmt.Sprintf("%v:%v", s.AutoFilter.TopLeftCell, s.AutoFilter.BottomRightCell)}
	}

	dimension := xlsxDimension{}
	dimension.Ref = "A1:" + GetCellIDStringFromCoords(maxCell, maxRow)
	if dimension.Ref == "A1:A1" {
		dimension.Ref = "A1"
	}
	worksheet.Dimension = dimension
	return nil
}

func (s *Sheet) makeRows(worksheet *xlsxWorksheet, styles *xlsxStyleSheet, refTable *RefTable, relations *xlsxWorksheetRels, maxLevelCol uint8) error {
	s.mustBeOpen()
	maxRow := 0
	maxCell := 0
	var maxLevelRow uint8
	xSheet := xlsxSheetData{}
	makeR := func(row *Row) error {
		r := row.num
		if r > maxRow {
			maxRow = r
		}
		xRow := xlsxRow{}
		xRow.R = r + 1
		if row.customHeight {
			xRow.CustomHeight = true
			xRow.Ht = fmt.Sprintf("%g", row.GetHeight())
		}
		xRow.OutlineLevel = row.GetOutlineLevel()
		if xRow.OutlineLevel > maxLevelRow {
			maxLevelRow = xRow.OutlineLevel
		}
		makeC := func(cell *Cell) error {
			var XfId int

			c := cell.num
			col := s.Col(c)
			if col != nil {
				XfId = col.outXfID
			}

			// generate NumFmtId and add new NumFmt
			xNumFmt := styles.newNumFmt(cell.NumFmt)

			style := cell.style
			switch {
			case style != nil:
				XfId = handleStyleForXLSX(style, xNumFmt.NumFmtId, styles)
			case len(cell.NumFmt) == 0:
				// Do nothing
			case col == nil:
				XfId = handleNumFmtIdForXLSX(xNumFmt.NumFmtId, styles)
			case !compareFormatString(col.numFmt, cell.NumFmt):
				XfId = handleNumFmtIdForXLSX(xNumFmt.NumFmtId, styles)
			}

			if c > maxCell {
				maxCell = c
			}
			xC := xlsxC{
				S: XfId,
				R: GetCellIDStringFromCoords(c, r),
			}
			if cell.formula != "" {
				xC.F = &xlsxF{Content: cell.formula}
			}
			switch cell.cellType {
			case CellTypeInline:
				// Inline strings are turned into shared strings since they are more efficient.
				// This is what Excel does as well.
				fallthrough
			case CellTypeString:
				if len(cell.Value) > 0 {
					xC.V = strconv.Itoa(refTable.AddString(cell.Value))
				} else if len(cell.RichText) > 0 {
					xC.V = strconv.Itoa(refTable.AddRichText(cell.RichText))
				}
				xC.T = "s"
			case CellTypeNumeric:
				// Numeric is the default, so the type can be left blank
				xC.V = cell.Value
			case CellTypeBool:
				xC.V = cell.Value
				xC.T = "b"
			case CellTypeError:
				xC.V = cell.Value
				xC.T = "e"
			case CellTypeDate:
				xC.V = cell.Value
				xC.T = "d"
			case CellTypeStringFormula:
				xC.V = cell.Value
				xC.T = "str"
			default:
				panic(errors.New("unknown cell type cannot be marshaled"))
			}

			xRow.C = append(xRow.C, xC)
			if nil != cell.DataValidation {
				if nil == worksheet.DataValidations {
					worksheet.DataValidations = &xlsxDataValidations{}
				}
				cell.DataValidation.Sqref = xC.R
				worksheet.DataValidations.DataValidation = append(worksheet.DataValidations.DataValidation, cell.DataValidation)
				worksheet.DataValidations.Count = len(worksheet.DataValidations.DataValidation)
			}

			if cell.Hyperlink != (Hyperlink{}) {
				if worksheet.Hyperlinks == nil {
					worksheet.Hyperlinks = &xlsxHyperlinks{HyperLinks: []xlsxHyperlink{}}
				}

				var relId string
				for _, rel := range relations.Relationships {
					if rel.Target == cell.Hyperlink.Link {
						relId = rel.Id
					}
				}

				if relId != "" {

					xlsxLink := xlsxHyperlink{
						RelationshipId: relId,
						Reference:      xC.R,
						DisplayString:  cell.Hyperlink.DisplayString,
						Tooltip:        cell.Hyperlink.Tooltip}
					worksheet.Hyperlinks.HyperLinks = append(worksheet.Hyperlinks.HyperLinks, xlsxLink)
				}
			}

			if cell.HMerge > 0 || cell.VMerge > 0 {
				// r == rownum, c == colnum
				mc := xlsxMergeCell{}
				start := fmt.Sprintf("%s%d", ColIndexToLetters(c), r+1)
				endcol := c + cell.HMerge
				endrow := r + cell.VMerge + 1
				end := fmt.Sprintf("%s%d", ColIndexToLetters(endcol), endrow)
				mc.Ref = start + ":" + end
				if worksheet.MergeCells == nil {
					worksheet.MergeCells = &xlsxMergeCells{}
				}
				worksheet.MergeCells.Cells = append(worksheet.MergeCells.Cells, mc)
				worksheet.MergeCells.addCell(mc)
			}
			return nil
		}
		err := row.ForEachCell(makeC, SkipEmptyCells)
		if err != nil {
			return err
		}
		xSheet.Row = append(xSheet.Row, xRow)
		return nil
	}

	err := s.ForEachRow(makeR, SkipEmptyRows)
	if err != nil {
		return err
	}

	// Update sheet format with the freshly determined max levels
	s.SheetFormat.OutlineLevelCol = maxLevelCol
	s.SheetFormat.OutlineLevelRow = maxLevelRow
	// .. and then also apply this to the xml worksheet
	worksheet.SheetFormatPr.OutlineLevelCol = s.SheetFormat.OutlineLevelCol
	worksheet.SheetFormatPr.OutlineLevelRow = s.SheetFormat.OutlineLevelRow
	if worksheet.MergeCells != nil {
		worksheet.MergeCells.Count = len(worksheet.MergeCells.Cells)
	}

	if s.AutoFilter != nil {
		worksheet.AutoFilter = &xlsxAutoFilter{Ref: fmt.Sprintf("%v:%v", s.AutoFilter.TopLeftCell, s.AutoFilter.BottomRightCell)}
	}

	worksheet.SheetData = xSheet
	dimension := xlsxDimension{}
	dimension.Ref = "A1:" + GetCellIDStringFromCoords(maxCell, maxRow)
	if dimension.Ref == "A1:A1" {
		dimension.Ref = "A1"
	}
	worksheet.Dimension = dimension
	return nil
}

func (s *Sheet) makeDataValidations(worksheet *xlsxWorksheet) {
	s.mustBeOpen()
	if len(s.DataValidations) > 0 {
		if worksheet.DataValidations == nil {
			worksheet.DataValidations = &xlsxDataValidations{}
		}
		worksheet.DataValidations.DataValidation = append(worksheet.DataValidations.DataValidation, s.DataValidations...)
		worksheet.DataValidations.Count = len(worksheet.DataValidations.DataValidation)
	}
}

func (s *Sheet) MarshalSheet(w io.Writer, refTable *RefTable, styles *xlsxStyleSheet, relations *xlsxWorksheetRels) error {
	worksheet := newXlsxWorksheet()

	s.handleMerged()
	s.makeSheetView(worksheet)
	s.makeSheetFormatPr(worksheet)
	maxLevelCol := s.makeCols(worksheet, styles)
	s.makeDataValidations(worksheet)
	s.prepSheetForMarshalling(maxLevelCol)
	err := s.prepWorksheetFromRows(worksheet, relations)
	if err != nil {
		return err
	}
	return nil
}

// Dump sheet to its XML representation, intended for internal use only
func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet, relations *xlsxWorksheetRels) *xlsxWorksheet {
	s.mustBeOpen()
	worksheet := newXlsxWorksheet()

	// Scan through the sheet and see if there are any merged cells. If there
	// are, we may need to extend the size of the sheet. There needs to be
	// phantom cells underlying the area covered by the merged cell
	s.handleMerged()

	s.makeSheetView(worksheet)
	s.makeSheetFormatPr(worksheet)
	maxLevelCol := s.makeCols(worksheet, styles)
	s.makeDataValidations(worksheet)
	s.makeRows(worksheet, styles, refTable, relations, maxLevelCol)

	return worksheet
}

func handleStyleForXLSX(style *Style, NumFmtId int, styles *xlsxStyleSheet) (XfId int) {
	xFont, xFill, xBorder, xCellXf := style.makeXLSXStyleElements()
	fontId := styles.addFont(xFont)
	fillId := styles.addFill(xFill)

	// HACK - adding light grey fill, as in OO and Google
	greyfill := xlsxFill{}
	greyfill.PatternFill.PatternType = "lightGray"
	styles.addFill(greyfill)

	borderId := styles.addBorder(xBorder)
	xCellXf.FontId = fontId
	xCellXf.FillId = fillId
	xCellXf.BorderId = borderId
	xCellXf.NumFmtId = NumFmtId
	// apply the numFmtId when it is not the default cellxf
	if xCellXf.NumFmtId > 0 {
		xCellXf.ApplyNumberFormat = true
	}

	xCellXf.Alignment.Horizontal = style.Alignment.Horizontal
	xCellXf.Alignment.Indent = style.Alignment.Indent
	xCellXf.Alignment.ShrinkToFit = style.Alignment.ShrinkToFit
	xCellXf.Alignment.TextRotation = style.Alignment.TextRotation
	xCellXf.Alignment.Vertical = style.Alignment.Vertical
	xCellXf.Alignment.WrapText = style.Alignment.WrapText

	XfId = styles.addCellXf(xCellXf)
	return
}

func handleNumFmtIdForXLSX(NumFmtId int, styles *xlsxStyleSheet) (XfId int) {
	xCellXf := makeXLSXCellElement()
	xCellXf.NumFmtId = NumFmtId
	if xCellXf.NumFmtId > 0 {
		xCellXf.ApplyNumberFormat = true
	}
	XfId = styles.addCellXf(xCellXf)
	return
}

func IsSaneSheetName(sheetName string) error {
	runeLength := utf8.RuneCountInString(sheetName)
	if runeLength > 31 || runeLength == 0 {
		return fmt.Errorf("sheet name must be 31 or fewer characters long.  It is currently '%d' characters long", runeLength)
	}
	// Iterate over the runes
	for _, r := range sheetName {
		// Excel forbids : \ / ? * [ ]
		if r == ':' || r == '\\' || r == '/' || r == '?' || r == '*' || r == '[' || r == ']' {
			return fmt.Errorf("sheet name must not contain any restricted characters : \\ / ? * [ ] but contains '%s'", string(r))
		}
	}
	return nil
}
