local _version = '2.12.0'

if tikz
then
	if tikz.version ~= _version
	then
		error( 'version chaos' )
	end
	return
end

tikz = { version = _version }

--==========================================================================--
-- DEFINES
--==========================================================================--

-- directions
tikz.north      = 'north'
tikz.north_east = 'north east'
tikz.east       = 'east'
tikz.south_east = 'south east'
tikz.south      = 'south'
tikz.south_west = 'south west'
tikz.west       = 'west'
tikz.north_west = 'north west'

-- colors
tikz.black     = 'black'
tikz.blue      = 'blue'
tikz.brown     = 'brown'
tikz.cyan      = 'cyan'
tikz.darkgray  = 'darkgray'
tikz.gray      = 'gray'
tikz.green     = 'green'
tikz.lightgray = 'lightgray'
tikz.lime      = 'lime'
tikz.magenta   = 'magenta'
tikz.olive     = 'olive'
tikz.orange    = 'orange'
tikz.pink      = 'pink'
tikz.purple    = 'purple'
tikz.red       = 'red'
tikz.teal      = 'teal'
tikz.violet    = 'violet'
tikz.yellow    = 'yellow'
tikz.white     = 'white'

-- align
tikz.center = 'center'
tikz.left   = 'left'
tikz.right  = 'right'

tikz.none   = 'none'

-- line caps
tikz.round = 'round'

--==========================================================================--
-- HELPERS --
--==========================================================================--

-- if defined writes output here
local outfile

--
-- Prints arguments to tex and to stdout.
--
local function tprint( ... )
	local args = {...}
	print( table.concat( args ) )

	if tex
	then
		tex.print( table.concat( args ) )
	end

	if outfile
	then
		outfile:write( table.concat( args ) )
		outfile:write( '\n%%% DO NOT EDIT THIS FILE %%%\n' )
	end
end
tikz.tprint = tprint

--
-- Flattens an object stack.
--
-- The content of all numerated unclassed objects
-- are put into a copy of obj.
--
local function flatten( o )
	local r = { }
	for _, v in ipairs( o )
	do
		if type( v ) == 'table' and getmetatable( v ) == nil
		then
			local fv = flatten( v )
			for _, fvv in ipairs( fv )
			do
				table.insert( r, fvv )
			end
			for k, vv in pairs( fv )
			do
				if type( k ) ~= 'number'
				then
					r[ k ] = vv
				end
			end
		else
			table.insert( r, v )
		end
	end
	-- copies (and overwrites) all named attributes
	for k, v in pairs( o )
	do
		if type( k ) ~= 'number'
		then
			r[ k ] = v
		end
	end
	return r
end

--
-- A key marking an object having a zString function.
--
local _zString = { }

--
-- Converts everything to a tikz string.
--
local function zString( v )
	if type( v ) == 'table' and v[ _zString ]
	then
		return v[ _zString ]( v )
	end
	return tostring( v )
end

--
-- Creates an immutable class.
--
local function immutable( definer )
	local def = { }
	local lazy = { }
	local proto = { }
	def.lazy = lazy
	def.proto = proto
	definer( def ) -- the callee fills in data
	local k = { } -- key the hidden table

	local mt = { }
	mt.__index =
		function( self, key )
			if key == 'id' then return def.id end

			-- first check if native value
			local v = self[ k ][ key ]
			if v then return v end

			-- then if a lazy function
			local f = lazy[ key ]
			if f
			then
				local v = f( self )
				self[ k ][ key ] = v -- cache
				return v
			end

			-- then if a proto value
			v = proto[ key ]
			if v
			then
				if type( v ) == 'function'
				then
					return(
						function(...)
							return v( self, table.unpack( {...} ) )
						end
					)
				else
					return v
				end
			end

			-- otherwise
			-- error( 'invalid property' )
		end

	mt.__newindex =
		function( )
			error( 'this is an immutable' )
		end

	if def.add
	then
		mt.__add = def.add
	end

	if def.concat
	then
		mt.__concat = def.concat
	end

	if def.div
	then
		mt.__div = def.div
	end

	if def.mul
	then
		mt.__mul = def.mul
	end

	if def.sub
	then
		mt.__sub = def.sub
	end

	if def.tostring
	then
		mt.__tostring = def.tostring
	end

	return function(...)
		local args = {...}
		local nt = def.constructor( table.unpack( args ) )
		local o = { [ k ] = nt, [ _zString ] = def.zString }
		setmetatable( o, mt )
		return o
	end
end
tikz.immutable = immutable

--
-- writes out direct options
--
local function _opts( s, _opts, haveOpts )
	if not _opts
	then
		return haveOpts
	end

	for k, v in pairs( _opts )
	do
		if not haveOpts
		then
			table.insert( s, '[' )
		else
			table.insert( s, ',' )
		end
		haveOpts = true
		if type( k ) == 'number'
		then
			table.insert( s, v )
		else
			table.insert( s, k..'='..v )
		end
	end
	return haveOpts
end

-------------------------------------------------------------------------------
-- Point.
-------------------------------------------------------------------------------
tikz.p =
immutable( function( def )
	def.id = 'point'

	def.add =
	function( left, right )
		if right.id == 'line'
		or right.id == 'bezier3'
		then
			return right + left
		end

		return tikz.p{ left.x + right.x, left.y + right.y }
	end

	def.concat =
	function( left, right )
		return zString( left )..zString( right )
	end

	-- constructor
	def.constructor =
	function( args )
		return { x = args[ 1 ], y = args[ 2 ] }
	end

	-- divisor
	def.div =
	function( left, right )
		return tikz.p{ left.x / right, left.y / right }
	end

	def.mul =
	function( left, right )
		if type( right ) == 'number'
		then
			return tikz.p{ left.x * right, left.y * right }
		elseif type( left ) == 'number'
		then
			return tikz.p{ left * right.x, left * right.y }
		elseif type( right ) == 'table'
		then
			if right.id == 'point'
			then
				return tikz.p{ left.x * right.x, left.y * right.y }
			end
		end
		error( 'invalid operation' )
	end

	def.sub =
	function( left, right )
		return tikz.p{ left.x - right.x, left.y - right.y }
	end

	def.tostring =
	function( self )
		return 'p{ '..self.x..', '..self.y..' }'
	end

	-- converts to a tikz string.
	def.zString =
	function( self )
		return '('..self.x..','..self.y..')'
	end
end )

