--[[ MONITOR PIN KEYPAD LOCK 
MADE BY JIAN AKA GOOCHGUY
NO WATERMARKS, IF YOU USE
MY PROGRAM, PLEASE LET 
THE PEOPLE WHO SEE IT 
KNOW WHERE YOU GOT IT.
DON'T TAKE CREDIT FOR MY WORK.]]--


term.clear()
term.setCursorPos(1,1)

local sound = false -- toggle sound (noteblock detected on initilization)
local m -- monitor
local n -- noteblock

local data = {
outputSide = "back" ,
invert = false , -- Whether to invert the redstone output 
openTime = 5 , -- Time (in seconds) to keep the door open
gridColor = colors.white , -- Color of the grid (Set to same as bgColor for 'no' grid)
gridBgColor = colors.black , -- bg color of the grid. (usually same color as bgColor)
numColor = colors.lightBlue , -- Color of Keys
bgColor = colors.black , -- Background Color
inputColor = colors.red , -- Input screen color
yVal = 1 , --optionally set the vertical position of the keypad
censor = true --censor the pad input
}



local disp = {}
local obj = {}
local curInput = {}

--function checklist

local mainLoop
local renderKeys 
local getInput 
local logInput
local compareInput 
local grantAccess 
local denyAccess
local door

-- SHA256 API

local sha256
local digestblock
local initH256 
local preproc
local s232num
local num2s
local str2hexa
local rrotate
local lshift
local rshift
local rshift1
local bnot
local band
local bxor
local make_bitop
local make_bitop_uncached
local memoize


--pad labels	
local _p = {
{ " 1 " , " 2 " , " 3 " } ,
{ " 4 " , " 5 " , " 6 " } ,
{ " 7 " , " 8 " , " 9 " } ,
{ "COR" , "CLR" , "ENT" }
}

local _pZero = {
{ " 1 " , " 2 " , " 3 " } ,
{ " 4 " , " 5 " , " 6 " } ,
{ " 7 " , " 8 " , " 9 " } ,
{ "COR" , " 0 " , "ENT" }
}

local function toFile( t , file )
	local str = textutils.serialize(t)
	if fs.exists(file) then fs.delete(file) end
	local f = fs.open(file,"w")
	f.write(str)
	f.close()
end

local function fromFile( file )
	if ( not fs.exists(file) ) then error("call invalid file") end
	local f = fs.open( file , "r" )
	local table = textutils.unserialize( f.readAll() )
	f.close()
	return table
end


local function init()

--check for monitor
	for _,v in ipairs( rs.getSides() ) do
		if peripheral.getType( v ) == "monitor" then
			monSide = v
		end
	end
	if monSide == nil then
		error("ERR: NO MONITOR FOUND!")
	else
		m = peripheral.wrap( monSide )
		print( "Monitor found on " .. monSide .. " side." )
	end
	
--check for iron noteblock

	for _,v in ipairs( rs.getSides() ) do
		if peripheral.getType( v ) == "note" then
			print("Iron noteblock found, sound enabled!")
			sound = true
			n = peripheral.wrap( v )
		end
	end

--setup
	if ( not fs.isDir("pin") ) then fs.makeDir("pin") end
	if fs.exists("pin/data") then 
		data = fromFile( "pin/data" )
	else
		repeat 
			print("Select Code? (1-5 digit number) ")
			local input = read()
			if #input > 5 then print("PIN TOO LONG.")
			elseif type( string.find( input , "0" ) ) == "number" then print("CANT CONTAIN 0's")
			elseif tonumber(input) == nil then print("NOT A NUMBER")
			else data.pin = sha256(input)
			end
		until ( #input <= 5 ) and ( tonumber( input ) ~= nil ) and ( type( string.find( input , "0" ) ) ~= "number" )
	end	
	if data.scale == nil then changeScale() end
	mainLoop()

end


 --change the data.scale of the keypad (autoscaling coming soon)
function changeScale()
	local newScale = 0
	repeat 
		print("Input New Scale Value ( 0.5 - 5 )")
		local input = read()
		if ( tonumber(input) == nil ) or ( tonumber(input) < 0.5 ) or ( tonumber(input) > 5 ) then 
			print("invalid number")
		else 
			data.scale = tonumber(input)
			newScale = 1
		end 
	until newScale == 1
	toFile( data , "pin/data")
end

function changeColors()
	tried = false
	local new = {}
	local input = ""

	repeat
		term.clear()
		term.setCursorPos( 1 , 1 )
		print("Possible choices are:" .. 
		"\nwhite , orange , magenta , lightBlue , yellow , lime , pink , gray, " ..
		"lightGray , cyan , purple , blue , brown , green , red , black" ..
		"Case sensitive, no spaces." 
		)

		if tried == true then 
			term.setTextColor(colors.red)
			print("\nOne of your colors was invalid, please try again.") 
			term.setTextColor(colors.white)
		end


		print("\nEnter a desired grid color: ")
		input = read()
		new.gridColor = colors[input]

		print("Enter a desired grid background color: ")
		input = read()
		new.gridBgColor = colors[input]

		print("Enter a desired background color: ")
		input = read()
		new.bgColor = colors[input]

		print("Enter a desired button label color: ")
		input = read()
		new.numColor = colors[input]

		print("Enter a desired display text color: ")
		local input = read()
		new.inputColor = colors[input]

		
		function checkValidColors(t) 
			local valid = true
			for k,v in pairs(new) do
				if not v then valid = false end
			end
			return valid
		end

		tried = true
		
	until checkValidColors() == true

	for k,v in pairs(new) do
		data[k] = new[k]
	end

	new = nil

	toFile( data , "pin/data")
end

function changePin()
	local newPin = ""
	repeat 
		term.clear()
		print("Enter the desired pin")
		print("Select Code? (1-5 digit number) ")
		local input = read()
		if #input > 5 then print("PIN TOO LONG.")
		elseif type( string.find( input , "0" ) ) == "number" then print("CANT CONTAIN 0's")
		elseif tonumber(input) == nil then print("NOT A NUMBER")
		else data.pin = sha256(input)
		end
	until ( #input <= 5 ) and ( tonumber( input ) ~= nil ) and ( type( string.find( input , "0" ) ) ~= "number" )
	toFile( data , "pin/data")
end


--create a string containing the numbers already inputted and spaces to fill


function getCurrentInput()
	local curStr = " "
		for k,v in ipairs(curInput) do
			curStr = curStr .. v .. " "
		end
		if #curInput%2 ~= 0 then 
			local curStr = curStr .. " "
		end
		curStr = curStr .. string.rep( " " , 11 - #curStr )
	return curStr
end

--render the keypad to the monitor

function renderKeys()
	m.setBackgroundColor(data.bgColor)
	m.clear()
	local w , h = m.getSize()
	local _x1 =  math.floor(w/2) - 5 
		if type(data.yVal) == "number" then 
			_y1 = data.yVal
		else 
			_y1 = math.floor(h/2) - 5
		end
	m.setCursorPos(_x1 , _y1)
	m.setTextColor( data.gridColor )
	m.setBackgroundColor( data.gridBgColor )
	m.write("+---+---+---+")
	local _y = _y1+1
	m.setCursorPos(	_x1 , _y )
	m.write("|")
	m.setBackgroundColor( data.bgColor )
	m.setTextColor( data.inputColor )
	disp.x , disp.y = m.getCursorPos()
	if data.censor == true then 
		local censorStr = string.rep( " *" , #curInput )
				if #censorStr < 11 then
					repeat
						censorStr = censorStr .. " "
					until #censorStr == 11
				end
		m.write( censorStr )
	else
		m.write( getCurrentInput() )
	end
	m.setBackgroundColor( data.gridBgColor ) 
	m.setTextColor( data.gridColor )
	m.write("|")
	local _y = _y+1
	m.setCursorPos( _x1 , _y )
	m.write("+---+---+---+")
	local _y = _y+1		
		for k,v in ipairs( _p ) do
			m.setCursorPos( _x1 , _y )
			m.setTextColor( data.gridColor )
			m.setBackgroundColor( data.gridBgColor )
			m.write("|")
				for k2,v2 in ipairs(_p[k]) do
					_num = ( k-1 )*3 + k2
					obj[_num] = {}
					obj[_num].label = v2
					obj[_num].x1 , obj[_num].y = m.getCursorPos()
					obj[_num].x2 = ( obj[_num].x1 ) + 2
					if v2 == "COR" then 
						m.setTextColor( colors.white ) 
						m.setBackgroundColor( colors.orange )					
					elseif v2 == "ENT" then 
						m.setTextColor( colors.white )
						m.setBackgroundColor( colors.green )
					elseif v2 == "CLR" then
						m.setTextColor( colors.white )
						m.setBackgroundColor( colors.red )
					else 
						m.setTextColor( data.numColor )
						m.setBackgroundColor( data.bgColor ) 
					end
					m.write( v2 )
					m.setBackgroundColor( data.gridBgColor )
					m.setTextColor( data.gridColor )
					m.write( "|" )
				end
			_y = _y + 1
			m.setCursorPos( _x1 , _y )
			m.write( "+---+---+---+" )
			_y = _y + 1
		end	
end

--deny/grant access

function denyAccess()
	m.setCursorPos( disp.x , disp.y )
	m.setBackgroundColor( colors.red )
	m.write("  DENIED   ")
	m.setBackgroundColor( data.bgColor )
	m.setTextColor( data.numColor )
	if sound then	
		n.playNote( 4 , 23 )
		sleep(0.15)
		n.playNote( 4 , 20 )
	end
	sleep(2)
	mainLoop()
end

function grantAccess()
	m.setCursorPos( disp.x , disp.y )
	m.setBackgroundColor( colors.green )
	m.write("  GRANTED  ")
	m.setBackgroundColor( data.bgColor )
	m.setTextColor( data.numColor )
	if sound then	
		n.playNote( 4 , 20 )
		sleep(0.15)
		n.playNote( 4 , 23 )
	end
	if data.invert == true then redstone.setOutput( data.outputSide , false ) elseif data.invert == false then redstone.setOutput( data.outputSide , true ) end
	sleep( data.openTime )
	if data.invert == true then redstone.setOutput( data.outputSide , true ) elseif data.invert == false then redstone.setOutput( data.outputSide , false ) end
	curInput = {}
	mainLoop()
end

--check if the current input is correct

function compareInput()
	local inputStr = ""
		for k,v in ipairs( curInput ) do
			inputStr = inputStr .. v
		end
	inputKey = sha256(inputStr)
	--print( "PIN: " .. data.pin )  
	--print( "INPUT: " .. inputStr )
	curInput = {}
	if data.pin == inputKey then 
		grantAccess() 
	else 
		denyAccess()
	end
end
--record monitor touch

function logInput( ... )
	for k,v in ipairs( arg ) do
		print( k .. " " .. v )  
	end
end

function getInput()
	local e = { os.pullEvent() }
	if e[1] == "monitor_touch" and e[2] == monSide then
		local x , y = e[3] , e[4]
			for k,v in ipairs(obj) do
				if ( x >= v.x1 ) and ( x <= v.x2 ) and ( y == v.y ) then
					if v.label == "CLR" then
						curInput = {}
						if sound then n.playNote( 3 , 1 ) end
					elseif v.label == "ENT" then 
						compareInput()
					elseif v.label == "COR" then 
						table.remove( curInput )
						if sound then n.playNote( 3 , 5 ) end
					elseif ( #curInput < 5 ) then
						table.insert( curInput , tostring(k) )
						if sound then n.playNote( 3 , 12 ) end
					end
				end
			end
	elseif e[1] == "char" then
		if e[2] == "s" then 
			changeScale()
		elseif e[2] == "c" then
			changeColors()
		elseif e[2] == "p" then
			changePin()
		end
	end
end

function mainLoop()
	while true do
		m.clear()
		m.setTextScale(data.scale)
		renderKeys()
			term.clear()
			term.setCursorPos( 1 , 1 )
			print("S - Scale")
			print("C - Colors")
			print("P - Pin")
		getInput()
	end
end


--  
--  Adaptation of the Secure Hashing Algorithm (SHA-244/256)
--  Found Here: http://lua-users.org/wiki/SecureHashAlgorithm
--  
--  Using an adapted version of the bit library
--  Found Here: https://bitbucket.org/Boolsheet/bslf/src/1ee664885805/bit.lua
--  

local MOD = 2^32
local MODM = MOD-1

function memoize(f)
	local mt = {}
	local t = setmetatable({}, mt)
	function mt:__index(k)
		local v = f(k)
		t[k] = v
		return v
	end
	return t
end

function make_bitop_uncached(t, m)
	local function bitop(a, b)
		local res,p = 0,1
		while a ~= 0 and b ~= 0 do
			local am, bm = a % m, b % m
			res = res + t[am][bm] * p
			a = (a - am) / m
			b = (b - bm) / m
			p = p*m
		end
		res = res + (a + b) * p
		return res
	end
	return bitop
end

function make_bitop(t)
	local op1 = make_bitop_uncached(t,2^1)
	local op2 = memoize(function(a) return memoize(function(b) return op1(a, b) end) end)
	return make_bitop_uncached(op2, 2 ^ (t.n or 1))
end

local bxor1 = make_bitop({[0] = {[0] = 0,[1] = 1}, [1] = {[0] = 1, [1] = 0}, n = 4})

function bxor(a, b, c, ...)
	local z = nil
	if b then
		a = a % MOD
		b = b % MOD
		z = bxor1(a, b)
		if c then z = bxor(z, c, ...) end
		return z
	elseif a then return a % MOD
	else return 0 end
end

function band(a, b, c, ...)
	local z
	if b then
		a = a % MOD
		b = b % MOD
		z = ((a + b) - bxor1(a,b)) / 2
		if c then z = bit32_band(z, c, ...) end
		return z
	elseif a then return a % MOD
	else return MODM end
end

function bnot(x) return (-1 - x) % MOD end

function rshift1(a, disp)
	if disp < 0 then return lshift(a,-disp) end
	return math.floor(a % 2 ^ 32 / 2 ^ disp)
end

function rshift(x, disp)
	if disp > 31 or disp < -31 then return 0 end
	return rshift1(x % MOD, disp)
end

function lshift(a, disp)
	if disp < 0 then return rshift(a,-disp) end 
	return (a * 2 ^ disp) % 2 ^ 32
end

function rrotate(x, disp)
    x = x % MOD
    disp = disp % 32
    local low = band(x, 2 ^ disp - 1)
    return rshift(x, disp) + lshift(low, 32 - disp)
end

local k = {
	0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
	0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
	0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
	0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
	0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
	0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
	0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
	0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
	0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
	0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
	0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
	0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
	0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
	0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
	0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
	0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
}

function str2hexa(s)
	return (string.gsub(s, ".", function(c) return string.format("%02x", string.byte(c)) end))
end

function num2s(l, n)
	local s = ""
	for i = 1, n do
		local rem = l % 256
		s = string.char(rem) .. s
		l = (l - rem) / 256
	end
	return s
end

function s232num(s, i)
	local n = 0
	for i = i, i + 3 do n = n*256 + string.byte(s, i) end
	return n
end

function preproc(msg, len)
	local extra = 64 - ((len + 9) % 64)
	len = num2s(8 * len, 8)
	msg = msg .. "\128" .. string.rep("\0", extra) .. len
	assert(#msg % 64 == 0)
	return msg
end

function initH256(H)
	H[1] = 0x6a09e667
	H[2] = 0xbb67ae85
	H[3] = 0x3c6ef372
	H[4] = 0xa54ff53a
	H[5] = 0x510e527f
	H[6] = 0x9b05688c
	H[7] = 0x1f83d9ab
	H[8] = 0x5be0cd19
	return H
end

function digestblock(msg, i, H)
	local w = {}
	for j = 1, 16 do w[j] = s232num(msg, i + (j - 1)*4) end
	for j = 17, 64 do
		local v = w[j - 15]
		local s0 = bxor(rrotate(v, 7), rrotate(v, 18), rshift(v, 3))
		v = w[j - 2]
		w[j] = w[j - 16] + s0 + w[j - 7] + bxor(rrotate(v, 17), rrotate(v, 19), rshift(v, 10))
	end

	local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
	for i = 1, 64 do
		local s0 = bxor(rrotate(a, 2), rrotate(a, 13), rrotate(a, 22))
		local maj = bxor(band(a, b), band(a, c), band(b, c))
		local t2 = s0 + maj
		local s1 = bxor(rrotate(e, 6), rrotate(e, 11), rrotate(e, 25))
		local ch = bxor (band(e, f), band(bnot(e), g))
		local t1 = h + s1 + ch + k[i] + w[i]
		h, g, f, e, d, c, b, a = g, f, e, d + t1, c, b, a, t1 + t2
	end

	H[1] = band(H[1] + a)
	H[2] = band(H[2] + b)
	H[3] = band(H[3] + c)
	H[4] = band(H[4] + d)
	H[5] = band(H[5] + e)
	H[6] = band(H[6] + f)
	H[7] = band(H[7] + g)
	H[8] = band(H[8] + h)
end

function sha256(msg)
	msg = preproc(msg, #msg)
	local H = initH256({})
	for i = 1, #msg, 64 do digestblock(msg, i, H) end
	return str2hexa(num2s(H[1], 4) .. num2s(H[2], 4) .. num2s(H[3], 4) .. num2s(H[4], 4) ..
		num2s(H[5], 4) .. num2s(H[6], 4) .. num2s(H[7], 4) .. num2s(H[8], 4))
end

init()