local getopt = require('getopt')
local lib14a = require('read14a')

copyright = 'Copyright (c) 2017 IceSQL AB. All rights reserved.'
author = "Christian Herrmann"
version = 'v1.0.3'
desc = [[
This script writes a empty template for 3D printing system onto a empty NTAG213 or MAGIC NTAG21*

Thanks to @jack for his invaluable input on some of the configuration.
]]
example =[[
     -- This will generate GOLD, PLA, TH, EU, 200m,  tagbased uid.
     script run ntag_3d -c 46 -m 50 -p 5448 -s 4555  -l 200
	 
     -- This will generate GOLD, PLA, TH, EU, 200m,  userbased uid.
     script run ntag_3d -u 11223344556677 -c 46 -m 50 -p 5448 -s 4555  -l 200
	 
	 -- This will generate GOLD, PLA, TH, EU, 200m,  userbased uid. and configure a MAGIC NTAG.
     script run ntag_3d -u 11223344556677 -c 46 -m 50 -p 5448 -s 4555  -l 200 -1	 
]]
usage = [[
script run calc_ntag_3d -h -t -u <uid> -c <color> -m <material> -p <region> -s <region> -l <length>

Arguments:
	-h             : this help
	-t             : selftest
	-u <UID>       : UID
	-c <COLOR>     : color of filament
	-m <MATERIAL>  : material of filament
	-p <REGION>    : Manufacturer region
	-s <REGION>    : Sales region
	-l <LENGTH>    : Spool length.  Use only 100,200,300.   300 has problems on OSX
]]

local DEBUG = true
local TIMEOUT = 10000 -- 10 seconds
local bxor = bit32.bxor
local band = bit32.band
local rshift = bit32.rshift

local _regions = {
	{'4742', 'GB'},
	{'5457', 'TW'},
	{'4555', 'EU'},
	{'5553', 'US'},
	{'454E', 'EN'},
	{'4A50', 'JP'},
	{'434E', 'CN'},
	{'5448', 'TH'},
	{'4153', 'AS'},
	{'5246', 'RF'},
	{'4746', 'GF'},
	{'4341', 'CA'},
	{'504D', 'PM'},
	{'5044', 'PD'},
}
local _manufacturers = {
	{'5457', 'TW'},
	{'434E', 'CN'},
	{'5448', 'TH'},
}
local _sales = {
	{'4742', 'GB'},
	{'4555', 'EU'},
	{'5553', 'US'},
	{'454E', 'EN'},
	{'504D', 'PM'},
}
local _materials = {
	{'20', 'Other material'},
	{'41', 'ABS'},
	{'46', 'Flexible TPE Tree'},
	{'46', 'TPE'},
	{'46', 'PVA'},
	{'47', 'PETG'},	
	{'50', 'PLA'},
	{'51', 'PLA'},
	{'54', 'Tough PLA'},
	{'55', 'UVCR'},
	{'56', 'Water Soluble PVA'},
}
local _colors = {
	{'30', 'Bronze'},
	{'31', 'Silver'},
	{'32', 'Clear Red'},
	{'33', 'Clear'},
	{'34', 'Bottle Green'},
	{'35', 'Neon Magenta'},
	{'36', 'SteelBlue'},
	{'37', 'Sun Orange'},
	{'38', 'Pearl White'},
	{'39', 'Copper'},
	{'41', 'Purple'},
	{'42', 'Blue'},
	{'43', 'Neon Tangerine'},
	{'44', 'Viridity'},
	{'45', 'Olivine'},
	{'46', 'Gold'},
	{'47', 'Green'},
	{'48', 'Neon Green'},
	{'49', 'Snow White'},
	{'4A', 'Neon Yellow'},
	{'4B', 'Black'},
	{'4C', 'Violet'},
	{'4D', 'Grape Purple'},
	{'4E', 'Purpurine'},
	{'4F', 'Clear Yellow'},
	{'50', 'Clear Green'},
	{'51', 'Clear Tangerine'},
	{'52', 'Red'},
	{'53', 'Cyber Yellow'},
	{'54', 'Tangerine'},
	{'55', 'Clear Blue'},
	{'56', 'Clear Purple'},
	{'57', 'White'},
	{'58', 'Clear Magenta'},
	{'59', 'Yellow'},
	{'5A', 'Nature'},
}
---
--
local function find( arr, name )
	if not name then return nil end
    name = name:lower()
    for k, v in pairs(arr) do
		if ( v[2]:lower() == name or v[1]:lower() == name ) then
			return v
		end
	end
	return nil
