import "stringUtil"
import "textUtil"

// InspectDialog: a Dialog subclass specialized for inspecting MiniScript values.
InspectDialog = new textUtil.Dialog
InspectDialog.width = 55
InspectDialog.contentHeight = 15
InspectDialog.backColor = "#140951"
InspectDialog.titleColor = "#1C4581"
InspectDialog.textColor = "#65B4A7"
InspectDialog.valueColor = "#F0FEDF"
InspectDialog.valueBackground = "#1C4581"
InspectDialog.valueInfo = null

InspectDialogValue = {}  // stack of values (if we deside to go inside lists/maps
InspectDialogValue.value = null
InspectDialogValue.parent = null
InspectDialogValue.type = null
InspectDialogValue.lines = null
InspectDialogValue.linesOffset = null
InspectDialogValue.linesBelowContent = null
InspectDialogValue.keyWidth = null
InspectDialogValue.sortedKeys = null
InspectDialogValue.kLines = null
InspectDialogValue.vLines = null
InspectDialogValue.cursor = null
InspectDialogValue.isAtKey = false
InspectDialogValue.init = function(value, dlog)
	self.value = @value
	self.type = InspectDialogValue.getTypeString(@value)
	if @value == null then
		self.initSimpleValue "null", dlog
	else if @value isa number then
		self.initSimpleValue str(value), dlog
	else if @value isa string then
		self.initSimpleValue value, dlog
	else if @value isa funcRef then
		self.initSimpleValue str(@value), dlog
	else if @value isa list then
		self.keyWidth = 5
		self.sortedKeys = value.indexes
		self.initContainerValue value, dlog
	else if @value isa map then
		self.keyWidth = (dlog.width - 4) / 2
		self.sortedKeys = value.indexes.sort
		self.initContainerValue value, dlog
	else
		self.initSimpleValue "value of unknown type", dlog
	end if
	return self
end function
InspectDialogValue.getTypeString = function(value)
	if @value == null then
		return "null"
	else if @value isa number then
		return "number"
	else if @value isa string then
		return "string(" + value.len + ")"
	else if @value isa funcRef then
		return "funcRef"
	else if @value isa list then
		return "list(" + value.len + ")"
	else if @value isa map then
		typeString = "map(" + value.len + ")"
		name = mapName(value)
		if name then typeString += " == " + name
		return typeString
	else
		return "unknown type"
	end if
end function
InspectDialogValue.initSimpleValue = function(text, dlog)
	self.lines = text.wrap(dlog.width - 4)
	self.linesOffset = 0
	self.linesBelowContent = self.lines.len - dlog.contentHeight
	self.draw = function
		dlog.drawSimpleValue self
	end function
	self.handleKey = function(k)
		if k.code == 19 then  // up
			self.scrollUp
			return true
		else if k.code == 20 then  // down
			self.scrollDown
			return true
		end if
	end function
	self.setCursor = function(hIndex, vIndex) ; end function
	self.wheelDown = function
		self.scrollDown
	end function
	self.wheelUp = function
		self.scrollUp
	end function
end function
InspectDialogValue.scrollDown = function
	if self.linesBelowContent > 0 then
		self.linesOffset += 1
		self.linesBelowContent -= 1
	end if
end function
InspectDialogValue.scrollUp = function
	if self.linesOffset > 0 then
		self.linesOffset -= 1
		self.linesBelowContent += 1
	end if
end function
InspectDialogValue.initContainerValue = function(value, dlog)
	InspectDialogValue = outer.InspectDialogValue
	valueWidth = dlog.width - 5 - self.keyWidth
	self.kLines = []
	self.vLines = []
	for k in self.sortedKeys
		self.kLines.push str(@k)[:self.keyWidth]
		self.vLines.push str(value[@k])[:valueWidth]
	end for
	self.linesOffset = 0
	self.linesBelowContent = self.sortedKeys.len - dlog.contentHeight
	if self.sortedKeys.len > 0 then self.cursor = 0
	self.draw = function
		dlog.drawContainerValue self
	end function
	self.handleKey = function(k)
		if k.code == 19 then  // up
			self.cursorUp
			return true
		else if k.code == 20 then  // down
			self.cursorDown dlog.contentHeight
			return true
		else if k.code == 17 then  // left
			if not self.isAtKey then self.isAtKey = true
			return true
		else if k.code == 18 then  // right
			if self.isAtKey then self.isAtKey = false
			return true
		else if k.code == 10 then  // return
			oldValueInfo = dlog.valueInfo
			dlog.valueInfo = (new InspectDialogValue).init(self.valueUnderCursor, dlog)
			dlog.valueInfo.parent = oldValueInfo
			return true
		end if
	end function
	self.setCursor = function(hIndex, vIndex)
		if vIndex + self.linesOffset >= self.sortedKeys.len then return false
		self.cursor = vIndex + self.linesOffset
		self.isAtKey = hIndex < self.keyWidth
		return true
	end function
	self.wheelDown = function
		self.cursorDown dlog.contentHeight
	end function
	self.wheelUp = function
		self.cursorUp
	end function