-------------------------------------------------------------------------------
-- A full circle.
-------------------------------------------------------------------------------
tikz.circle =
immutable( function( def )
	def.id = 'circle'

	-- constructor
	def.constructor =
	function( args )
		return {
			at     = args.at,
			radius = args.radius,
		}
	end

	-- converts to a tikz string.
	def.zString =
	function( self )
		return zString( self.at )..' circle ('.. self.radius .. ')'
	end
end )

-------------------------------------------------------------------------------
-- A quadratic bezier curve.
-------------------------------------------------------------------------------
tikz.bezier2 =
immutable( function( def )
	def.id = 'bezier2'

	-- constructor
	def.constructor =
	function( args )
		local p1 = args.p1
		local pc = args.pc
		local p2 = args.p2

		return {
			p1 = p1,
			pc = pc,
			p2 = p2,
		}
	end

	-- draws helping information
	def.proto.drawHelpers =
	function( self, prefix )
		tikz.draw{ draw = 'red',  tikz.line{ self.p1, self.pc } }
		tikz.draw{ draw = 'blue', tikz.line{ self.pc, self.p2 } }
		if prefix ~= nil
		then
			tikz.put{ tikz.node{
				at = self.p1,
				anchor = 'north west',
				text = prefix .. '1',
			} }
			tikz.put{ tikz.node{
				at = self.pc,
				anchor = 'north west',
				text = prefix .. 'c',
			} }
			tikz.put{ tikz.node{
				at = self.p2,
				anchor = 'north west',
				text = prefix .. '2',
			} }
		end
	end

	-- converts to a tikz string.
	def.zString =
	function( self )
		local s = { }
		table.insert( s, zString( self.p1 ) )
		table.insert( s, '.. controls' )
		table.insert( s, zString( self.pc ) )
		table.insert( s, '..' )
		table.insert( s, zString( self.p2 ) )
		return table.concat( s, ' ' )
	end
end )

-------------------------------------------------------------------------------
-- A cubic bezier curve.
-------------------------------------------------------------------------------
tikz.bezier3 =
immutable( function( def )
	def.id = 'bezier3'

	def.add =
	function( left, right )
		if right.id == 'point'
		then
			return tikz.bezier3{
				p1  = left.p1  + right,
				pc1 = left.pc1 + right,
				pc2 = left.pc2 + right,
				p2  = left.p2  + right,
			}
		end
		error( 'unknown operation' )
	end

	-- constructor
	def.constructor =
	function( args )
		local p1  = args.p1
		local pc1 = args.pc1
		local pc2 = args.pc2
		local p2  = args.p2

		return {
			p1  = p1,
			pc1 = pc1,
			pc2 = pc2,
			p2  = p2,
		}
	end

	-- draws helping information
	def.proto.drawHelpers =
	function( self, prefix )
		tikz.draw{ draw = 'red',    tikz.line{ self.p1,  self.pc1 } }
		tikz.draw{ draw = 'yellow', tikz.line{ self.pc1, self.pc2 } }
		tikz.draw{ draw = 'blue',   tikz.line{ self.pc2, self.p2  } }
		if prefix ~= nil
		then
			tikz.put{ tikz.node{
				at = self.p1,
				anchor = 'north west',
				text = prefix .. '1',
			} }
			tikz.put{ tikz.node{
				at = self.pc1,
				anchor = 'north west',
				text = prefix .. 'c1',
			} }
			tikz.put{ tikz.node{
				at = self.pc2,
				anchor = 'north west',
				text = prefix .. 'c2',
			} }
			tikz.put{ tikz.node{
				at = self.p2,
				anchor = 'north west',
				text = prefix .. '2',
			} }
		end
	end

	def.mul =
	function( left, right )
		if type( right ) == 'number'
		then
			return tikz.bezier3 {
				p1  = left.p1  * right,
				pc1 = left.pc1 * right,
				pc2 = left.pc2 * right,
				p2  = left.p2  * right,
			}
		elseif type( left ) == 'number'
		then
			return tikz.bezier3 {
				p1  = right.p1  * left,
				pc1 = right.pc1 * left,
				pc2 = right.pc2 * left,
				p2  = right.p2  * left,
			}
		else
			error( 'invalid operation' )
		end
	end

	-- point in center
	def.lazy.pc =
	function( self )
		return self.pt( 0.5 )
	end

	-- gets angle at t (0-1)
	def.proto.phit =
	function( self, t )
		local p1  = self.p1
		local pc1 = self.pc1
		local pc2 = self.pc2
		local p2  = self.p2

		local u   = 1 - t
		local uu3 = 3 * u * u
		local ut6 = 6 * u * t
		local tt3 = 3 * t * t

		return math.atan2(
			-uu3 * p1.y + uu3 * pc1.y - ut6 * pc1.y + ut6 * pc2.y - tt3 * pc2.y + tt3 * p2.y,
			-uu3 * p1.x + uu3 * pc1.x - ut6 * pc1.x + ut6 * pc2.x - tt3 * pc2.x + tt3 * p2.x
		)
	end

	-- reverts p1 and p2 and pc1 and pc2 respectively
	def.lazy.revert =
	function( self )
		return tikz.bezier3{
			p1  = self.p2,
			pc1 = self.pc2,
			pc2 = self.pc1,
			p2  = self.p1,
		}
	end

	-- gets point at t (0-1)
	def.proto.pt =
	function( self, t )
		if type( t ) ~= 'number' or t < 0 or t > 1
		then
			error( 'invalid pt' )
		end

		local p1 = self.p1
		local pc1 = self.pc1
		local pc2 = self.pc2
		local p2 = self.p2
		local t1 = 1 - t

		return(
			tikz.p{
				t1^3*p1.x + 3*t*t1^2*pc1.x + 3 *t^2*t1*pc2.x + t^3*p2.x,
				t1^3*p1.y + 3*t*t1^2*pc1.y + 3 *t^2*t1*pc2.y + t^3*p2.y
			}
		)
	end

	def.sub =
	function( left, right )
		if right.id == 'point'
		then
			return tikz.bezier3{
				p1  = left.p1  - right,
				pc1 = left.pc1 - right,
				pc2 = left.pc2 - right,
				p2  = left.p2  - right,
			}
		end
		error( 'unknown operation' )
	end

	-- converts to a tikz string.
	def.zString =
	function( self )
		local s = { }
		table.insert( s, zString( self.p1 ) )
		table.insert( s, '.. controls' )
		table.insert( s, zString( self.pc1 ) )
		table.insert( s, 'and' )
		table.insert( s, zString( self.pc2 ) )
		table.insert( s, '..' )
		table.insert( s, zString( self.p2 ) )
		return table.concat( s, ' ' )
	end
end )

