-- Playdate CoreLibs: Object
-- Copyright (C) 2015 Panic, Inc.

-- loosely based on http://replayism.com/code/barebones-lua-class/

Object = {}
Object.__index = Object
Object.class = Object
Object.className = 'Object'

--override to initialize 
function Object:init(...) end


-- override to base a subclass on an already existing table (see CoreLibs: Sprites)
function Object.baseObject()
	return {}
end


local __NewClass = {}

function class(ClassName, properties, namespace)
    __NewClass.className = ClassName
    __NewClass.properties = properties
    __NewClass.namespace = namespace    
	return __NewClass
end


function __NewClass.extends(Parent)

	if type(Parent) == 'string' then
        Parent = _G[Parent]
    elseif Parent == nil then 
	    Parent = Object
	end
	local Child = __NewClass.properties or {}
	Child.__index = Child
	Child.class = Child
	Child.className = __NewClass.className
	Child.super = Parent
	
	-- lua doesn't walk the metatable chain for these, but we want them to inherit:
	Child.__gc 			= Parent.__gc
	Child.__newindex 	= Parent.__newindex
	Child.__mode 		= Parent.__mode
	Child.__tostring 	= Parent.__tostring
	Child.__len 		= Parent.__len
	Child.__unm 		= Parent.__unm
	Child.__add 		= Parent.__add
	Child.__sub 		= Parent.__sub
	Child.__mul 		= Parent.__mul
	Child.__div 		= Parent.__div
	Child.__mod 		= Parent.__mod
	Child.__pow 		= Parent.__pow
	Child.__concat 		= Parent.__concat
	Child.__eq 			= Parent.__eq
	Child.__lt 			= Parent.__lt
	Child.__le 			= Parent.__le
	
	local mt = {
		__index = Parent,
		__call = function(self, ...)
			local instance = Child.baseObject()
			setmetatable(instance, Child)
--			instance.__index = instance
-- 			instance.class = instance
			instance.super = Child
			Child.init(instance, ...)
			return instance
		end
		}
	
	setmetatable(Child, mt)

	if (__NewClass.namespace ~= nil) then
		__NewClass.namespace[__NewClass.className] = Child
	else
		_G[__NewClass.className] = Child
	end
	
	__NewClass.properties = nil
	__NewClass.className = nil
	__NewClass.namespace = nil
end

-- Returns a boolean value that indicates whether the receiver is an instance of Class or an instance of any class that Class inherits from
function Object:isa(Class)
	local isa = false
	local currentClass = self
  
	while (currentClass ~= nil) and (isa == false) do
		if (currentClass == Class) then
			isa = true
		else
			currentClass = currentClass.super
		end
	end
	return isa
end


-- function Object:tableDump([indent], [table])
-- debugging convenience function that prints all of the objects key/value pairs, and the key/value pairs for all superclasses
function Object:tableDump(indent, table)

	local function printFormattedKeyValue(indent, k, v)
		v = v or ''
		print(string.rep('  ', indent) .. k .. ': ' .. tostring(v))
	end

	table = table or self
	local indent = indent or 0
	local super = nil
	
	for k, v in pairs(table) do
		if k == '__index' or k == 'class' or k == 'isa' or k == 'init' or k == 'tableDump' or k == 'baseObject' or k == 'className' then
	    	-- do nothing
	    elseif k == 'super' then
			super = v
		elseif type(v) == 'table' then
			printFormattedKeyValue(indent, k)
			Object.tableDump(v, indent+1, v)
		else
			printFormattedKeyValue(indent, k, v)
		end
	end
	
	if super ~= nil and super.className ~= 'Object' then
		print()
		printFormattedKeyValue(indent, 'super (' .. super.className .. ')')
		super.tableDump(super, indent+1)
	end
end


-- printTable(...) is just like print but formats tables

local insert = table.insert
local sort   = table.sort
local concat = table.concat
local unpack = table.unpack
local function repeatString(s,t)
	local chars = {}
	for i=1,t do
		chars[i] = s
	end
	return concat(chars)
end

-- based on compareAnyTypes from http://lua-users.org/wiki/SortedIteration
local function sortAny(val1, val2)
    local type1,type2 = type(val1), type(val2)
    local num1,num2  = tonumber(val1), tonumber(val2)
    
    if num1 and num2 then
        return num1 < num2
    elseif type1 ~= type2 then
        return type1 < type2
    elseif type1 == 'string'  then
        return val1 < val2
    elseif type1 == 'boolean' then
        return val1
    else
        return tostring(val1) < tostring(val2)
    end
end

-- from https://www.lua.org/pil/19.3.html
local function pairsByKey(t, f)
	local a = {}
	for n in pairs(t) do insert(a, n) end
	sort(a, f or sortAny)
	local i = 0 -- iterator variable
	local iter = function() -- iterator function
		i = i + 1
		if a[i] == nil then return nil
		else return a[i], t[a[i]] end
	end
	return iter
end

local encounteredTables = nil -- {}
local function tableToString(o,path,t)
	path = path or '/'
	if encounteredTables[o] then
		return 'reference: '..encounteredTables[o]
	end
	encounteredTables[o] = path
	
	t = t or 1
	local lines = {'{'}
	local tabs = repeatString('\t', t)
	t += 1
	
	local line = #lines + 1
	for k,v in pairsByKey(o) do
		local ktype = type(k)
		local vtype = type(v)
		
		local key = ''
		if ktype ~= 'number' then
			key = '['..tostring(k)..'] = '
		end
		
		local value
		if vtype == 'table' then
			value = tableToString(v, path..k..'/', t)
		else
			value = tostring(v)
		end
		lines[line] = tabs..key..value..','
		
		line += 1
	end
	lines[line] = repeatString('\t', t-2)..'}'
	return concat(lines, '\n')
end

function printTable(...)
	encounteredTables = {}
	local args = {...}
	for i=1,#args do
		local a = args[i]
		if type(a) == 'table' then
			-- encounteredTables[a] = '/'..i
			args[i] = tableToString(a)
		end
	end
	print(unpack(args))
	encounteredTables = nil
end
