--[[
	Sigmar's Garden
	A recreation by Trollbreeder
]]

--[[
	Modified No-Sell MIT License

	Copyright © 2021 Trollbreeder
	(Real name withheld)

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal in
	the Software with minimal restrictions, including without limitation the rights
	to use, copy, modify, merge, publish, or distribute copies of the Software, and
	to permit persons to whom the Software is furnished to do so, subject to the
	following conditions:

	The above copyright notice and these permission notices shall be included in all
	copies or substantial portions of the Software.

	The original creator of the software must be credited in one or more visible ways, 
	including in this license file, the Software, and/or its code (if released publicly)

	The Software may NOT be sublicensed or sold without major change or utilisation
	such that the Software as a whole is no longer recognisable as the original.
	Using the Software as part of one greater whole is allowed, but selling the
	Software "as is" without major change is not. The Software will always and
	forever be free of charge.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
	SOFTWARE.
]]

local tArgs = {...}
if #tArgs == 0 then 
	tArgs[1] = 1
end

-- The board
local board
local selected = {0, 0, colors.lime} -- Selected X, Y, Color
local wins = 0
local alreadyWon = false -- No cheating

-- Set palette colors for inconvenient colors
term.setPaletteColor(colors.orange, 0xB56B4B) -- Slightly light brown 
term.setPaletteColor(colors.magenta, 0xABBAA3) -- Grayish
term.setPaletteColor(colors.lime, 0xB6A77E) -- Beige
term.setPaletteColor(colors.pink, 0x392F25) -- Dark brown
term.setPaletteColor(colors.cyan, 0x725F4E) -- Brown
term.setPaletteColor(colors.purple, 0xC4BCA5) -- Silvery beige

