=begin
	Ruby X11 Client Library
	Copyright 2001 by Mathieu Bouchard

	Base.rb: Integers; Geometry; Colors.

	$Id: Base.rb,v 1.44 2001/07/07 21:27:24 matju Exp $

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License as published by the Free Software Foundation; either
	version 2 of the License, or (at your option) any later version.

	This library is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
	Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

=end

require "X11/Type"

module X11 # the whole file
#----------------------------------------------------------------#

add :Int8,    IntType.new -2** 7...2** 7, "c", 1
add :Int16,   IntType.new -2**15...2**15, "s", 2
add :Int32,   IntType.new -2**31...2**31, "l", 4
add :Uint1,   IntType.new      0...2** 1, "C", 1
add :Uint8,   IntType.new      0...2** 8, "C", 1
add :Uint16,  IntType.new      0...2**16, "S", 2
add :Uint16BE,IntType.new      0...2**16, "n", 2
add :Uint32,  IntType.new      0...2**32, "L", 4
add :Uint32BE,IntType.new      0...2**32, "N", 2
add :Angle,   IntType.new      0...23040, "S", 2

Button    = Uint8
KeyCode   = Uint8
Keysym    = Uint16
BitMask   = Uint32
TimeStamp = Uint32
Colornum  = Uint32

add :Uint8_but_255,  IntType.new 0  ...255, "C", 1
add :Uint8_only_255, IntType.new 255...256, "C", 1

#----------------------------------------------------------------#
# X11 Geometric types

module Vector2
	def + a; type.new x+a.x, y+a.y; end
	def - a; type.new x-a.x, y-a.y; end
	def * a; type.new x*a  , y*a  ; end
	def / a; type.new x/a  , y/a  ; end
end

# coordinates on an orthogonal 2d lattice.
# within the limits of X11 16-bit signed integer.
class Point < Tuple; include Vector2; fields_are \
	[:x, Int16],
	[:y, Int16]

	# custom fast xwrite
	def self.xwrite(output,object); output.write object.tuple.pack("ss"); end
end

# absolute difference between two points;
# similarly, a point + a size = another point.
# within the limits of X11 -> 16-bit unsigned integer.
# i renamed width,height to x,y.
class Size < Tuple; include Vector2; fields_are \
	[:x, Uint16],
	[:y, Uint16]

	# custom fast xwrite
	def self.xwrite(output,object); output.write object.tuple.pack("SS"); end
end

Dimension = Size # 0.4 compat

# a point P and a size S as defined above, describing a rectangle
# such that points (0 <= x-Px < Sx, 0 <= y-Py < Sy) are inside.
class Rectangle < Tuple; fields_are \
	[:point,Point],
	[:size, Size]

	# custom fast xwrite
	def self.xwrite(output,object)
		output.write((
			object.tuple[0].tuple +
			object.tuple[1].tuple
		).pack("ssSS"))
	end

	def dimension; size; end # 0.4 compat
end

# two points P, Q such that those points are inside:
# ((1-u)*Px + u*Qx, (1-u)*Py + u*Qy), for 0 <= u <= 1
class Segment < Tuple; fields_are \
	[:start, Point],
	[:finish,Point]
end

# a pieslice whose radii are aligned with the coordinate axis, such that
# its points are both in oval (1/2+(x-Px)/Sx)**2 + (1/2+(y-Py)/Sy)**2 = 1
# and in angle a1<=atan2(y-Py-Sy/2,x-Px-Sx/2)<=a2 (well i think so)
# angles are the interval 0...2*pi mapped onto the Angle range
class Arc < Tuple; fields_are \
	[:point,  Point],
	[:size,   Size],
	[:angle1, Angle],
	[:angle2, Angle]
end

#----------------------------------------------------------------#
# accepts formats:
# (r,g,b) ("#rgb") ("#rrggbb") ("#rrrgggbbb") ("#rrrrggggbbbb")

class RGB < Tuple; fields_are \
	[:red,   Uint16],
	[:green, Uint16],
	[:blue,  Uint16]

	Hex = "[0-9A-Fa-f]"

	ColorRE = (1..4).map {|i|
		Regexp.new("^\#" + ("(#{Hex}{#{i}})") * 3 + "$")
	}

	def initialize(code,*args)
		return super if args.length>0
		m =
			ColorRE[1].match(code) ||
			ColorRE[0].match(code) ||
			ColorRE[3].match(code) ||
			ColorRE[2].match(code)
		if not m then raise "Invalid color code: #{code}" end
		rgb = m[1].hex,m[2].hex,m[3].hex
		case m[1].length
		when 2; rgb.map! {|x| x * 0x101 }
		when 1; rgb.map! {|x| x * 0x1111 }
		when 4; # rien
		when 3; rgb.map! {|x| x * 0xffff / 0xfff }
		end
		super(*rgb)
	end

	# custom fast xwrite
	def self.xwrite(output,object); output.write object.tuple.pack("SSS"); end