end function
InspectDialogValue.cursorUp = function
	if self.cursor != null and self.cursor > 0 then
		self.cursor -= 1
		if self.linesOffset > self.cursor then
			self.linesOffset -= 1
			self.linesBelowContent += 1
		end if
	end if
end function
InspectDialogValue.cursorDown = function(contentHeight)
	if self.cursor != null and self.cursor + 1 < self.sortedKeys.len then
		self.cursor += 1
		if self.cursor >= self.linesOffset + contentHeight then
			self.linesOffset += 1
			self.linesBelowContent -= 1
		end if
		return true
	end if
end function
InspectDialogValue.valueUnderCursor = function
	if self.cursor == null then return
	key = self.sortedKeys[self.cursor]
	if self.isAtKey then return @key
	return self.value[@key]
end function

InspectDialog.make = function(value)
	dlog = textUtil.Dialog.make("Inspect value")
	dlog.__isa = InspectDialog
	dlog.height = dlog.contentHeight + 8
	dlog.okBtn.caption = "Inspect (Ret)"
	dlog.okBtn.visible = true
	dlog.altBtn.caption = "Current (c)"
	dlog.altBtn.visible = true
	dlog.altBtn.key = "c"
	dlog.cancelBtn.caption = "Initial (Esc)"
	dlog.cancelBtn.visible = true
	dlog.backBtn = new textUtil.DialogButton
	dlog.backBtn.caption = "Back (b)"
	dlog.backBtn.visible = false
	dlog.backBtn.key = "b"
	dlog.backBtn.x = dlog.left + 2
	dlog.backBtn.y = dlog.top - 1
	dlog.valueInfo = (new InspectDialogValue).init(@value, dlog)
	return dlog
end function

InspectDialog.drawContent = function(cols, rows)
	self._cols = cols
	self._rows = rows
	text.setCell self._cols, self._rows, " "
	text.setCellBackColor self._cols, self._rows, self.backColor
	value = @self.valueInfo.value
	typeRow = self.top - 1
	self.center = self.left + self.width / 2
	text.color = self.textColor; text.backColor = self.backColor
	textUtil.printCenteredAt self.center, typeRow, self.valueInfo.type
	if self.valueInfo.parent != null then
		self.backBtn.visible = true
		text.color = "#CCCCCC"
		self.backBtn.draw
	else
		self.backBtn.visible = false
	end if
	self.valueInfo.draw
end function

InspectDialog.drawSimpleValue = function(dlogValue)
	vCols = self._cols[2:-2]
	vRows = self._rows[5:-3]
	text.setCell vCols, vRows, " "
	text.setCellBackColor vCols, vRows, self.valueBackground
	if dlogValue.linesOffset > 0 then
		text.color = self.textColor; text.backColor = self.backColor
		textUtil.printCenteredAt self.center, self._rows[-3], "Press ↑ to scroll"
	end if
	if dlogValue.linesBelowContent > 0 then
		text.color = self.textColor; text.backColor = self.backColor
		textUtil.printCenteredAt self.center, self._rows[4], "Press ↓ to scroll"
	end if
	lines = dlogValue.lines[dlogValue.linesOffset : dlogValue.linesOffset + self.contentHeight]
	text.color = self.valueColor ; text.backColor = self.valueBackground
	for i in lines.indexes
		textUtil.printAt vCols[0], vRows[-1 - i], lines[i]
	end for
	self.okBtn.visible = false
	self.altBtn.visible = (dlogValue.parent != null)
	self.cancelBtn.visible = true
end function

