package xlsx

import (
	"fmt"
	"strings"
)

type MemoryRow struct {
	row    *Row
	maxCol int
	cells  []*Cell
}

func makeMemoryRow(sheet *Sheet) *MemoryRow {
	mr := &MemoryRow{
		row:    new(Row),
		maxCol: -1,
	}
	mr.row.Sheet = sheet
	mr.row.cellStoreRow = mr
	sheet.setCurrentRow(mr.row)
	return mr
}

func (mr *MemoryRow) Updatable() {
	// Do nothing
}

func (mr *MemoryRow) CellUpdatable(c *Cell) {
	// Do nothing
}

func (mr *MemoryRow) AddCell() *Cell {
	cell := newCell(mr.row, mr.maxCol+1)
	mr.PushCell(cell)
	return cell
}

func (mr *MemoryRow) PushCell(c *Cell) {
	mr.growCellsSlice(c.num + 1)
	mr.cells[c.num] = c
}

func (mr *MemoryRow) growCellsSlice(newSize int) {
	if newSize > (mr.maxCol + 1) {
		mr.maxCol = (newSize - 1)
	}

	capacity := cap(mr.cells)
	if newSize > capacity {
		newCap := 2 * capacity
		if newSize > newCap {
			newCap = newSize
		}
		capacity = newCap
	}
	newSlice := make([]*Cell, newSize, capacity)
	copy(newSlice, mr.cells)
	mr.cells = newSlice
}

func (mr *MemoryRow) GetCell(colIdx int) *Cell {
	if colIdx >= len(mr.cells) {
		cell := newCell(mr.row, colIdx)
		mr.growCellsSlice(colIdx + 1)

		mr.cells[colIdx] = cell
		return cell
	}

	cell := mr.cells[colIdx]
	if cell == nil {
		cell = newCell(mr.row, colIdx)
		mr.cells[colIdx] = cell
	}
	return cell
}

func (mr *MemoryRow) ForEachCell(cvf CellVisitorFunc, option ...CellVisitorOption) error {
	flags := &cellVisitorFlags{}
	for _, opt := range option {
		opt(flags)
	}
	fn := func(ci int, c *Cell) error {
		if c == nil {
			if flags.skipEmptyCells {
				return nil
			}
			c = mr.GetCell(ci)
		}
		if !c.Modified() && flags.skipEmptyCells {
			return nil
		}
		c.Row = mr.row
		return cvf(c)
	}

	for ci, cell := range mr.cells {
		err := fn(ci, cell)
		if err != nil {
			return err
		}
	}
	cellCount := len(mr.cells)
	if !flags.skipEmptyCells {
		for ci := cellCount; ci < mr.row.Sheet.MaxCol; ci++ {
			c := mr.GetCell(ci)
			err := cvf(c)
			if err != nil {
				return err
			}

		}
	}

	return nil
}

// MaxCol returns the index of the rightmost cell in the row's column.
func (mr *MemoryRow) MaxCol() int {
	return mr.maxCol
}

// CellCount returns the total number of cells in the row.
func (mr *MemoryRow) CellCount() int {
	return mr.maxCol + 1
}

// MemoryCellStore is the default CellStore - it holds all rows and
// cells in system memory.  This is fast, right up until you run out
// of memory ;-)
type MemoryCellStore struct {
	rows map[string]*Row
}

// UseMemoryCellStore is a FileOption that makes all Sheet instances
// for a File use memory as their backing store.  This is the default
// backing store.  You can use this option when you are comfortable
// keeping the contents of each Sheet in memory.  This is faster than
// using a disk backed store, but can easily use a large amount of
// memory and, if you exhaust the available system memory, it'll
// actualy be slower than using a disk backed store (e.g. DiskV).
func UseMemoryCellStore(f *File) {
	f.cellStoreConstructor = NewMemoryCellStore
}

// NewMemoryCellStore returns a pointer to a newly allocated MemoryCellStore
func NewMemoryCellStore() (CellStore, error) {
	cs := &MemoryCellStore{
		rows: make(map[string]*Row),
	}
	return cs, nil
}

// Close is nullOp for the MemoryCellStore, but we have to comply with
// the interface.
func (mcs *MemoryCellStore) Close() error {
	return nil
}

// ReadRow returns a Row identfied by the given key.
func (mcs *MemoryCellStore) ReadRow(key string, s *Sheet) (*Row, error) {
	r, ok := mcs.rows[key]
	if !ok {
		return nil, NewRowNotFoundError(key, "No such row")
	}
	return r, nil
}

// WriteRow pushes the Row to the MemoryCellStore.
func (mcs *MemoryCellStore) WriteRow(r *Row) error {
	if r != nil {
		key := r.key()
		mcs.rows[key] = r
	}
	return nil
}

// MoveRow moves the persisted Row's position in the sheet.
func (mcs *MemoryCellStore) MoveRow(r *Row, index int) error {
	oldKey := r.key()
	r.num = index
	newKey := r.key()
	if _, exists := mcs.rows[newKey]; exists {
		return fmt.Errorf("Target index for row (%d) would overwrite a row already exists", index)
	}
	mcs.rows[newKey] = r
	delete(mcs.rows, oldKey)
	return nil
}

// RemoveRow removes a row from the sheet, it doesn't specifically
// move any following rows, leaving this decision to the user.
func (mcs *MemoryCellStore) RemoveRow(key string) error {
	r, ok := mcs.rows[key]
	if ok {
		r.Sheet.setCurrentRow(nil)
		delete(mcs.rows, key)
	}
	return nil
}

// MakeRowWithLen returns an empty Row, with a preconfigured starting length.
func (mcs *MemoryCellStore) MakeRowWithLen(sheet *Sheet, len int) *Row {
	mr := makeMemoryRow(sheet)
	mr.maxCol = len - 1
	mr.growCellsSlice(len)
	return mr.row
}

// MakeRow returns an empty Row
func (mcs *MemoryCellStore) MakeRow(sheet *Sheet) *Row {
	return makeMemoryRow(sheet).row
}

// Extract the row key from a provided cell key
func keyToRowKey(key string) string {
	parts := strings.Split(key, ":")
	return parts[0] + ":" + parts[1]
}