end

#----------------------------------------------------------------#

module StringLikeList
	def ===(other)
		return true if String===other
		super
	end

	def xwrite(output,object)
		return super if not String===object
		output.write object
		output.pad unless @options == :unpadded
	end

	# keep this in sync with List#xread...
	def xread(input,count=nil)
		raise "count mismatch" if length and count==length
		count ||= length
		# count ||= chunk.length / l
		raise ItDependsError if not count
		object = input.read(count)
		input.pad unless @options == :unpadded
		object
	end
end

(String8 = List.of Uint8).extend StringLikeList
(String8Unpadded = List.of Uint8, nil, :unpadded).extend StringLikeList

String16 = List.of Uint16BE

#!@#$ does this work?
class Str < Tuple; fields_are \
	[:s, Uint8, :length],
	[:s, String8Unpadded, :data]

	def ===(obj)
		String===obj || super(obj)
	end

	#!@#$ gets overwritten by compiler.
	def self.xwrite(output,object)
		p "foo"
		object=Str[object] if String===object
		super(output,object)
	end

	#!@#$ ?
	def self.xread(input); STDERR.puts "foo"; super.s; end
end

class TimeCoord < Tuple; fields_are \
	[:time, TimeStamp],
	[:point, Point]
end

class XImage
	#!@#$
	# this is a padded list of bytes as used in get_image, put_image
end

#----------------------------------------------------------------#

def self.choice     (*args);      ChoiceType[*args]; end
def self.multichoice(*args); MultiChoiceType[*args]; end

# choices for: generic

add :None,   choice(:None)
add :Bool,   choice(false, true)
add :CurrentTime, choice(:CurrentTime)
add :TimeOrNow, Any.of TimeStamp, CurrentTime

# choices for: misc 1

add :ServerHandshake, choice(:Failed,:Success,:Authenticate)
add :MotionNotifyDetail, choice(:Normal,:Hint)
add :SameScreenAndFocus, multichoice(:Focus,:SameScreen)

add :DoColor, multichoice(:do_red, :do_green, :do_blue)
DoColor.int_type = Uint8

add :AnyPropertyType, choice(:AnyPropertyType)
add :ChangeType, choice(:Insert,:Delete)

add :AnyModifier, choice(:AnyModifier)
AnyModifier.base = 0x8000

add :AllTemporary, choice(:AllTemporary)

# choices for: graphics

add :GCFunction, choice(
	:Clear, :And, :AndReverse, :Copy,
	:AndInverted, :NoOp, :Xor, :Or,
	:Nor, :Equiv, :Invert, :OrReverse,
	:CopyInverted, :OrInverted, :Nand, :Set)

add :GCLineStyle, choice(:Solid, :OnOffDash, :DoubleDash)
add :GCCapStyle,  choice(:NotLast, :Butt, :Round, :Projecting)
add :GCJoinStyle, choice(:Miter, :Round, :Bevel)
add :GCFillStyle, choice(:Solid, :Tiled, :Stippled, :OpaqueStippled)
add :GCFillRule,  choice(:EvenOdd, :Winding)
add :GCArcMode,   choice(:Chord, :PieSlice)
add :GCSubwindowMode, choice(:ClipByChildren, :IncludeInferiors)

add :CoordinateMode, choice(:Origin, :Previous)
add :PolyShape, choice(:Complex, :Nonconvex, :Convex)

# choices for: windows

add :VisualClass, choice(
	:StaticGray, :GrayScale, :StaticColor, 
	:PseudoColor, :TrueColor, :DirectColor)

add :BitGravity, choice(:Forget, :Static,
	:NorthWest, :North, :NorthEast,
	:West, :Center, :East,
	:SouthWest, :South, :SouthEast)

add :WinGravity, choice(:Unmap,*(BitGravity.choices[1..-1]))

# choices for: events

add :EventDestination, choice(:PointerWindow,:InputFocus)

add :EventMask, multichoice(
	:KeyPress,    :KeyRelease,
	:ButtonPress, :ButtonRelease,
	:EnterWindow, :LeaveWindow,
	:PointerMotion, :PointerMotionHint,
	:Button1Motion, :Button2Motion, :Button3Motion,
	:Button4Motion, :Button5Motion, :ButtonMotion,
	:KeymapState,
	:Exposure, :VisibilityChange,
	:StructureNotify, :ResizeRedirect,
	:SubstructureNotify, :SubstructureRedirect,
	:FocusChange, :PropertyChange, :ColormapChange,
	:OwnerGrabButton)