-------------------------------------------------------------------------------
-- A bend line
-------------------------------------------------------------------------------
tikz.bline =
immutable( function( def )
	def.id = 'bline'

	-- moves the line.
	def.add =
	function( left, right )
		if right.id == 'point'
		then
			return tikz.line{ p1 = left.p1 + right, p2 = left.p2 + right }
		end
		error( 'unknown operation' )
	end

	-- constructor
	--
	-- either
	--   { p1, p2 },            (a list )
	--   { p1 = ..., p2 = ... },
	--   { pc = ..., phi = ..., length = ... },
	def.constructor =
	function( args )
		local p1
		local p2

		if #args > 1
		then
			-- list
			p1 = args[ 1 ]
			p2 = args[ 2 ]
		elseif type( args.p1 ) ~= 'nil' and type( args.p2 ) ~= 'nil'
		then
			p1 = args.p1
			p2 = args.p2
		elseif args.phi ~= nil
		then
			local phi = args.phi
			if args.pc ~= nil
			then
				if args.p1 ~= nil
				or args.p2 ~= nil
				then
					error( 'invalid line options', 2 )
				end
				local pc    = args.pc
				local len05 = args.length / 2
				p1 = pc + tikz.p{ math.cos( phi ), math.sin( phi ) } * len05
				p2 = pc - tikz.p{ math.cos( phi ), math.sin( phi ) } * len05
			elseif type( args.p1 ) ~= 'nil'
			then
				if args.pc ~= nil
				or args.p2 ~= nil
				then
					error( 'invalid line options', 2 )
				end
				p1 = args.p1
				p2 = p1 + tikz.p{ math.cos( phi ), math.sin( phi ) } * args.length
			else
				error( 'invalid line options', 2 )
			end
		else
			error( 'invalid line options', 2 )
		end

		return {
			bend_left  = args.bend_left,
			bend_right = args.bend_right,
			p1         = p1,
			p2         = p2,
		}
	end

	-- converts to a cubic bezier (that should look the same)
	def.lazy.bezier3 =
	function( self )
		local phi
		if self.bend_right ~= nil then
			phi = self.bend_right * math.pi / 180
		else
			phi = -self.bend_left * math.pi / 180
		end
		local line = self.line
		local phil = line.phi
		local len = line.length * 0.3915
		local lt1 =
			tikz.line{
				p1  = line.p1,
				phi = phil-phi,
				length = len
			}
		local lt2 =
			tikz.line{
				p1 = line.p2,
				phi = phil+phi+math.pi,
				length = len
			}

		return tikz.bezier3{
			p1  = line.p1,
			pc1 = lt1.p2,
			pc2 = lt2.p2,
			p2  = line.p2,
		}
	end

	-- converts to a straight line.
	def.lazy.line =
	function( self )
		return tikz.line{
			p1 = self.p1,
			p2 = self.p2,
		}
	end

	-- point in center
	def.lazy.pc =
	function( self )
		return self.bezier3.pc
	end

	-- gets point at t (0-1).
	def.proto.pt =
	function( self, t )
		return self.bezier3.pt( t )
	end

	-- converts to a tikz string.
	def.zString =
	function( self )
		local s = { }
		table.insert( s, zString( self.p1 ) )
		table.insert( s, 'to' )
		if self.bend_left ~= nil
		then
			table.insert( s, '[bend left='..self.bend_left..']' )
		end
		if self.bend_right ~= nil
		then
			table.insert( s, '[bend right='..self.bend_right..']' )
		end
		table.insert( s, zString( self.p2 ) )
		return table.concat( s, ' ' )
	end
end )

-------------------------------------------------------------------------------
-- A curve is defined by a list of points.
-------------------------------------------------------------------------------
curve =
immutable( function( def )
	def.id = 'curve'

	-- constructor
	def.constructor =
	function( args )
		return {
			points  = args.points,
			tension = args.tension,
			cycle   = args.cycle,
		}
	end

	-- draws helping information
	def.proto.drawHelpers =
	function( self, prefix )
		local points = self.points
		for i, pi in ipairs( points )
		do
			tikz.draw{
				fill = 'black',
				tikz.circle{
					at = pi,
					radius = 0.05,
				}
			}
			if prefix ~= nil
			then
				tikz.put{ tikz.node{
					at = pi,
					anchor = 'north west',
					text = prefix .. i,
				} }
			end
		end
	end

	-- converts to a tikz string.
	def.zString =
	function( self )
		local s = { 'plot' }
		table.insert( s, '[' )
		if self.cycle
		then
			table.insert( s, 'smooth cycle' )
		else
			table.insert( s, 'smooth' )
		end
		if self.tension then
			table.insert( s, ',tension='..self.tension )
		end
		table.insert( s, ']' )
		local points = self.points
		table.insert( s, 'coordinates' )
		table.insert( s, '{' )
		for _, p in ipairs( points )
		do
			table.insert( s, zString( p ) )
		end
		table.insert( s, '}' )
		return table.concat( s, ' ' )
	end
end )

