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