add :PointerEventMask, choice(nil, nil,
	:ButtonPress, :ButtonRelease, 
	:EnterWindow, :LeaveWindow,
	:PointerMotion, :PointerMotionHint,
	:Button1Motion, :Button2Motion, :Button3Motion,
	:Button4Motion, :Button5Motion, :ButtonMotion,
	:KeymapState)

add :DeviceEventMask, choice(:KeyPress, :KeyRelease,
	:ButtonPress, :ButtonRelease,
	nil, nil,
	:PointerMotion, :PointerMotionHint,
	:Button1Motion, :Button2Motion, :Button3Motion,
	:Button4Motion, :Button5Motion, :ButtonMotion)

PointerEvent = PointerEventMask
DeviceEvent = DeviceEventMask

# choices for: key, mouse

add :KeyMask, multichoice(
	:Shift,:Lock,:Control,
	:Mod1,:Mod2,:Mod3,:Mod4,:Mod5)
KeyMask.int_type = Uint16

add :KeyButMask, multichoice(*(KeyMask.choices +
	[:Button1, :Button2, :Button3, :Button4, :Button5]))
KeyButMask.int_type = Uint16

# choices for: misc 2

add :Significance, choice(:LeastSignificant, :MostSignificant)
add :BackingStore, choice(:Never, :WhenMapped, :Always)

add :WindowClass, choice(:CopyFromParent, :InputOutput, :InputOnly)
WindowClass.int_type = Uint16

add :MapState, choice(:Unmapped, :Unviewable, :Viewable)
add :StackMode, choice(:Above, :Below, :TopIf, :BottomIf, :Opposite)
add :CirculateDirection, choice(:RaiseLowest, :LowerHighest)
add :ChangePropertyMode, choice(:Replace, :Prepend, :Append)

add :CrossingNotifyDetail, choice(
	:Ancestor, :Virtual, :Inferior, :Nonlinear, :NonlinearVirtual)

add :CrossingNotifyMode, choice(:Normal, :Grab, :Ungrab)

add :FocusDetail, choice(
	:Ancestor, :Virtual, :Inferior, :Nonlinear,
	:NonlinearVirtual, :Pointer, :PointerRoot, :None)

add :FocusMode, choice(:Normal, :Grab, :Ungrab, :WhileGrabbed)

add :VisibilityState, choice(:Unobscured, :PartiallyObscured,:FullyObscured)
add :CirculatePlace, choice(:Top, :Bottom)
add :PropertyNotifyState, choice(:NewValue, :Deleted)
add :ColormapNotifyState, choice(:Uninstalled, :Installed)
add :MappingNotifyRequest, choice(:Modifier, :Keyboard, :Pointer)
add :SyncMode, choice(:Synchronous, :Asynchronous)

add :GrabStatus, choice(
	:Success, :AlreadyGrabbed, :InvalidTime,
	:NotViewable, :Frozen)

add :AllowEventsMode, choice(
	:AsyncPointer, :SyncPointer, :ReplayPointer,
	:AsyncKeyboard, :SyncKeyboard,
	:ReplayKeyboard, :AsyncBoth, :SyncBoth)

add :InputFocusRevertTo, choice(:None, :PointerRoot, :Parent)
add :SetInputFocus, choice(:None, :PointerRoot)

add :DrawDirection, choice(:LeftToRight, :RightToLeft)

add :ClipRectangleOrdering, choice(
	:UnSorted, :YSorted, :YXSorted, :YXBanded)

add :ImageFormat, choice(:Bitmap, :XYPixmap, :ZPixmap)
add :SizeClass, choice(:Cursor, :Tile, :Stipple)
add :LedMode, choice(:Off, :On)
add :AutoRepeatMode, choice(:Off, :On, :Default)
add :ScreenSaver, choice(:No, :Yes, :Default)
add :HostChangeMode, choice(:Insert, :Delete)

#!@#$
add :HostFamily, choice(
	# 0
	:Internet, :DECnet, :Chaos,
	# 252
	:LocalHost, :Krb5Principal, :Netname,
	# 256
	:Local,
	# 65535
	:Wild)

add :AccessMode, choice(:Disabled, :Enabled)
add :CloseDownMode, choice(:Destroy, :RetainPermanent, :RetainTemporary)
add :ScreenSaverAction, choice(:Reset, :Activate)
add :MappingChangeStatus, choice(:Success, :Busy, :Failed)

add :BackgroundPixmapChoice, choice(:None, :ParentRelative)
add :CopyFromParent, choice(:CopyFromParent)

add :ColormapAlloc, choice(:None, :All)

add :AnyButton, choice(:AnyButton)

#----------------------------------------------------------------#
end # of module X11