-------------------------------------------------------------------------------
-- A full ellipse.
-------------------------------------------------------------------------------
tikz.ellipse =
immutable( function( def )
	def.id = 'ellipse'

	-- constructor
	def.constructor =
	function( args )
		local at = args.at
		local xradius = args.xradius
		local yradius = args.yradius

		return {
			at = at,
			xradius = xradius,
			yradius = yradius,
		}
	end

	-- intersect with a line.
	def.proto.intersectLine =
	function( self, line )
		local at = self.at
		local rx = self.xradius
		local ry = self.yradius
		local p1 = line.p1
		local p2 = line.p2

		local x0 = p1.x
		local y0 = p1.y
		local x1 = p2.x
		local y1 = p2.y
		local cx = at.x
		local cy = at.y

		x0 = x0 - cx
		y0 = y0 - cy
		x1 = x1 - cx
		y1 = y1 - cy

		local A = ((x1 - x0) * (x1 - x0)) / rx / rx + ((y1 - y0) * (y1 - y0)) / ry / ry
		local B = (2 * x0 * (x1 - x0)) / rx / rx + (2 * y0 * (y1 - y0)) / ry / ry
		local C = (x0 * x0) / rx / rx + (y0 * y0) / ry / ry - 1
		local D = B * B - 4 * A * C

		local tv = { }
		if D == 0
		then
			table.insert( tv, -B / 2 / A )
		else
			table.insert( tv, (-B + math.sqrt(D)) / 2 / A )
			table.insert( tv, (-B - math.sqrt(D)) / 2 / A )
		end

		local r = { }
		for _, t in ipairs( tv )
		do
			if t >= 0 and t <= 1
			then
				table.insert(
					r,
					p1 + tikz.p{ t * line.length * math.cos( line.phi ), t * line.length * math.sin( line.phi ) }
				)
			end
		end

		if #r == 0
		then
			return
		elseif #r == 1
		then
			return r[ 1 ]
		else
			return r
		end
	end

	-- converts to a tikz string.
	def.zString =
	function( self )
		s = { }
		table.insert( s, zString( self.at ) )
		table.insert( s, 'ellipse' )
		table.insert( s, '[' )
		table.insert( s, 'x radius =' )
		table.insert( s, zString( self.xradius ) )
		table.insert( s, ', y radius =' )
		table.insert( s, zString( self.yradius ) )
		table.insert( s, ']' )
		return table.concat( s, ' ' )
	end
end )

-------------------------------------------------------------------------------
-- An ellipse part.
-------------------------------------------------------------------------------
tikz.ellipseArc =
immutable( function( def )
	def.id = 'ellipseArc'

	-- constructor
	def.constructor =
	function( args )
		return {
			at      = args.at,
			from    = args.from,
			to      = args.to,
			xradius = args.xradius,
			yradius = args.yradius,
		}
	end

	-- converts to a tikz string.
	def.zString =
	function( self )
		s = { }
		table.insert( s, 'plot' )
		table.insert( s, '[domain =' )
		table.insert( s, self.from )
		table.insert( s, ':' )
		table.insert( s, self.to )
		table.insert( s, ',variable = \\phi,smooth]' )
		table.insert( s,
			'({ '..zString(self.at.x)..'+'..zString(self.xradius)..'*cos(\\phi)},'
			..'{ '..zString(self.at.y)..'+'..zString(self.yradius)..'*sin(\\phi)})'
		)
		return table.concat( s, ' ' )
	end
end )

