local Graph = {
	getColor = function(self, value, trace)
		local colorIndex = math.ceil((value - self.min[trace]) / ((self.max[trace] - self.min[trace]) / #self.gradient[trace]))
		if colorIndex == 0 then colorIndex = 1 end
		if self.target.isColor() then
			return self.gradient[trace][colorIndex]
		else
			return colors.white
		end
	end,
	draw = function(self)
		--get the updates first.
		for i=1, #self.update do
			self.history[i][#self.history[i] + 1] = self.update[i]()
		end
		if self.target.setTextScale then self.target.setTextScale(0.5) end
		self.target.setBackgroundColor(colors.black)
		self.target.setTextColor(colors.white)
		self.target.clear()
		local newMax, newMin = self.max[1], self.min[1]
		--draw traces
		for trace=1,#self.update do
			if self.history[trace][#self.history[trace]] > self.max[trace] then
				if self.sharedBounds and self.history[trace][#self.history[trace]] > newMax then
					newMax = self.history[trace][#self.history[trace]]
				else
					self.max[trace] = self.history[trace][#self.history[trace]]
				end
			elseif self.history[trace][#self.history[trace]] < self.min[trace] then
				if self.sharedBounds and self.history[trace][#self.history[trace]] < newMin then
					newMin = self.history[trace][#self.history[trace]]
				else
					self.min[trace] = self.history[trace][#self.history[trace]]
				end
			end
			local xLim, yLim = self.target.getSize()
			if #self.history[trace] > xLim then table.remove(self.history[trace], 1) end
			--we set up our value string for numeric value readout here, but draw it later so it doesn't interfere with the full-screen trace.
			for i=1, #self.history[trace] do
				self.target.setCursorPos(i, yLim - math.ceil((yLim - 1) * (self.history[trace][i] - self.min[trace]) / (self.max[trace] - self.min[trace])))
				self.target.setBackgroundColor(self:getColor(self.history[trace][i], trace))
				self.target.write(" ")
				self.target.setBackgroundColor(colors.black)
			end
		end
		--adjust min-max for next round.
		if self.sharedBounds then
			for i=1, #self.update do
				self.max[i] = newMax
				self.min[i] = newMin
			end
		end
		--draw trace labels.
		for trace = 1, #self.update do
			local valueString, cStart, cEnd = ""
			if self.valueText[trace] then
				valueString = self.valueText[trace]..": "
				cStart = #valueString + 1
				valueString = valueString..tostring(self.history[trace][#self.history[trace]])
				cEnd = #valueString
				valueString = valueString.." / "..self.max[trace]
			end
			local xLim, yLim = self.target.getSize()
			if trace + 1 <= yLim then
				self.target.setCursorPos(2, trace + 1)
				for i=1, #valueString do
					if i >= cStart and i <= cEnd then
						self.target.setTextColor(self:getColor(self.history[trace][#self.history[trace]], trace))
					end
					self.target.write(string.sub(valueString, i, i))
					self.target.setTextColor(colors.white)
				end
			end
		end
	end,
	tostring = function(self)
		local valueString = ""
		if self.valueText[1] then
			valueString = self.valueText[1]..": "
		end
		valueString = valueString..tostring(self.history[1][#self.history[1]]).." / "..self.max[1]
		return valueString
	end,
}

local gmetatable = {
	__index = Graph,
	__tostring = function(g) return g:tostring() end,
}

function new(target, updateFunction, valueText, gradientTable, min, max, sharedBounds)
	if not target then return nil, "Must specify output target!" end
	if not updateFunction then return nil, "Must specify update function!" end
	local g = {
		min = type(min) == "table" and min or {},
		max = type(max) == "table" and max or {},
		target = target,
		update = type(updateFunction) == "table" and updateFunction or {updateFunction},
		history = {},
		gradient = {},
		valueText = type(valueText) == "table" and valueText or {valueText},
		sharedBounds = shareBounds or false
	}
	for i=1, #g.update do
		g.history[i] = {}
	end
	if type(min) ~= "table" then
		local setMin = min or 0
		for i=1, #g.update do
			g.min[i] = setMin
		end
	end
	if type(max) ~= "table" then
		local setMax = max or 1
		for i=1, #g.update do
			g.max[i] = setMax
		end
	end
	if gradientTable and type(gradientTable) == "table" and type(gradientTable[1]) == "table" then
		g.gradient = gradientTable
	elseif gradientTable and type(gradientTable) == "table" then
		for i=1, #g.update do
			g.gradient[i] = gradientTable
		end
	else
		for i=1, #g.update do
			g.gradient[i] = {colors.red, colors.orange, colors.yellow, colors.lime, colors.green}
		end
	end
	setmetatable(g, gmetatable)
	return g
end