module X11
  module Form
    # A form object is an X11 packet definition. We use forms to encode
    # and decode X11 packets as we send and receive them over a socket.
    #
    # We can create a packet definition as follows:
    #
    #   class Point < BaseForm
    #     field :x, Int8
    #     field :y, Int8
    #   end
    #
    #   p = Point.new(10,20)
    #   p.x => 10
    #   p.y => 20
    #   p.to_packet => "\n\x14"
    #
    # You can also read from a socket:
    #
    #   Point.from_packet(socket) => #<Point @x=10 @y=20>
    #
    class Form
      def self.structs
        []
      end

      def self.fields
        []
      end
    end

    class BaseForm < Form
      include X11::Type

      # initialize field accessors
      def initialize(*params)
        self.class.fields.each do |f|
          if !f.value
            param = params.shift
            #p [f,param]
            instance_variable_set("@#{f.name}", param)
          end
        end
      end

      def to_packet(dpy)
        # fetch class level instance variable holding defined fields
        structs = self.class.structs

        packet = structs.map do |s|
          # fetch value of field set in initialization

          value = s.type == :unused ? nil : instance_variable_get("@#{s.name}")
          case s.type
          when :field
            if s.value
              if s.value.respond_to?(:call)
                value = s.value.call(self)
              else
                value = s.value
              end
            end
            #p [s,value]

            if value.is_a?(BaseForm)
              v = value.to_packet(dpy)
            else
              #p [s,value]
              v = s.type_klass.pack(value, dpy)
            end
            #p v
            v
          when :unused
            sz = s.size.respond_to?(:call) ? s.size.call(self) : s.size
            "\x00" * sz
          when :length, :format_length
            #p [s,value]
            #p [value.size]
            s.type_klass.pack(value.size, dpy)
          when :string
            s.type_klass.pack(value, dpy)
          when :list
            Array(value).collect do |obj|
              if obj.is_a?(BaseForm)
                obj.to_packet(dpy)
              else
                s.type_klass.pack(obj, dpy)
              end
            end
          end
        end.join
      end

      class << self
        def structs
          superclass.structs + Array(@structs) #instance_variable_get("@structs"))
        end

        # FIXME: Doing small reads from socket is a bad idea, and
        # the protocol provides length fields that makes it unnecessary.
        def from_packet(socket)
          # fetch class level instance variable holding defined fields

          form = new
          lengths = {}

          structs.each do |s|
            case s.type
            when :field
              val = if s.type_klass.superclass == BaseForm
                s.type_klass.from_packet(socket)
              else
                s.type_klass.unpack( socket.read(s.type_klass.size) )
              end
              form.instance_variable_set("@#{s.name}", val)
            when :unused
              sz = s.size.respond_to?(:call) ? s.size.call(self) : s.size
              socket.read(sz)
            when :length
              size = s.type_klass.unpack( socket.read(s.type_klass.size) )
              lengths[s.name] = size
            when :format_length
              size = s.type_klass.unpack( socket.read(s.type_klass.size) )
              lengths[s.name] = case form.format
                                when 8 then size
                                when 16 then size*2
                                when 32 then size*4
                                else 0
                                end
            when :string
              len = lengths[s.name]
              val = s.type_klass.unpack(socket, len)
              form.instance_variable_set("@#{s.name}", val)
            when :list
              len = lengths[s.name]
              if len
                val = len.times.collect do
                  s.type_klass.from_packet(socket)
                end
              else
                val = []
                while ob = s.type_klass.from_packet(socket)
                  val << ob
                end
              end
              form.instance_variable_set("@#{s.name}", val)
            end
          end

          return form
        end

        Field = Struct.new(:name, :type, :type_klass, :value, :size, keyword_init: true)
        
        def field(name, type_klass, type = nil, value: nil)
          # name, type_klass, type = args
          class_eval do
            if value && value.respond_to?(:call)
              define_method(name.to_sym) { value.call(self) }
            else
              attr_accessor name
            end
          end

          s = Field.new
          s.name = name
          s.type = (type == nil ? :field : type)
          s.type_klass = type_klass
          s.value = value

          @structs ||= []
          @structs << s
        end

        def unused(size)
          @structs ||= []
          @structs << Field.new(size: size, type: :unused)
        end

        def fields
          super+Array(@structs).dup.delete_if{|s| s.type == :unused or s.type == :length or s.type == :format_length}
        end
      end
    end

    # # Predefined constants, that can be used in the form of symbols
    
    module Atoms
      PRIMARY   = 1
      SECONDARY = 2
      ARC = 3
      ATOM = 4
      BITMAP = 5
      CARDINAL = 6
      COLORMAP = 7
      CURSOR = 8
      #...
      STRING = 31
      VISUALID = 32
      WINDOW = 33
      WM_COMMAND = 34
      WM_HINTS = 35
    end

    PointerWindow = 0
    InputFocus = 1
    
    # FIXME: Deprecated in favour of the Constants module
    AtomAtom=4
    CardinalAtom=6
    WindowAtom=33
    
    ##
    ## X11 Packet Defintions
    ##

    class ClientHandshake < BaseForm
      field :byte_order, Uint8
      unused 1
      field :protocol_major_version, Uint16
      field :protocol_minor_version, Uint16
      field :auth_proto_name, Uint16, :length
      field :auth_proto_data, Uint16, :length
      unused 2
      field :auth_proto_name, String8, :string
      field :auth_proto_data, String8, :string
    end

    class FormatInfo < BaseForm
      field :depth, Uint8
      field :bits_per_pixel, Uint8
      field :scanline_pad, Uint8
      unused 5
    end

    class VisualInfo < BaseForm
      field :visual_id, VisualID
      field :qlass, Uint8
      field :bits_per_rgb_value, Uint8
      field :colormap_entries, Uint16
      field :red_mask,  Uint32
      field :green_mask, Uint32
      field :blue_mask, Uint32
      unused 4
    end

    class DepthInfo < BaseForm
      field :depth, Uint8
      unused 1
      field :visuals, Uint16, :length
      unused 4
      field :visuals, VisualInfo, :list
    end

    class ScreenInfo < BaseForm
      field :root, Window
      field :default_colormap, Colormap
      field :white_pixel, Colornum
      field :black_pixel, Colornum
      field :current_input_masks, Mask
      field :width_in_pixels, Uint16
      field :height_in_pixels, Uint16
      field :width_in_millimeters, Uint16
      field :height_in_millimeters, Uint16
      field :min_installed_maps, Uint16
      field :max_installed_maps, Uint16
      field :root_visual, VisualID
      field :backing_stores, Uint8
      field :save_unders, Bool
      field :root_depth, Uint8
      field :depths, Uint8,:length
      field :depths, DepthInfo, :list
    end

    class DisplayInfo < BaseForm
      field :release_number,     Uint32
      field :resource_id_base,   Uint32
      field :resource_id_mask,   Uint32
      field :motion_buffer_size, Uint32
      field :vendor, Uint16, :length
      field :maximum_request_length, Uint16
      field :screens, Uint8, :length
      field :formats, Uint8, :length
      field :image_byte_order, Signifigance
      field :bitmap_bit_order, Signifigance
      field :bitmap_format_scanline_unit, Uint8
      field :bitmap_format_scanline_pad,  Uint8
      field :min_keycode, KeyCode
      field :max_keycode, KeyCode
      unused 4
      field :vendor,  String8,    :string
      field :formats, FormatInfo, :list
      field :screens, ScreenInfo, :list
    end

    class Rectangle < BaseForm
      field :x, Int16
      field :y, Int16
      field :width, Uint16
      field :height, Uint16
    end

    class Error < BaseForm
      field :error, Uint8
      field :code,  Uint8
      field :sequence_number, Uint16
      field :bad_resource_id, Uint32
      field :minor_opcode, Uint16
      field :major_opcode, Uint8
      unused 21

      # The original request
      attr_accessor :request
    end

    # XRender structures

    class DirectFormat < BaseForm
      field :red,        Uint16
      field :red_mask,   Uint16
      field :green,      Uint16
      field :green_mask, Uint16
      field :blue,       Uint16
      field :blue_mask,  Uint16
      field :alpha,      Uint16
      field :alpha_mask, Uint16
    end

    class PictVisual < BaseForm
      field :visual, Uint32
      field :format, Uint32
    end

    class PictDepth < BaseForm
      field :depth, Uint8
      unused 1
      field :visuals, Uint16, :length
      unused 4
      field :visuals, PictVisual, :list
    end
    
    class PictScreen < BaseForm
      field :depths, Uint32, :length
      field :fallback, Uint32
      field :depths, PictDepth, :list
    end

    class PictFormInfo < BaseForm
      field :id, Uint32
      field :type, Uint8
      field :depth, Uint8
      unused 2
      field :direct, DirectFormat
      field :colormap, Colormap
    end
    
    # # Requests

    # Constants, p112 onwards
    CopyFromParent = 0
    InputOutput    = 1
    InputOnly      = 2

    CWBackPixmap  = 0x0001
    CWBackPixel   = 0x0002
    CWBorderPixmap= 0x0004
    CWBorderPixel = 0x0008
    CWBitGravity  = 0x0010
    CWWinGravity  = 0x0020
    CWBackingStore     = 0x0040
    CWBackingPlanes    = 0x0080
    CWBackingPixel     = 0x0100
    CWOverrideRedirect = 0x0200
    CWSaveUnder        = 0x0400
    CWEventMask        = 0x0800
    CWColorMap         = 0x2000

    KeyPressMask           = 0x000001
    KeyReleaseMask         = 0x000002
    ButtonPressMask        = 0x000004
    ButtonReleaseMask      = 0x000008
    EnterWindowMask        = 0x000010
    LeaveWindowMask        = 0x000020
    PointerMotionMask      = 0x000040
    PointerMotionHintMask  = 0x000080
    Button1MotionMask      = 0x000100
    # 0x200 .. 0x40000; page 113
    ExposureMask           = 0x008000
    VisibilityChangeMask   = 0x010000
    StructureNotifyMask    = 0x020000
    ResizeRedirectMask     = 0x040000
    SubstructureNotifyMask = 0x080000
    SubstructureRedirectMask=0x100000
    FocusChangeMask        = 0x200000
    PropertyChangeMask     = 0x400000
    ColormapChangeMask     = 0x800000
    OwnerGrabButtonMask    = 0x100000

    class CreateWindow < BaseForm
      field :opcode, Uint8, value: 1
      field :depth,  Uint8
      field :request_length, Uint16, value: ->(cw) { len = 8 + cw.value_list.length }
      field :wid, Window
      field :parent, Window
      field :x, Int16
      field :y, Int16
      field :width, Uint16
      field :height, Uint16
      field :border_width, Uint16
      field :window_class, Uint16
      field :visual, VisualID
      field :value_mask, Bitmask
      field :value_list, Uint32, :list
    end

    class ChangeWindowAttributes < BaseForm
      field :opcode, Uint8, value: 2
      unused 1
      field :request_length, Uint16, value: ->(cw) { 3 + cw.value_list.length }
      field :window, Window
      field :value_mask, Bitmask
      field :value_list, Uint32, :list
    end

    class GetWindowAttributes < BaseForm
      field :opcode, Uint8, value: 3
      unused 1
      field :request_length, Uint16, value: 2
      field :window, Window
    end

    class WindowAttributes < BaseForm
      field :reply, Uint8, value: 1
      field :backing_store, Uint8
      field :sequence_number, Uint16
      field :reply_length, Uint32
      field :visual, VisualID
      field :wclass, Uint16
      field :bit_gravity, Uint8
      field :win_gravity, Uint8
      field :backing_planes, Uint32
      field :backing_pixel, Uint32
      field :save_under, Bool
      field :map_is_installed, Bool
      field :map_state, Uint8
      field :override_redirect, Bool
      field :colormap, Colormap
      field :all_event_masks, Uint32
      field :your_event_masks, Uint32
      field :do_not_propagate_mask, Uint16
      unused 2
    end

    class DestroyWindow < BaseForm
      field :opcode, Uint8, value: 4
      unused 1
      field :request_length, Uint16, value: 2
      field :window, Window
    end

    class ChangeSaveSet < BaseForm
      field :opcode, Uint8, value: 6
      field :mode, Uint8
      field :request_length, Uint16, value: 2
      field :window, Window
    end
    
    class ReparentWindow < BaseForm
      field :opcode, Uint8, value: 7
      unused 1
      field :request_length, Uint16, value: 4
      field :window, Window
      field :parent, Window
      field :x, Int16
      field :y, Int16
    end
      
    class MapWindow < BaseForm
      field :opcode, Uint8, value: 8
      unused 1
      field :request_length, Uint16, value: 2
      field :window, Window
    end

    class UnmapWindow < BaseForm
      field :opcode, Uint8, value: 10
      unused 1
      field :request_length, Uint16, value: 2
      field :window, Window
    end

    class ConfigureWindow < BaseForm
      field :opcode, Uint8, value: 12
      unused 1
      field :request_length, Uint16, value: ->(cw) { 3 + cw.values.length }
      field :window, Window
      field :value_mask, Uint16
      unused 2
      field :values, Uint32, :list
    end

    class GetGeometry < BaseForm
      field :opcode, Uint8, value: 14
      unused 1
      field :request_length, Uint16, value: 2
      field :drawable, Drawable
    end

    class Geometry < BaseForm
      field :reply, Uint8, value: 1
      field :depth, Uint8
      field :sequence_number, Uint16
      field :reply_length, Uint32
      field :root, Window
      field :x, Int16
      field :y, Int16
      field :width, Uint16
      field :height, Uint16
      field :border_width, Uint16
      unused 10
    end

    class QueryTree < BaseForm
      field :opcode, Uint8, value: 15
      unused 1
      field :request_length, Uint16, value: 2
      field :window, Window
    end

    class QueryTreeReply < BaseForm
      field :reply, Uint8, value: 1
      unused 1
      field :sequence_number, Uint16
      field :reply_length, Uint32
      field :root, Window
      field :parent, Window
      field :children, Uint16, :length
      unused 14
      field :children, Window, :list
    end

    class InternAtom < BaseForm
      field :opcode, Uint8, value: 16
      field :only_if_exists, Bool
      field :request_length, Uint16, value: ->(ia) {
        2+(ia.name.length+3)/4
      }
      field :name, Uint16, value: ->(ia) {
        ia.name.length
      }
      unused 2
      field :name, String8, :string
    end

    class Reply < BaseForm
      field :reply, Uint8
    end

    class InternAtomReply < Reply
      unused 1
      field :sequence_number, Uint16
      field :reply_length, Uint32
      field :atom, Atom
      unused 20
    end

    class GetAtomName < BaseForm
      field :opcode, Uint8, value: 17
      unused 1
      field :request_length, Uint16, value: 2
      field :atom, Atom
    end

    class AtomName < Reply
      unused 1
      field :sequence_number, Uint16
      field :reply_length, Uint32
      field :name, Uint16, :length
      unused 22
      field :name, String8, :string
    end

    Replace = 0
    Prepend = 1
    Append = 2
    
    class ChangeProperty < BaseForm
      field :opcode, Uint8, value: 18
      field :mode, Uint8
      field :request_length, Uint16, value: ->(cp) {
        #p [:data, cp.data, :len, cp.data.length, :total, 6+(cp.data.length+3)/4]
        6+(cp.data.length+3)/4
      }
      field :window, Window
      field :property, Atom
      field :type, Atom
      field :format, Uint8
      unused 3
      field :data, Uint32, value: ->(cp) { cp.data.length / (cp.format/8) }
      field :data, Uint8, :list
    end

    class GetProperty < BaseForm
      field :opcode, Uint8, value: 20
      field :delete, Bool
      field :request_length, Uint16, value: 6
      field :window, Window
      field :property, Atom
      field :type, Atom
      field :long_offset, Uint32
      field :long_length, Uint32
    end
    
    class Property < BaseForm
      field :reply, Uint8, value: 1
      field :format, Uint8
      field :sequence_number, Uint16
      field :reply_length, Uint32
      field :type, Atom
      field :bytes_after, Uint32
      field :value, Uint32, :format_length
      unused 12
      field :value, String8, :string
    end

    class SendEvent < BaseForm
      field :opcode, Uint8, value: 25
      field :propagate, Bool
      field :request_length, Uint16, value: 11
      field :destination, Window
      field :event_mask, Uint32
      field :event, Uint32 # FIXME: This is wrong, and will break on parsing.
    end
    
    class GrabButton < BaseForm
      field :opcode, Uint8, value: 28
      field :owner_events, Bool
      field :request_length, Uint16, value: 6
      field :grab_window, Window
      field :event_mask, Uint16
      field :pointer_mode, Uint8
      field :keyboard_mode, Uint8
      field :confine_to, Window
      field :cursor, Cursor
      field :button, Uint8
      unused 1
      field :modifiers, Uint16
    end

    class GrabKey < BaseForm
      field :opcode, Uint8, value: 33
      field :owner_event, Bool
      field :request_length, Uint16, value: 4
      field :grab_window, Window
      field :modifiers, Uint16
      field :keycode, Uint8
      field :pointer_mode, Uint8
      field :keyboard_mode, Uint8
      unused 3
    end

    class OpenFont < BaseForm
      field :opcode, Uint8, value: 45
      unused 1
      field :request_length, Uint16, value: ->(of) {
        3+(of.name.length+3)/4
      }
      field :fid, Font
      field :name, Uint16, :length
      unused 2
      field :name, String8, :string
    end
    
    class ListFonts < BaseForm
      field :opcode, Uint8, value: 49
      unused 1
      field :request_length, Uint16, value: ->(lf) {
        2+(lf.pattern.length+4)/4
      }
      field :max_names, Uint16
      field :length_of_pattern, Uint16,value: ->(lf) {
        lf.pattern.length
      }
      field :pattern, String8
    end

    class CreatePixmap < BaseForm
      field :opcode, Uint8, value: 53
      field :depth, Uint8
      field :request_length, Uint16, value: 4
      field :pid, Pixmap
      field :drawable, Uint32
      field :width, Uint16
      field :height, Uint16
    end

    class Str < BaseForm
      field :name, Uint8, :length, value: ->(str) { str.name.length }
      field :name, String8Unpadded, :string

      def to_s
        name
      end
    end

    class ListFontsReply < BaseForm
      field :reply, Uint8, value: 1
      unused 1
      field :sequence_number, Uint16
      field :reply_length, Uint32
      field :names, Uint16, :length
      unused 22
      field :names, Str, :list
    end

    FunctionMask = 0x1
    PlaneMask = 0x2
    ForegroundMask = 0x04
    BackgroundMask = 0x08
    FontMask = 0x4000
    GraphicsExposures = 0x10000

    class CreateGC < BaseForm
      field :opcode, Uint8, value: 55
      unused 1
      field :request_length, Uint16, value: ->(cw) {
        len = 4 + cw.value_list.length
      }
      field :cid, Gcontext
      field :drawable, Drawable
      field :value_mask, Bitmask
      field :value_list, Uint32, :list
    end

    class ChangeGC < BaseForm
      field :opcode, Uint8, value: 56
      unused 1
      field :request_length, Uint16, value: ->(ch) {
        3+ ch.value_list.length
      }
      field :gc, Gcontext
      field :value_mask, Bitmask
      field :value_list, Uint32, :list
    end

    class ClearArea < BaseForm
      field :opcode, Uint8, value: 61
      field :exposures, Bool
      field :request_length, Uint16, value: 4
      field :window, Window
      field :x, Int16
      field :y, Int16
      field :width, Uint16
      field :height, Uint16
    end

    class CopyArea < BaseForm
      field :opcode, Uint8, value: 62
      unused 1
      field :request_length, Uint16, value: 7
      field :src_drawable, Drawable
      field :dst_drawable, Drawable
      field :gc, Gcontext
      field :src_x,  Uint16
      field :src_y,  Uint16
      field :dst_x,  Uint16
      field :dst_y,  Uint16
      field :width,  Uint16
      field :height, Uint16
    end

    class PolyFillRectangle < BaseForm
      field :opcode, Uint8, value: 70
      unused 1
      field :request_length, Uint16, value: ->(ob) {
        len = 3 + 2*(Array(ob.rectangles).length)
      }
      field :drawable, Drawable
      field :gc, Uint32
      field :rectangles, Rectangle, :list
    end

    Bitmap = 0
    XYPixmap=1
    ZPixmap=2
    
    class PutImage < BaseForm
      field :opcode, Uint8, value: 72
      field :format, Uint8
      field :request_length, Uint16, value: ->(pi) {
        6+(pi.data.length+3)/4
      }
      field :drawable, Drawable
      field :gc, Gcontext
      field :width, Uint16
      field :height, Uint16
      field :dstx, Int16
      field :dsty, Int16
      field :left_pad, Uint8
      field :depth, Uint8
      unused 2
      field :data, String8 #, :string
    end

    class ImageText8 < BaseForm
      field :opcode, Uint8, value: 76
      field :n, Uint8, :length
      field :request_length, Uint16, value: ->(it) { 4+(it.n.length+3)/4 }
      field :drawable, Drawable
      field :gc, Gcontext
      field :x, Int16
      field :y, Int16
      field :n, String8, :string
    end

    class ImageText16 < BaseForm
      field :opcode, Uint8, value: 77
      field :n, Uint8, :length
      field :request_length, Uint16, value: ->(it) { 4+(it.n.length*2+3)/4 }
      field :drawable, Drawable
      field :gc, Gcontext
      field :x, Int16
      field :y, Int16
      field :n, String16, :string
    end

    class CreateColormap < BaseForm
      field :opcode, Uint8, value: 78
      field :alloc, Uint8
      field :request_length, Uint16, value: 4
      field :mid, Colormap
      field :window, Window
      field :visual, Uint32
    end

    class QueryExtension < BaseForm
      field :opcode, Uint8, value: 98
      unused 1
      field :request_length, Uint16, value: ->(qe) { 2+(qe.name.length+3)/4 }
      field :name, Uint16, :length
      unused 2
      field :name, String8
    end

    class QueryExtensionReply < Reply
      unused 1
      field :sequence_number, Uint16
      field :reply_length, Uint32
      field :present, Bool
      field :major_opcode, Uint8
      field :first_event, Uint8
      field :first_error, Uint8
      unused 20
    end

    class GetKeyboardMapping < BaseForm
      field :opcode, Uint8, value: 101
      unused 1
      field :request_length, Uint16, value: 2
      field :first_keycode, Uint8
      field :count, Uint8
      unused 2
    end

    class GetKeyboardMappingReply < Reply
      field :keysyms_per_keycode, Uint8
      field :sequence_number, Uint16
      field :reply_length, Uint32
      unused 24
      field :keysyms, Keysym, :list
    end

    # Events (page ~157)
    # FIXME: Events have quite a bit of redundancy, but unfortunately
    # BaseForm can't handle subclassing well.

    Shift = 0x001
    Lock  = 0x002
    Control = 0x004
    Mod1 = 0x008
    Mod2 = 0x010
    Mod3 = 0x0020
    Mod4 = 0x0040
    Mod5 = 0x0080
    Button1 = 0x100
    Button2 = 0x200
    Button3 = 0x400
    Button4 = 0x800
    Button5 = 0x1000

    class Event < BaseForm
      field :code, Uint8
    end

    class SimpleEvent < Event
      field :detail, Uint8
      field :sequence_number, Uint16
    end

    class InputEvent < SimpleEvent
      field :time, Uint32
      field :root, Window
      field :event, Window
      field :child, Window
      field :root_x, Int16
      field :root_y, Int16
      field :event_x, Int16
      field :event_y, Int16
      field :state, Uint16
    end

    class EnterLeaveNotify < InputEvent
      field :mode, Uint8
      field :same_screen_or_focus, Uint8

      def same_screen = same_screen_or_focus.anybit?(0x02)
      def focus = same_screen_or_focus.anybit?(0x01)
    end

    class EnterNotify < EnterLeaveNotify
    end

    class LeaveNotify < EnterLeaveNotify
    end

    class FocusIn < SimpleEvent
      field :event, Window
      field :mode, Uint8
      unused 23
    end

    class FocusOut < FocusIn
    end
      
    class PressEvent < InputEvent
      field :same_screen, Bool
      unused 1
    end

    class ButtonPress < PressEvent
    end

    class KeyPress < PressEvent
    end

    class KeyRelease < PressEvent
    end

    class MotionNotify < PressEvent
    end

    class ButtonRelease < PressEvent
    end

    class ReparentNotify < SimpleEvent
      field :event, Window
      field :window, Window
      field :parent, Window
      field :x, Int16
      field :y, Int16
      field :override_redirect, Bool
      unused 11
    end

    class ConfigureRequest < Event # 23
      field :stack_mode,      Uint8
      field :sequence_number, Uint16
      field :parent, Window
      field :window, Window
      field :sibling, Window
      field :x, Int16
      field :y, Int16
      field :width, Uint16
      field :height, Uint16
      field :border_width, Uint16
      field :value_mask, Uint16
      unused 4
    end
    
    class Expose < SimpleEvent
      field :window, Window
      field :x, Uint16
      field :y, Uint16
      field :width, Uint16
      field :height, Uint16
      field :count, Uint16
      unused 14
    end

    class NoExposure < SimpleEvent # 14
      field :drawable, Drawable
      field :minor_opcode, Uint16
      field :major_opcode, Uint8
      unused 21
    end

    class CreateNotify < SimpleEvent # 16
      field :parent, Window
      field :window, Window
      field :x, Int16
      field :y, Int16
      field :width, Uint16
      field :height, Uint16
      field :border_width, Uint16
      field :override_redirect, Bool
    end
    
    class DestroyNotify < Event
      unused 1
      field :sequence_number, Uint16
      field :event, Window
      field :window, Window
      unused 20
    end
    
    class UnmapNotify < Event
      unused 1
      field :sequence_number, Uint16
      field :event, Window
      field :window, Window
      field :from_configure, Bool
      unused 19
    end
    
    class MapNotify < Event
      unused 1
      field :sequence_number, Uint16
      field :event, Window
      field :window, Window
      field :override_redirect, Bool
      unused 19
    end

    class MapRequest < Event
      unused 1
      field :sequence_number, Uint16
      field :parent, Window
      field :window, Window
      unused 20
    end

    class ConfigureNotify < Event
      unused 1
      field :sequence_number, Uint16
      field :event, Window
      field :window, Window
      field :above_sibling, Window
      field :x, Int16
      field :y, Int16
      field :width, Uint16
      field :height, Uint16
      field :border_width, Uint16
      field :override_redirect, Bool
      unused 5
    end

    class ClientMessage < Event
      field :format, Uint8
      field :sequence_number, Uint16
      field :window, Window
      field :type, Atom
      field :data, X11::Type::Message
    end

    class PropertyNotify < Event # 28
      unused 1
      field :sequence_number, Uint16
      field :window, Window
      field :atom, Atom
      field :time, Uint32
      field :state, Uint8
      unused 15
    end


    # XRender extension
    # From https://cgit.freedesktop.org/xorg/proto/renderproto/tree/renderproto.h
    class XRenderQueryVersion < BaseForm
      field :req_type, Uint8
      field :render_req_type, Uint8, value: 0
      field :request_length, Uint16, value: 3
      field :major_version, Uint32
      field :minor_version, Uint32
    end

    class XRenderQueryVersionReply < Reply
      unused 1
      field :sequence_number, Uint16
      field :request_length, Uint32
      field :major_version, Uint32
      field :minor_version, Uint32
      #unused 16
    end

    class XRenderQueryPictFormats < BaseForm
      field :req_type, Uint8
      field :render_req_type, Uint8, value: 1
      field :request_length, Uint16, value: 1
    end

    class XRenderQueryPictFormatsReply < Reply
      unused 1
      field :sequence_number, Uint16
      field :length, Uint32
      field :formats, Uint32, :length
      field :screens, Uint32, :length
      field :depths, Uint32, :length
      field :visuals, Uint32, :length
      field :subpixel, Uint32, :length
      unused 4
      field :formats, PictFormInfo, :list
      field :screens, PictScreen, :list
      field :subpixels, Uint32, :list
     end

     class XRenderCreatePicture < BaseForm
       field :req_type, Uint8
       field :render_req_type, Uint8, value: 4
       field :request_length, Uint16, value: ->(cp) {
         5 + Array(cp.value_list).length
       }
       field :pid, Uint32
       field :drawable, Uint32
       field :format, Uint32
       field :value_mask, Uint32
       field :value_list, Uint32, :list
     end

     class XRenderCreateGlyphSet < BaseForm
       field :req_type, Uint8
       field :render_req_type, Uint8, value: 17
       field :request_length, Uint16, value: 3
       field :gsid, Uint32
       field :format, Uint32
     end

     class GlyphInfo < BaseForm
       field :width,  Uint16
       field :height, Uint16
       field :x, Int16
       field :y, Int16
       field :x_off, Int16
       field :y_off, Int16
     end

     class XRenderAddGlyphs < BaseForm
       field :req_type, Uint8
       field :render_req_type, Uint8, value: 20
       field :request_length, Uint16, value: ->(ag) {
         if ag.glyphs.length != ag.glyphids.length
           raise "Mismatch: Expected XRenderAddGlyphs glyphs and glyphids to be same length"
         end

         # GlyphInfo length == 3 + Glyphid length == 1
         3 + ag.glyphs.length * 4 + (ag.data.length+3)/4
       }
       field :glyphset, Uint32
       field :glyphs,   Uint32, :length
       field :glyphids, Uint32, :list
       field :glyphs,   GlyphInfo, :list
       field :data,     String8
     end

     class XRenderColor < BaseForm
       field :red,   Uint16
       field :green, Uint16
       field :blue,  Uint16
       field :alpha, Uint16
     end

     class GlyphElt32 < BaseForm
       field :glyphs, Uint8, :length
       unused 3
       field :delta_x, Uint16
       field :delta_y, Uint16
       field :glyphs, Uint32, :list
     end

     # This is *also* the same as XRenderCOmpositeGlyphs16 and 8 w/other render_req_type,
     # but do we care?
     class XRenderCompositeGlyphs32 < BaseForm
       field :req_type, Uint8
       field :render_req_type, Uint8, value: 25
       field :request_length, Uint16, value: ->(ch) {
         7 + (ch.glyphcmds[0].glyphs.length + 2) # per glyphcmd
       }
       field :op, Uint8
       unused 3
       field :src, Uint32
       field :dst, Uint32
       field :mask_format, Uint32
       field :glyphset, Uint32
       field :xsrc, Uint16
       field :ysrc, Uint16
       # FIXME:
       # We say this is a list, because technically it is
       # But currently it'll break with more than one item.
       field :glyphcmds, GlyphElt32, :list
     end

     class XRenderFillRectangles < BaseForm
       field :req_type, Uint8
       field :render_req_type, Uint8, value: 26
       field :request_length, Uint16, value: ->(fr) { 5 + fr.rects.length * 2 }
       field :op, Uint8
       unused 3
       field :dst, Uint32
       field :color, Uint32
       field :rects, Rectangle, :list
     end

     class XRenderCreateSolidFill < BaseForm
       field :req_type, Uint8
       field :render_req_type, Uint8, value: 33
       field :request_length, Uint16, value: 4
       field :fill, Uint32
       field :color, XRenderColor
     end
  end
end