-------------------------------------------------------------------------------
-- Single line (segment).
-------------------------------------------------------------------------------
tikz.line =
immutable( function( def )
	def.id = 'line'

	-- moves the line.
	def.add =
	function( left, right )
		if right.id == 'point'
		then
			return tikz.line{
				p1 = left.p1 + right,
				p2 = left.p2 + right,
			}
		end
		error( 'unknown operation' )
	end

	-- multiplicator
	def.mul =
	function( left, right )
		if type( left ) == 'number'
		then
			return tikz.line{
				p1 = right.p1 * left,
				p2 = right.p2 * left,
			}
		elseif type( right ) == 'number'
		then
			return tikz.line{
				p1 = left.p1 * right,
				p2 = left.p2 * right,
			}
		end
		error( 'unknown operation' )
	end

	-- constructor
	--
	-- either
	--   { p1, p2 },            (a list )
	--   { p1 = ..., p2 = ... },
	--   { pc = ..., phi = ..., length = ... },
	def.constructor =
	function( args )
		local p1
		local p2

		if #args > 1
		then
			-- list
			p1 = args[ 1 ]
			p2 = args[ 2 ]
		elseif type( args.p1 ) ~= 'nil' and type( args.p2 ) ~= 'nil'
		then
			p1 = args.p1
			p2 = args.p2
		elseif args.phi ~= nil
		then
			local phi = args.phi
			if args.pc ~= nil
			then
				if args.p1 ~= nil
				or args.p2 ~= nil
				then
					error( 'invalid line options', 2 )
				end
				local pc    = args.pc
				local len05 = args.length / 2
				p1 = pc + tikz.p{ math.cos( phi ), math.sin( phi ) } * len05
				p2 = pc - tikz.p{ math.cos( phi ), math.sin( phi ) } * len05
			elseif type( args.p1 ) ~= 'nil'
			then
				if args.pc ~= nil
				or args.p2 ~= nil
				then
					error( 'invalid line options', 2 )
				end
				p1 = args.p1
				p2 = p1 + tikz.p{ math.cos( phi ), math.sin( phi ) } * args.length
			else
				error( 'invalid line options', 2 )
			end
		else
			error( 'invalid line options', 2 )
		end

		return {
			p1 = p1,
			p2 = p2,
		}
	end

	-- divisor
	def.div =
	function( left, right )
		if type( right ) == 'number'
		then
			return tikz.line{
				p1 = left.p1 / right,
				p2 = left.p2 / right,
			}
		end
		error( 'unknown operation' )
	end

	-- length of line.
	def.lazy.length =
	function( self )
		local p1 = self.p1
		local p2 = self.p2
		local dx = p2.x - p1.x
		local dy = p2.y - p1.y
		local result = math.sqrt( dx*dx + dy*dy )
		return result
	end

	-- center of line.
	def.lazy.pc =
	function( self )
		local p1 = self.p1
		local p2 = self.p2
		local result = tikz.p{ ( p1.x + p2.x ) / 2, ( p1.y + p2.y ) / 2 }
		return result
	end

	-- angle of line.
	def.lazy.phi =
	function( self )
		local p1 = self.p1
		local p2 = self.p2
		local result = math.atan2( p2.y - p1.y, p2.x - p1.x )
		return result
	end

	-- gets point at t (0-1).
	def.proto.pt =
	function( self, t )
		if type( t ) ~= 'number' or t < 0 or t > 1
		then
			error( 'invalid pt' )
		end

		local p1 = self.p1
		local p2 = self.p2
		local phi = self.phi
		local lt = self.length * t

		return(
			tikz.p{
				p1.x + lt * math.cos( phi ),
				p1.y + lt * math.sin( phi ),
			}
		)
	end

	-- intersects the line with another.
	def.proto.intersectLine =
	function( self, line )
		local p1 = self.p1
		local p2 = self.p2
		local p3 = line.p1
		local p4 = line.p2

		if p1.x == p3.x and p1.y == p3.y then return p1 end
		if p1.x == p4.x and p1.y == p4.y then return p1 end
		if p2.x == p3.x and p2.y == p3.y then return p2 end
		if p2.x == p4.x and p2.y == p4.y then return p2 end

		local den = ( p1.x - p2.x )*( p3.y - p4.y ) - ( p1.y - p2.y )*( p3.x - p4.x );
		if den == 0
		then
			-- doesn't intersect
			return
		end

		local a = p1.x*p2.y - p1.y*p2.x;
		local b = p3.x*p4.y - p3.y*p4.x;
		local x = ( a*( p3.x - p4.x ) - ( p1.x - p2.x )*b ) / den;
		local y = ( a*( p3.y - p4.y ) - ( p1.y - p2.y )*b ) / den;

		if
			(
				( x >= p1.x and x <= p2.x or x <= p1.x and x >= p2.x )
				and ( x >= p3.x and x <= p4.x or x <= p3.x and x >= p4.x )
			)
			or
			(
				( y >= p1.y and y <= p2.y or y <= p1.y and y >= p2.y )
				and ( y >= p3.y and y <= p4.y or y <= p3.y and y >= p4.y )
			)
		then
			return tikz.p{ x, y }
		end
	end

	-- converts to a tikz string.
	def.zString =
	function( self )
		local s = { }
		table.insert( s, zString( self.p1 ) )
		table.insert( s, '--' )
		table.insert( s, zString( self.p2 ) )
		return table.concat( s, ' ' )
	end
end )

-------------------------------------------------------------------------------
-- Function plot.
-------------------------------------------------------------------------------
tikz.plot =
immutable( function( def )
	def.id = 'plot'

	-- constructor
	def.constructor =
	function( args )
		return {
			at      = args.at,
			from    = args.from,
			to      = args.to,
			step    = args.step,
			tension = args.tension,
			func    = args.func,
		}
	end

	def.lazy.points =
	function( self )
		local from = self.from
		local to = self.to
		local step = self.step
		if step == nil
		then
			step = ( to - from ) / 100
		end
		local points = { }
		local d
		local at = self.at or tikz.p0
		for d = from, to, step
		do
			table.insert( points, at + self.func( d ) )
		end
		return points
	end

	-- converts to a tikz string.
	def.zString =
	function( self )
		local s = { 'plot' }
		table.insert( s, '[' )
		table.insert( s, 'smooth' )
		if self.tension
		then
			table.insert( s, ',tension='..self.tension )
		end
		table.insert( s, ']' )

		local points = self.points

		table.insert( s, 'coordinates' )
		table.insert( s, '{' )
		for _, p in ipairs( points )
		do
			table.insert( s, zString( p ) )
		end
		table.insert( s, '}' )
		return table.concat( s, ' ' )
	end
end )