InspectDialog.drawContainerValue = function(dlogValue)
	kCols = self._cols[2 : 2 + dlogValue.keyWidth]
	vCols =  self._cols[2 + dlogValue.keyWidth + 1 : -2]
	vRows = self._rows[5:-3]
	text.setCell kCols, vRows, " "
	text.setCellBackColor kCols, vRows, self.valueBackground
	text.setCell vCols, vRows, " "
	text.setCellBackColor vCols, vRows, self.valueBackground
	if dlogValue.linesOffset > 0 then
		text.color = self.textColor; text.backColor = self.backColor
		textUtil.printCenteredAt self.center, self._rows[-3], "Move cursor up to scroll"
	end if
	if dlogValue.linesBelowContent > 0 then
		text.color = self.textColor; text.backColor = self.backColor
		textUtil.printCenteredAt self.center, self._rows[4], "Move cursor down to scroll"
	end if
	kLines = dlogValue.kLines[dlogValue.linesOffset : dlogValue.linesOffset + self.contentHeight]
	vLines = dlogValue.vLines[dlogValue.linesOffset : dlogValue.linesOffset + self.contentHeight]
	if dlogValue.cursor != null then
		cursor = dlogValue.cursor - dlogValue.linesOffset
	else
		cursor = -1
	end if
	cursorBackColor = color.lerp(self.valueColor, color.black)
	if cursor >= 0 then
		cursorRow = vRows[-1 - cursor]
		if dlogValue.isAtKey then
			text.setCellBackColor kCols, cursorRow, self.valueColor
			text.setCellBackColor vCols, cursorRow, cursorBackColor
		else
			text.setCellBackColor kCols, cursorRow, cursorBackColor
			text.setCellBackColor vCols, cursorRow, self.valueColor
		end if
	end if
	for i in kLines.indexes
		if i != cursor then
			text.color = self.valueColor ; text.backColor = self.valueBackground
		else if dlogValue.isAtKey then
			text.color = self.valueBackground ; text.backColor = self.valueColor
		else
			text.color = self.valueColor ; text.backColor = cursorBackColor
		end if
		textUtil.printAt kCols[0], vRows[-1 - i], kLines[i]
		if i != cursor then
			text.color = self.valueColor ; text.backColor = self.valueBackground
		else if not dlogValue.isAtKey then
			text.color = self.valueBackground ; text.backColor = self.valueColor
		else
			text.color = self.valueColor ; text.backColor = cursorBackColor
		end if
		textUtil.printAt vCols[0], vRows[-1 - i], vLines[i]
	end for
	if dlogValue.cursor != null then
		text.color = self.textColor; text.backColor = self.backColor
		underCursor = dlogValue.valueUnderCursor
		textUtil.printCenteredAt self.center, self._rows[3], "cursor at: " + InspectDialogValue.getTypeString(@underCursor)
	end if
	self.okBtn.visible = true
	self.altBtn.visible = (dlogValue.parent != null)
	self.cancelBtn.visible = true
end function

InspectDialog.handleKey = function(k)
	if k.code == 27 then
		info = self.valueInfo
		while info.parent
			info = info.parent
		end while
		self.cancelBtn.payload = @info.value
		return null
	else if k == self.altBtn.key and self.altBtn.visible then
		self.altBtn.payload = @self.valueInfo.value
		return null
	else if k == self.backBtn.key and self.backBtn.visible then
		self.valueInfo = self.valueInfo.parent
		self.draw
		return true
	end if
	isHandled = self.valueInfo.handleKey(k)
	if isHandled then self.draw
	return isHandled
end function

InspectDialog.handleMouse = function
	if self.backBtn.visible and self.backBtn.contains(mouse) then
		if self.backBtn.trackHit then
			self.valueInfo = self.valueInfo.parent
			self.draw
			return true
		end if
	end if
	if self.okBtn.visible and self.okBtn.contains(mouse) then
		if self.okBtn.trackHit then
			oldValueInfo = self.valueInfo
			self.valueInfo = (new InspectDialogValue).init(self.valueInfo.valueUnderCursor, self)
			self.valueInfo.parent = oldValueInfo
			self.draw
			return true
		end if
	end if
	if self.cancelBtn.visible and self.cancelBtn.contains(mouse) then
		info = self.valueInfo
		while info.parent
			info = info.parent
		end while
		self.cancelBtn.payload = @info.value
		return null
	end if
	if self.altBtn.visible and self.altBtn.contains(mouse) then
		self.altBtn.payload = @self.valueInfo.value
		return null
	end if
	hIndex = self._cols[2:-2].indexOf(textUtil.textCol(mouse.x))
	vIndex = self._rows[5:-3].indexOf(textUtil.textRow(mouse.y))
	if hIndex != null and vIndex != null and self.valueInfo.setCursor(hIndex, self.contentHeight - vIndex - 1) then
		self.draw
		return true
	end if
	return false
end function

InspectDialog.handleMouseWheel = function(axisValue)
	if axisValue > 0 then
		self.valueInfo.wheelDown
	else
		self.valueInfo.wheelUp
	end if
	self.draw
end function

if locals == globals then
	d = InspectDialog.make({"a": 1.79769313486231570e+308, "b": [null, @PixelDisplay.drawImage, 1/0, 0/0, text, Display, file.readLines("/sys/tips.txt").join(char(13))] + ["foo"] * 20})
	btn = d.show
	obj = @btn.payload
	print "You selected: " + str(@obj).ellideEnd(53)
end if