local dictionary = { -- meanings for all colors
	["0"] = "V", -- Vitae (life)
	["1"] = "c", -- copper
	["2"] = "l", -- lead
	["3"] = "A", -- Air
	["4"] = "g", -- gold
	["5"] = " ", -- empty
	["6"] = "s", -- silver
	["7"] = "!", -- Slag (counts as empty, but locks)
	["8"] = "S", -- Salt
	["9"] = "i", -- iron
	["a"] = "q", -- quicksilver
	["b"] = "W", -- Water
	["c"] = "t", -- tin
	["d"] = "E", -- Earth
	["e"] = "F", -- Fire
	["f"] = "M"  -- Mors (death)
}
local react = { -- A list of possible reactions (These can always be vice-versa'd!)
{"0", "f"}, -- Vitae and Mors
{"3", "3"}, -- Cardinal elements with themselves
{"b", "b"},
{"d", "d"},
{"e", "e"},
{"8", "8"}, -- Salt matches with itself...
{"8", "3"}, -- ...or a cardinal element.
{"8", "b"},
{"8", "d"},
{"8", "e"},
-- Todo: metals
}
local metal = { -- List of metals (Metal, Quicksilver/Empty, Name)
{"2", "a", "Lead"},
{"c", "a", "Tin"},
{"9", "a", "Iron"},
{"1", "a", "Copper"},
{"6", "a", "Silver"},
{"4", "5", "Gold"} -- The 5 is handled specially so you can remove gold by clicking on it
}
local metalIndex = 1
local boardid
-- Load the board from a data file in boards
-- Check readme.txt for the structure of board data
local function loadBoard(id)
	if boardid ~= id then alreadyWon = false end -- no farming a single board forever
	boardid = id
	id = fs.combine(fs.getDir(shell.getRunningProgram()), "boards/"..tostring(id or 1)) 
	if not id:find("%.") then id = id..".dat" end
	if not fs.exists(id) then print("You have found the secret board!\nContact the devs about this please!") os.sleep() os.pullEvent("key") id = fs.combine(fs.getDir(shell.getRunningProgram()), "boards/secret.dat") boardid = "secret" end
	local x = fs.open(id, "r") -- default: User/Scripts/boards/1.dat
	description = x.readLine()
	local f = x.readAll()
	board = paintutils.parseImage(f)
	metalIndex = 1
	selected = {0, 0, colors.lime}
	x.close()
end

local function isMetal(c)
	c = c or colors.lime
	for k, v in ipairs(metal) do
		if colors.toBlit(c) == v[1] then return true end
	end
	return false
end

-- NOTE TO EVERYONE
-- IT IS BOARD[Y][X]
-- NOT X Y!!! 
-- IT IS Y X!!!!!

-- Is this atom locked?
local function isLocked(x, y)
	if board[y] == nil then return false end
	local empties = 0
	local function isEmpty(x, y)
		if board[y] == nil then return true
		elseif (board[y][x] == 0) or (not board[y][x]) or (board[y][x] == colors.lime) then return true end
		return false
	end
	if isEmpty(x, y-1) then empties = empties + 1 end
	if isEmpty(x-1, y) then empties = empties + 1 end
	if isEmpty(x+1, y) then empties = empties + 1 end
	if isEmpty(x, y+1) then empties = empties + 1 end
	if empties < 2 then return true end
	if isMetal(board[y][x]) and (colors.toBlit(board[y][x]) ~= metal[metalIndex][1]) then return true end 
	return false
end

-- Draw every square on the board
local function drawBoard()
	term.setBackgroundColor(colors.lime)
	term.clear()
	term.setCursorPos(1,1)
	print(description)
	local empty = true
	for k, v in ipairs(board) do
		for l, w in ipairs(v) do
			if w == 0 or w == nil or w == colors.lime then
				term.blit(" ", "5", "5")
			elseif w == colors.gray then -- who cares
				term.blit("!", "7", "7")
			elseif isLocked(l, k) then
				empty = false
				term.blit(dictionary[colors.toBlit(w)], "0", "7")
			elseif (l == selected[1]) and (k == selected[2]) and (w == colors.white) then
				empty = false
				term.blit("V", "0", "f")
			elseif (w == colors.white) then
				empty = false
				term.blit("V", "f", "0")
			elseif (l == selected[1]) and (k == selected[2]) then
				empty = false
				term.blit(dictionary[colors.toBlit(w)], colors.toBlit(w), "0")
			else
				empty = false
				term.blit(dictionary[colors.toBlit(w)], "0", colors.toBlit(w))
			end
		end
		print()
	end
	print("\nMetal: "..metal[metalIndex][3].."\nWins: "..wins.."\nPress SHIFT to retry.\nPress SPACEBAR for a new game.\nPress ENTER to exit.")
	-- We win!
	if empty then print("You win!"); if not alreadyWon then wins = wins + 1; alreadyWon = true; drawBoard() end return end
end
if tArgs[1] == "?" then -- Load random board
	local a = 0
	for i = 1, math.huge do
		if not fs.exists(fs.combine(fs.getDir(shell.getRunningProgram()), "boards/"..i..".dat")) then a = i-1 break end
	end
	loadBoard(math.random(1, a))
else -- Load first argument board or first one
	loadBoard(tArgs[1] or 1)
end

drawBoard()
-- Game logic
while true do
	local e = {os.pullEvent()}
	if e[1] == "mouse_click" then
		e[4] = e[4] - 1
		if (board[e[4]] ~= nil) and (board[e[4]][e[3]] ~= nil) and not ((e[3] == selected[1]) and (e[4] == selected[2])) then
			tempCol = board[e[4]][e[3]] -- Store color of this cell
			-- Handle reactions
			for k, v in ipairs(react) do 
				if (colors.toBlit(tempCol) == v[2] and colors.toBlit(selected[3]) == v[1]) 
				or (colors.toBlit(tempCol) == v[1] and colors.toBlit(selected[3]) == v[2]) then
					if not (isLocked(e[3], e[4]) or isLocked(selected[1], selected[2])) then 
						-- set to empty
						board[e[4]][e[3]] = colors.lime
						board[selected[2]][selected[1]] = colors.lime
						selected[1] = e[3]
						selected[2] = e[4]
						selected[3] = colors.lime
						tempCol = colors.lime
					end
				end
			end
			if (colors.toBlit(tempCol) == metal[metalIndex][1] and (colors.toBlit(selected[3]) == metal[metalIndex][2] or metal[metalIndex][2] == "5"))
			or ((colors.toBlit(tempCol) == metal[metalIndex][2] or metal[metalIndex][2] == "5") and colors.toBlit(selected[3]) == metal[metalIndex][1]) then 
				if not (isLocked(e[3], e[4]) or isLocked(selected[1], selected[2])) then
					-- set to empty
					board[e[4]][e[3]] = colors.lime
					if metal[metalIndex][2] ~= "5" then board[selected[2]][selected[1]] = colors.lime; selected[3] = colors.lime end
					selected[1] = e[3]
					selected[2] = e[4]
					
					tempCol = colors.lime
					metalIndex = (metalIndex) % (#metal) + 1
				end
			end
			selected[1] = e[3]
			selected[2] = e[4]
			selected[3] = tempCol
			drawBoard()
		end
	elseif e[1] == "key" then
		-- exit
		if e[2] == keys.enter then 
			term.setBackgroundColor(colors.black) term.clear() term.setCursorPos(1,1)
			print("Thanks for playing!\nCheck out Opus Magnum for the original Sigmar's Garden.") break
		-- new game
		elseif e[2] == keys.space then 
			if tArgs[1] == "?" then -- Random board
				local a = 0
				for i = 1, math.huge do
					if not fs.exists(fs.combine(fs.getDir(shell.getRunningProgram()), "boards/"..i..".dat")) then a = i-1 break end
				end
				loadBoard(math.random(1, a))
			else -- Next board
				if fs.exists(fs.combine(fs.getDir(shell.getRunningProgram()), "boards/"..((tonumber(boardid) or 0)+1)..".dat")) then
					loadBoard((tonumber(boardid) or 0)+1)
				else -- a winner is you
					term.setBackgroundColor(colors.black) term.clear() term.setCursorPos(1,1)
					print("Congratulations!\nYou have completed all of the boards!\nYou are the true Gardener of Sigmar!!")
					os.sleep()
					break
				end
			end
			drawBoard()
		-- reload
		elseif e[2] == keys.leftShift or e[2] == keys.rightShift then 
			loadBoard(boardid)
			drawBoard()
		end
	end
end
-- Reset palette
for i = 0, 15 do
	term.setPaletteColor(2^i, term.nativePaletteColor(2^i))
end