-------------------------------------------------------------------------------
-- A superEllipse
-------------------------------------------------------------------------------
tikz.superEllipse =
immutable( function( def )
	def.id = 'superEllipse'

	-- constructor
	def.constructor =
	function( args )
		local at = args.at
		local n = args.n
		local xradius = args.xradius
		local yradius = args.yradius

		return {
			at = at,
			n = n,
			xradius = xradius,
			yradius = yradius,
		}
	end

	-- returns p on degree (0-360)
	def.proto.pdeg =
	function( self, t )
		if type( t ) ~= 'number' or t < 0 or t > 360
		then
			error( 'invalid pdeg' )
		end

		local at = self.at
		local n = self.n
		local xo = self.xradius * math.abs( math.cos( t * math.pi / 180 ) )^(2/n)
		local yo = self.yradius * math.abs( math.sin( t * math.pi / 180 ) )^(2/n)

		if t <= 90
		then
			return tikz.p{ at.x + xo, at.y + yo }
		elseif t <= 180
		then
			return tikz.p{ at.x - xo, at.y + yo }
		elseif t <= 270
		then
			return tikz.p{ at.x - xo, at.y - yo }
		else
			return tikz.p{ at.x + xo, at.y - yo }
		end
	end

	-- converts to a tikz string.
	def.zString =
	function( self )
		local s = { }
		local at = self.at
		local xa = at.x
		local ya = at.y
		local n = self.n
		local xr = self.xradius
		local yr = self.yradius
		table.insert( s, 'plot[domain=0:90,variable=\\t,smooth]' )
		table.insert( s, '({'..xr..'*cos(\\t)^(2/'..n..')+'..xa..'},{'..yr..'*sin(\\t)^(2/'..n..')+'..ya..'})' )
		table.insert( s, '--' )
		table.insert( s, 'plot[domain=90:0,variable=\\t,smooth]' )
		table.insert( s, '({-'..xr..'*cos(\\t)^(2/'..n..')+'..xa..'},{'..yr..'*sin(\\t)^(2/'..n..')+'..ya..'})' )
		table.insert( s, '--' )
		table.insert( s, 'plot[domain=0:90,variable=\\t,smooth]' )
		table.insert( s, '({-'..xr..'*cos(\\t)^(2/'..n..')+'..xa..'},{-'..yr..'*sin(\\t)^(2/'..n..')+'..ya..'})' )
		table.insert( s, '--' )
		table.insert( s, 'plot[domain=90:0,variable=\\t,smooth]' )
		table.insert( s, '({'..xr..'*cos(\\t)^(2/'..n..')+'..xa..'},{-'..yr..'*sin(\\t)^(2/'..n..')+'..ya..'})' )
		table.insert( s, '-- cycle' )
		return table.concat( s, ' ' )
	end
end )

-------------------------------------------------------------------------------
-- A reference to a named node.
-------------------------------------------------------------------------------
local optionsNamed =
{
	ref = false,
	xshift = 'xshift',
	yshift = 'yshift',
}

tikz.named =
immutable( function( def )
	def.id = 'named'

	-- constructor
	def.constructor =
	function( args )
		local s = { }
		_doOptions( s, args, optionsNamed, false, 2 )
		return {
			opts = table.concat( s, ' ' ),
			ref = args.ref,
		}
	end

	-- converts to a tikz string.
	def.zString =
	function( self )
		local s = { }
		table.insert( s, '(' )
		if self.opts
		then
			table.insert( s, self.opts )
		end
		table.insert( s, self.ref )
		table.insert( s, ')' )
		return table.concat( s )
	end
end )

-------------------------------------------------------------------------------
-- Multiple connected lines.
-------------------------------------------------------------------------------
tikz.polyline =
immutable( function( def )
	def.id = 'polyline'

	-- constructor
	def.constructor =
	function( args )
		local o = { }
		for _, v in ipairs( args )
		do
			table.insert( o, v )
		end
		return o
	end

	-- converts to a tikz string.
	def.zString =
	function( self )
		local s = { '' }
		for k, v in ipairs( self )
		do
			if k > 1 then table.insert( s, '--' ) end
			table.insert( s, zString( v ) )
		end
		return table.concat( s, ' ' )
	end
end )

-------------------------------------------------------------------------------
-- A rectangle
-------------------------------------------------------------------------------
tikz.rect =
immutable( function( def )
	def.id = 'rect'

	-- constructor
	def.constructor =
	function( args )
		local psw
		local pne

		if args.pnw ~= nil
		then
			if args.pse ~= nil
			then
				psw = tikz.p{ args.pnw.x, args.pse.y }
				pne = tikz.p{ args.pse.x, args.pnw.y }
			elseif args.size ~= nil
			then
				psw = tikz.p{ args.pnw.x, args.pnw.y - args.size.y }
				pne = tikz.p{ args.pnw.x + args.size.x, args.pnw.y }
			else
				error( 'invalid rect options', 2 )
			end
		elseif args.psw ~= nil
		then
			psw = args.psw
			if args.pne ~= nil
			then
				pne = args.pne
			elseif args.size ~= nil
			then
				pne = psw + args.size
			else
				error( 'invalid rect options', 2 )
			end
		elseif args.pse ~= nil
		then
			if args.size ~= nil
			then
				psw = tikz.p{ args.pse.x - args.size.x, args.pse.y }
				pne = tikz.p{ args.pse.x, args.pse.y + args.size.y }
			else
				error( 'invalid rect options', 2 )
			end
		elseif args.pc ~= nil
		then
			if args.size ~= nil
			then
				psw = tikz.p{ args.pc.x - args.size.x / 2, args.pc.y - args.size.y / 2 }
				pne = tikz.p{ args.pc.x + args.size.x / 2, args.pc.y + args.size.y / 2 }
			else
				error( 'invalid rect options', 2 )
			end
		else
			error( 'invalid rect options', 2 )
		end

		return {
			psw = psw,
			pne = pne,
		}
	end

	def.zString =
	function( self )
		local s = { }
		table.insert( s, zString( self.psw ) )
		table.insert( s, 'rectangle' )
		table.insert( s, zString( self.pne ) )
		return table.concat( s, ' ' )
	end

	def.lazy.e =
	function( self )
		return self.pne.x
	end

	def.lazy.height =
	function( self )
		return self.pne.y - self.psw.y
	end

	def.lazy.n =
	function( self )
		return self.pne.y
	end

	def.lazy.pc =
	function( self )
		return tikz.p{ self.psw.x + self.width / 2, self.psw.y + self.height / 2 }
	end

	def.lazy.pe =
	function( self )
		return tikz.p{ self.pne.x, self.psw.y + self.height / 2 }
	end

	def.lazy.pn =
	function( self )
		return tikz.p{ self.psw.x + self.width / 2, self.pne.y }
	end

	def.lazy.pnw =
	function( self )
		return tikz.p{ self.psw.x, self.pne.y }
	end

	def.lazy.ps =
	function( self )
		return tikz.p{ self.psw.x + self.width / 2, self.psw.y }
	end

	def.lazy.pse =
	function( self )
		return tikz.p{ self.pne.x, self.psw.y }
	end

	def.lazy.pw =
	function( self )
		return tikz.p{ self.psw.x, self.psw.y + self.height / 2 }
	end

	def.lazy.s =
	function( self )
		return self.psw.y
	end

	def.lazy.size =
	function( self )
		return tikz.p{ self.width, self.height }
	end

	def.lazy.w =
	function( self )
		return self.psw.x
	end

	def.lazy.width =
	function( self )
		return self.pne.x - self.psw.x
	end
end )