end
---
--
local function list( arr, desc )
	print ('Value\t'..desc)
	print (string.rep('=', 20))
    for k, v in pairs(arr) do
		print(("%s\t%s"):format(v[1],v[2]))
	end 
end
--- 
-- A debug printout-function
local function dbg(args)
	if not DEBUG then return end
	
	if type(args) == 'table' then
		local i = 1
		while result[i] do
			dbg(result[i])
			i = i+1
		end
	else
		print('###', args)
	end
end 
--- 
-- This is only meant to be used when errors occur
local function oops(err)
	print('ERROR: ',err)
	return nil,err
end
--- 
-- Usage help
local function help()
	print(copyright)
	print(version)	
	print(desc)
	print('Example usage')
	print(example)
end
--
-- Exit message
local function ExitMsg(msg)
	print( string.rep('--',20) )
	print( string.rep('--',20) )
	print(msg)
	print()
end
---
--
local function write_tag(uid, t)

	print('Writing to tag')
	core.console('hf mf dbg 0')
	
	local cmd = ''
	local pwd, pack = core.keygen_algo_d(uid)
	
	for i= 8, 23 do
		cmd = ('hf mfu wrbl b %02d d %s k %08X'):format(i, t[i], pwd)
		core.console(cmd)
	end
	
	--cfg1
	core.console(('hf mfu wrbl b 42 d %s k %08X'):format(t[42], pwd))
	--cfg0
	core.console(('hf mfu wrbl b 41 d %s k %08X'):format(t[41], pwd))
	--dynamic	
	core.console(('hf mfu wrbl b 40 d %s k %08X'):format(t[40], pwd))
	
	core.console('hf mf dbg 1')
	print('Done')	
end
---
-- configures a magic NTAG for NTAG213, with UID and PWD,PACK.
local function configure_magic_ntag(uid)

	print('Configuring MAGIC NTAG')
	-- Save the global args, those are *our* arguments
	local myargs = args
	
	local pwd, pack = core.keygen_algo_d(uid)
	
	-- Set the arguments for mfu_magic script v1.0.8
	-- -t 12   == configure NTAG213F
	-- -u    == set UID
	-- -p    == set pwd
	-- -a    == set pack
	args =('-t 12 -u %s -p %08X -a %04X'):format(uid, pwd, pack)
	require('../scripts/mfu_magic')
	
	-- Set back args. Not that it's used, just for the karma... 
	args = myargs
	
	print('Done')
end
---
-- generates random hex numbers between 31-39
local function random_num_hex(length)
	local str = ""
	local i
	for i = 1, length, 1 do
		str = str..math.random(31, 39)
	end
	return str
end
---
--
local function nwo( val )
	local b1 = band(val, 0xFF)
	local b2 = band( rshift(val,  8), 0xFF)
	local b3 = band( rshift(val, 16), 0xFF)
	local b4 = band( rshift(val, 24), 0xFF)
	return ('%02X%02X%02X%02X'):format(b1, b2, b3, b4)
end
---
-- NTAG213 template
local function template_NTAG213(uid, material, color, length, manufacture, sales)
	local pwd, pack = core.keygen_algo_d(uid)

	local m = tonumber(length, 10) * 1000
	local m_str = nwo(m)
	
	local t = {}
	-- default empty file
	for i = 0,42 do
		t[i] = '00000000'
	end