--==========================================================================--
-- STYLES --
--==========================================================================--
tikz.style =
immutable( function( def )
	def.id = 'style'

	-- constructor
	def.constructor =
	function( name )
		return { name=name }
	end
end )

tikz.above         = tikz.style( 'above' )
tikz.arrow         = tikz.style( '-{Latex}' )
tikz.below         = tikz.style( 'below' )
tikz.dashed        = tikz.style( 'dashed' )
tikz.dotted        = tikz.style( 'dotted' )
tikz.double_arrow  = tikz.style( '{Latex}-{Latex}' )
tikz.even_odd_rule = tikz.style( 'even odd rule' )
tikz.midway        = tikz.style( 'midway' )

--==========================================================================--
-- OUTPUT ---
--==========================================================================--

--
-- Writes out a named option.
--
--  ~s:        string to build
--  ~args:     arguments
--  ~name:     name of option
--  ~haveOpts: true if had options
--  ~rename:   if defined rename options on output
--
local function optionNamed( s, args, name, haveOpts, rename )

	local v = args[ name ]

	if not v
	then
		return haveOpts
	end

	if not rename
	then
		return haveOpts
	end

	if not haveOpts
	then
		table.insert( s, '[' )
	else
		table.insert( s, ',' )
	end

	if v == true
	then
		table.insert( s, rename )
	else
		table.insert( s, rename..'='..v )
	end

	return true
end

--
-- Writes out an unnamed option.
--
--  ~s:        string to build
--  ~name:     name of option
--  ~haveOpts: true if had options
--
local function unnamedOption( s, name, haveOpts )
	if not haveOpts
	then
		table.insert( s, '[' )
	else
		table.insert( s, ',' )
	end
	table.insert( s, name )
	return true
end

--
-- Custom key compare.
--
-- keys to put on top
local topKeys =
{
	node_distance = true,
}

local function kcompare( ka, kb )
	local ta = topKeys[ ka ]
	local tb = topKeys[ kb ]
	if ta and not tb then return true end
	if not ta and tb then return false end
	return ka < kb
end

--
-- A helper to parse arguments to pass as drawing options to hand to tikz.
--
--  s:        string to build
--  args:     the arguments to parse.
--  opts:     named options to pass on
--  haveOpts: true [ has already been built
--  level:    call level for error throwing
--
local function _doOptions( s, args, opts, haveOpts, level )
	args = flatten( args )

	for _, v in ipairs( args )
	do
		if v.id == 'style'
		then
			haveOpts = unnamedOption( s, v.name, haveOpts )
		end
	end

	local nkeys = { }
	for key in pairs( args )
	do
		if type( key ) ~= 'number'
		then
			table.insert( nkeys, key )
		end
	end
	table.sort( nkeys, kcompare )

	for _, k in ipairs( nkeys )
	do
		local ok = opts[ k ]
		if ok
		then
			haveOpts = optionNamed( s, args, k, haveOpts, ok )
		elseif ok == nil
		then
			error( 'unknown option '..k, level + 1 )
		end
	end

	haveOpts = _opts( s, args._opts, haveOpts )

	if haveOpts
	then
		table.insert( s, ']' )
	end
end

-------------------------------------------------------------------------------
-- Drawing shapes.
-------------------------------------------------------------------------------
local optionsDraw =
{
	decorate         = 'decorate',
	decoration       = 'decoration',
	dash_pattern     = 'dash pattern',
	color            = 'color',
	draw             = 'draw',
	fill             = 'fill',
	line_cap         = 'line cap',
	line_width       = 'line width',
	opacity          = 'opacity',
	pattern          = 'pattern',
	rotate           = 'rotate',
	transform_canvas = 'transform canvas',
	xshift           = 'xshift',
	yshift           = 'yshift',
	_butt_cap        = '-Butt Cap',
	butt_cap_        = 'Butt Cap-',
	_round_cap       = '-{Round Cap[]}',
	round_cap_       = '{Round Cap[]}-',
	_round_cap_      = '{Round Cap[]}-{Round Cap[]}',
}

--
-- Puts out a bounding box command
--
tikz.boundingbox =
function( args )
	tprint( '\\pgfresetboundingbox' )
	local s = { '\\path [use as bounding box] ' }
	for _, v in ipairs( args )
	do
		if v.id ~= 'style'
		then
			table.insert( s, zString( v ) )
		end
	end
	table.insert( s, ';' )
	tprint( table.unpack( s ) )
end

--
-- Puts out a draw command.
--
tikz.draw =
function( args )
	local s = { '\\draw ' }
	_doOptions( s, args, optionsDraw, false, 2 )

	for _, v in ipairs( args )
	do
		if v.id ~= 'style'
		then
			table.insert( s, zString( v ) )
		end
	end

	table.insert( s, ';' )
	tprint( table.unpack( s ) )
end

--
-- Puts out a path command.
--
tikz.path =
function( args )
	local s = { '\\path' }
	_doOptions( s, args, optionsDraw, false, 2 )

	for _, v in ipairs( args )
	do
		if v.id ~= 'style'
		then
			table.insert( s, zString( v ) )
		end
	end
	table.insert( s, ';' )
	tprint( table.unpack( s ) )
end

--
-- Puts out a bounding box.
--
tikz.clip =
function( args )
	local s = { '\\begin{pgfinterruptboundingbox}', '\\clip' }
	_doOptions( s, args, optionsDraw, false, 2 )

	for _, v in ipairs( args )
	do
		if v.id ~= 'style' then table.insert( s, zString( v ) ) end
	end
	table.insert( s, ';' )
	table.insert( s, '\\end{pgfinterruptboundingbox}' )
	tprint( table.unpack( s ) )
end

local shadeOptions =
{
	ball_color        = 'ball color',
	left_color        = 'left color',
	lower_left_color  = 'lower left',
	lower_right_color = 'lower right',
	opacity           = 'opacity',
	right_color       = 'right color',
	shading           = 'shading',
	upper_left_color  = 'upper left',
	upper_right_color = 'upper right',
}

--
-- Puts out a shade command.
--
tikz.shade =
function( args )
	local s = { '\\shade' }
	_doOptions( s, args, shadeOptions, false, 2 )

	for _, v in ipairs( args )
	do
		if v.id ~= 'style' then table.insert( s, zString( v ) ) end
	end
	table.insert( s, ';' )
	tprint( table.unpack( s ) )
end

--
-- Puts out z stuff.
--
tikz.put =
function( args )
	for _, v in ipairs( args )
	do
		local s = { }
		if v.id == 'node'
		then
			table.insert( s, '\\' )
			table.insert( s, zString( v ) )
			table.insert( s, ';' )
		else
			error( 'unknown: ' .. v.id, 1 )
		end
		tprint( table.unpack( s ) )
	end
end

tikz.declarehorizonalshading =
function( args )
	local steps = args.steps
	local name = args.name
	local s = { '\\pgfdeclarehorizontalshading{' .. name .. '}{100bp}{' }

	local keys = { }
	for k, _ in pairs( steps )
	do
		table.insert( keys, k )
	end

	table.sort( keys )
	for _, k in ipairs( keys )
	do
		if _ > 1 then table.insert( s, ';' ) end
		table.insert( s, 'color(' .. k .. 'bp)=(' .. steps[ k ] .. ')' )
	end
	table.insert( s, '}' )

	tprint( table.unpack( s ) )
end

-------------------------------------------------------------------------------
-- A node to be defined to tikZ
-------------------------------------------------------------------------------
local optionsNode =
{
	above          = 'above',
	anchor         = 'anchor',
	align          = 'align',
	at             = false,
	below          = 'below',
	color          = 'color',
	draw           = 'draw',
	left           = 'left',
	minimum_height = 'minimum height',
	node_distance  = 'node distance',
	name           = false,
	right          = 'right',
	rotate         = 'rotate',
	text           = false,
	text_width     = 'text width',
}

tikz.node =
immutable( function( def )
	def.id = 'node';

	def.zString =
		function( self )
			local s = { 'node' }

			table.insert( s, self.opts )
			if self.name
			then
				table.insert( s, ' (' )
				table.insert( s, self.name )
				table.insert( s, ')' )
			end

			if self.at
			then
				table.insert( s, ' at ' )
				table.insert( s, zString( self.at ) )
			end

			if self.text
			then
				table.insert( s, ' {' )
				local text = string.gsub( self.text, '\n', '' )
				table.insert( s, text )
				table.insert( s, '}' )
			end

			return table.concat( s )
		end

	-- constructor
	def.constructor =
		function( args )
			local s = { }
			_doOptions( s, args, optionsNode, false, 2 )
			return {
				at = args.at,
				name = args.name,
				opts = table.concat( s, ' ' ),
				text = args.text
			}
		end
end )

tikz.outfile =
function( filename )
	outfile = io.open( filename, 'w' )
	outfile:write( '%%% DO NOT EDIT THIS FILE %%%\n' )
end

--
-- Hacking help.
--
function _direct( args )
	tprint( table.unpack( args ) )
end

--
-- Checks version metch.
--
tikz.checkVersion =
function( v )
	if v ~= tikz.version
	then
		error( 'version mismatch', 1 )
	end
end

--
-- Creates a color.
--
tikz.colorRGB =
function( red, green, blue )
	return '{rgb,255:red,'..red..'; green,'..green..'; blue,'..blue..'}'
end

--
-- Makes a tikz scope.
-- Argument must be a function to call.
--
tikz.scope =
function( args )
	args = flatten( args )
	if #args ~= 1 or type( args[ 1 ] ) ~= 'function'
	then
		error( 'scope needs a function parameter' )
	end
	_direct{ [[\begin{scope}]] }
	args[ 1 ]( )
	_direct{ [[\end{scope}]] }
end

--==========================================================================--
-- SHORTCUTS
--==========================================================================--
tikz.p0 = tikz.p{ 0, 0 }


--==========================================================================--
-- Running code within tikz environment.
--==========================================================================--

local env_nesting = 0
local org_env
tikz.within =
function( version )
	if version ~= '*' and version ~= tikz.version
	then
		error( 'version mismatch', 1 )
	end

	env_nesting = env_nesting + 1
	if env_nesting > 1
	then
		return
	end

	org_env = _ENV
	local nenv = { }
	local mt = { }
	mt.__index =
		function( _, key )
			local zk = tikz[ key ]
			if zk ~= nil
			then
				return zk
			else
				return org_env[ key ]
			end
		end
	setmetatable( nenv, mt )
	local func = debug.getinfo( 2 ).func
	debug.setupvalue( func, 1, nenv )
end

tikz.without =
function( )
	env_nesting = env_nesting - 1
	if env_nesting > 0 then return end
	local func = debug.getinfo( 2 ).func
	debug.setupvalue( func, 1, org_env )
end