--	t[4]  = '0103A00C' --
--	t[5]  = '340300FE' -- 
-- 6,7
	t[8]  = '5A'..material..color..'00'  -- 5A, material, color, 00
	t[9]  = '00'..random_num_hex(3)  -- 00, three bytes serial number
	t[10] = m_str  -- total capacity
	t[11] = m_str  -- total capacity
	t[12] = 'D2002D00'  -- fixed
	t[13] = manufacture..sales  -- regioner, 
	t[14] = random_num_hex(4)  -- serial number
-- 15,16
	t[17] = '34000000' -- fixed
-- 18,19
	-- remaining capacity of spool
	t[20] = m_str
	t[21] = nwo( bxor(  m, 0x54321248))
	t[22] = nwo( bxor( (m - 3876923 ), 0x31275455))
	t[23] = nwo( bxor( (m + 6923923 ), 0x76235481))
-- 24-39	
	t[40] = '000000BD' --dynamic
	t[41] = '07000008' --cfg0
	t[42] = '80050000' --cfg1
	t[43] = ('%08X'):format(pwd)
	t[44] = ('%04X0000'):format(pack)
	return t
end
---
-- outputs the called arguments
local function print_conf(uid, material, color, length, producer, sales )
	print('Create tag as following')
	print( string.rep('--',16) )
	print('UID           ', uid)
	print('Material      ', material[2])
	print('Color         ', color[2])
	print('Spool length  ', length)
	print('Region')
	print('  manufacturer', producer[2])
	print('  sales       ', sales[2])
	print( string.rep('--',16) )	
end
---
-- self test
local function selftest()	
	list(_regions, 'Regions')
	list(_materials, 'Materials')
	list(_colors, 'Colors')
	return nil
end
--- 
-- The main entry point
local function main(args)

	math.randomseed(os.time());
	math.random();

	print( string.rep('--',20) )
	print( string.rep('--',20) )	
	print()
						
	local uid = '04C5DF4A6D5180'
	local useUID = false
	local useMAGIC = false
	local material, color, length, producer, sales
	
	if #args == 0 then return help() end

	-- Read the parameters
	for o, a in getopt.getopt(args, 'ht1u:l:m:c:p:s:') do
		if o == "h" then return help() end
		if o == "t" then return selftest() end
		if o == "u" then uid = a; useUID = true end
		if o == "m" then material = a end
		if o == "c" then color = a end
		if o == "l" then length = tonumber(a) end
		if o == "p" then producer = a end
		if o == "s" then sales = a end
		if o == "1" then useMAGIC = true end
	end

	color = find(_colors, color)
	if not color then list(_colors, 'Colors'); return oops('\n\nNot valid color') end
	
	material = find(_materials, material)
	if not material then list(_materials, 'Materials'); return oops('\n\nNot valid material') end
	
	producer = find(_manufacturers, producer)
	if not producer then list(_manufacturers, 'Regions Manufacturers'); return oops('\n\nNo valid manufacturer region') end

	sales = find(_sales, sales)
	if not sales then list(_sales, 'Regions Sales'); return oops('\n\nNo valid sales region') end
	
	if length > 300 then
		return oops('\n\nNot valid spool length. Must be lesser than 300')
	end
	
	if useUID then
		-- uid string checks
		if uid == nil then return oops('empty uid string') end
		if #uid == 0 then return oops('empty uid string') end
		if #uid ~= 14 then return oops('uid wrong length. Should be 7 hex bytes') end
	else
		-- GET TAG UID	
		local tag, err = lib14a.read(false, true)
		if not tag then return oops(err) end
		core.clearCommandBuffer()
		uid = tag.uid	
	end

	--print
	print_conf(uid, material, color, length, producer, sales )
	
	-- create template
	local t = template_NTAG213(uid, material[1], color[1], length, producer[1], sales[1])

	-- using MAGIC NTAG
	if useMAGIC then
		configure_magic_ntag(uid)
	end
	
	-- write template data to tag
	write_tag(uid, t)
end

main(args)