// full list of event/error/request codes for all extensions:
// http://www.opensource.apple.com/source/X11server/X11server-106.7/kdrive/xorg-server-1.6.5-apple3/dix/protocol.txt

var xutil = Require('x11/core/xutil');
var hexy = Require('x11/core/hexy').hexy;

var valueMask = {
    CreateWindow: {
        backgroundPixmap      : {
          mask: 0x00000001,
          format: 'L'
        },
        backgroundPixel       : {
          mask: 0x00000002,
          format: 'L'
        },
        borderPixmap          : {
          mask: 0x00000004,
          format: 'L'
        },
        borderPixel           : {
          mask: 0x00000008,
          format: 'L'
        },
        bitGravity            : {
          mask: 0x00000010,
          format: 'Cxxx'
        },
        winGravity            : {
          mask: 0x00000020,
          format: 'Cxxx'
        },
        backingStore          : {
          mask: 0x00000040,
          format: 'Cxxx'
        },
        backingPlanes         : {
          mask: 0x00000080,
          format: 'L'
        },
        backingPixel          : {
          mask: 0x00000100,
          format: 'L'
        },
        overrideRedirect      : {
          mask: 0x00000200,
          format: 'Cxxx'
        },
        saveUnder             : {
          mask: 0x00000400,
          format: 'Cxxx'
        },
        eventMask             : {
          mask: 0x00000800,
          format: 'L'
        },
        doNotPropagateMask    : {
          mask: 0x00001000,
          format: 'L'
        },
        colormap              : {
          mask: 0x00002000,
          format: 'L'
        },
        cursor                : {
          mask: 0x00004000,
          format: 'L'
        }
    },
    CreateGC: {
       'function'             : { // TODO: alias? _function?
          mask: 0x00000001,
          format: 'Cxxx'
        },
       planeMask              : {
          mask: 0x00000002,
          format: 'L'
        },
       foreground             : {
          mask: 0x00000004,
          format: 'L'
        },
       background             : {
          mask: 0x00000008,
          format: 'L'
        },
       lineWidth              : {
          mask: 0x00000010,
          format: 'Sxx'
        },
       lineStyle              : {
          mask: 0x00000020,
          format: 'Cxxx'
        },
       capStyle               : {
          mask: 0x00000040,
          format: 'Cxxx'
        },
       joinStyle              : {
          mask: 0x00000080,
          format: 'Cxxx'
        },
       fillStyle              : {
          mask: 0x00000100,
          format: 'Cxxx'
        },
       fillRule               : {
          mask: 0x00000200,
          format: 'Cxxx'
        },
       tile                   : {
          mask: 0x00000400,
          format: 'L'
        },
       stipple                : {
          mask: 0x00000800,
          format: 'L'
        },
       tileStippleXOrigin     : {
          mask: 0x00001000,
          format: 'sxx'
        },
       tileStippleYOrigin     : {
          mask: 0x00002000,
          format: 'sxx'
        },
       font                   : {
          mask: 0x00004000,
          format: 'L'
        },
       subwindowMode          : {
          mask: 0x00008000,
          format: 'Cxxx'
        },
       graphicsExposures      : {
          mask: 0x00010000,
          format: 'Cxxx'
        },
       clipXOrigin            : {
          mask: 0x00020000,
          format: 'Sxx'
        },
       clipYOrigin            : {
          mask: 0x00040000,
          format: 'Sxx'
        },
       clipMask               : {
          mask: 0x00080000,
          format: 'L'
        },
       dashOffset             : {
          mask: 0x00100000,
          format: 'Sxx'
        },
       dashes                 : {
          mask: 0x00200000,
          format: 'Cxxx'
        },
       arcMode                : {
          mask: 0x00400000,
          format: 'Cxxx'
        }
    },
    ConfigureWindow: {
      x                       : {
        mask: 0x000001,
        format: 'sxx'
      },
    	y                       : {
        mask: 0x000002,
        format: 'sxx'
      },
    	width                   : {
        mask: 0x000004,
        format: 'Sxx'
      },
    	height                  : {
        mask: 0x000008,
        format: 'Sxx'
      },
    	borderWidth             : {
        mask: 0x000010,
        format: 'Sxx'
      },
    	sibling                 : {
        mask: 0x000020,
        format: 'L'
      },
    	stackMode               : {
        mask: 0x000040,
        format: 'Cxxx'
      }
    }
};


var valueMaskName = {};
for (var req in valueMask) {
    var masks = valueMask[req];
    var names = valueMaskName[req] = {};
    for (var m in masks)
        names[masks[m].mask] = m;
}

function packValueMask(reqname, values)
{
    var bitmask = 0;
    var masksList = [];
    var format = '';
    var reqValueMask = valueMask[reqname];
    var reqValueMaskName = valueMaskName[reqname];

    if (!reqValueMask)
        throw new Error(reqname + ': no value mask description');

    for (var value in values)
    {
        var v = reqValueMask[value];
        if (v) {
          var valueBit = v.mask;
          if (!valueBit)
              throw new Error(reqname + ': incorrect value param ' + value);
          masksList.push(valueBit);
          bitmask |= valueBit;
        }
    }

    /* numeric sort */
    masksList.sort(function(a, b) {
      return a - b;
    });

    var args = [];
    for (m in masksList)
    {
       var valueName = reqValueMaskName[masksList[m]];
       format += reqValueMask[valueName].format
       args.push( values[valueName] );
    }
    return [format, bitmask, args]
}

/*

the way requests are described here

- outgoing request

   1) as function
   client.CreateWindow( params, params ) ->
       req = reqs.CreateWindow[0]( param, param );
       pack_stream.pack(req[0], req[1]);

   2) as array: [format, [opcode, request_length, additional known params]]

   client.MapWindow[0](id) ->
       req = reqs.MwpWindow;
       req[1].push(id);
       pack_stream.pack( req[0], req[1] );

- reply

*/

var templates = {
   CreateWindow: [
       // create request packet - function OR format string
       function(id, parentId, x, y, width, height, borderWidth, depth, _class, visual, values) {

           if (borderWidth === undefined)
               borderWidth = 0;
           if (depth === undefined)
               depth = 0;
           if (_class === undefined)
               _class = 0;
           if (visual === undefined)
               visual = 0;
           if (values === undefined)
               values = {}

           var format = 'CCSLLssSSSSLL';

           // TODO: slice from function arguments?

           // TODO: the code is a little bit mess
           // additional values need to be packed in the following way:
           // bitmask (bytes #24 to #31 in the packet) - 32 bit indicating what adittional arguments we supply
           // values list (bytes #32 .. #32+4*num_values) in order of corresponding bits TODO: it's actually not 4*num. Some values are 4b ytes, some - 1 byte

           var vals = packValueMask('CreateWindow', values);
           var packetLength = 8 + (values ? vals[2].length : 0);
           var args = [1, depth, packetLength, id, parentId, x, y, width, height, borderWidth, _class, visual];
           format += vals[0];
           args.push(vals[1]);
           args = args.concat(vals[2]);
           return [format, args];
       }

   ],

   ChangeWindowAttributes:[
       function(wid, values) {
           var format = 'CxSLSxx';
           var vals = packValueMask('CreateWindow', values);
           var packetLength = 3 + (values ? vals[2].length : 0);
           var args = [2, packetLength, wid, vals[1]];
           var valArr = vals[2];
           format += vals[0];
           args = args.concat(valArr);
           return [format, args];
        }
   ],

   GetWindowAttributes: [
       ['CxSL', [3, 2]],
       function(buf, backingStore)
       {
           // TODO: change from array to named object fields
           var res = buf.unpack('LSCCLLCCCCLLLS');
           var ret = {
               backingStore: backingStore
           };
           ( "visual klass bitGravity winGravity backingPlanes backingPixel" +
             " saveUnder mapIsInstalled mapState overrideRedirect colormap" +
             " allEventMasks myEventMasks doNotPropogateMask" )
           .split(' ').forEach(function(field, index) {
               ret[field] = res[index];
           });
           return ret;
       }
   ],

   DestroyWindow: [
       [ 'CxSL', [4, 2] ]
   ],

   ChangeSaveSet: [
      function(isInsert, wid)
      {
          return [ 'CCSL', [6, (isInsert ? 0 : 1), 2, wid]]
      }
   ],

   // wid, newParentId, x, y
   ReparentWindow: [
       [ 'CxSLLss', [7, 4]]
   ],

   MapWindow: [
       // 8 - opcode, 2 - length, wid added as parameter
       [ 'CxSL', [8, 2] ]
   ],

   UnmapWindow: [
       [ 'CxSL', [10, 2] ]
   ],

   ConfigureWindow: [
        /*
         * options : {
         *     x : x_value,
         *     y : y_value,
         *     width : width_value,
         *     height : height_value,
         *     borderWidth : borderWidth_value,
         *     sibling : sibling_value
         * }
         */
        function(win, options) {
            var vals = packValueMask('ConfigureWindow', options);
            var format = 'CxSLSxx' + vals[0];
            var args = [12, vals[2].length + 3, win, vals[1]];
            args = args.concat(vals[2]);
            return [format, args];
        }
   ],

   ResizeWindow: [
        function(win, width, height) {
            return module.exports.ConfigureWindow[0](win, { width : width, height: height });
        }
   ],

   MoveWindow: [
        function(win, x, y) {
            return module.exports.ConfigureWindow[0](win, { x : x, y: y });
        }
   ],

   MoveResizeWindow: [
        function(win, x, y, width, height) {
            return module.exports.ConfigureWindow[0](win, { x : x, y: y, width : width, height: height });
        }
   ],

   RaiseWindow: [
        function(win) {
            return module.exports.ConfigureWindow[0](win, { stackMode : 0 });
        }
   ],

   LowerWindow: [
        function(win) {
            return module.exports.ConfigureWindow[0](win, { stackMode : 1 });
        }
   ],

   QueryTree: [
        ['CxSL', [15, 2]],

        function(buf) {
            var tree = {};
            var res = buf.unpack('LLS');
            tree.root = res[0];
            tree.parent = res[1];
            tree.children = [];
            for (var i=0; i < res[2]; ++i)
                tree.children.push(buf.unpack('L', 24 + i*4)[0]);
            return tree;
        }
   ],

   // opcode 16
   InternAtom: [
       function (returnOnlyIfExist, value)
       {
           var padded = xutil.padded_string(value);
           return ['CCSSxxa', [16, returnOnlyIfExist ? 1 : 0, 2+padded.length/4, value.length, padded] ];
       },

       function(buf, seq_num) {
           var res = buf.unpack('L')[0];
           var pending_atom = this.pending_atoms[seq_num];
           if (!this.atoms[pending_atom]) {
               this.atoms[pending_atom] = res;
               this.atom_names[res] = pending_atom;
           }

           delete this.pending_atoms[seq_num];
           return res;
       }
   ],

   GetAtomName: [
       [ 'CxSL', [17, 2] ],
       function(buf, seq_num) {
          var nameLen = buf.unpack('S')[0];
          // Atom value starting from 24th byte in the buffer
          var name = buf.unpackString(nameLen, 24);
          var pending_atom = this.pending_atoms[seq_num];
          if (!this.atoms[pending_atom]) {
              this.atom_names[pending_atom] = name;
              this.atoms[name] = pending_atom;
          }

          delete this.pending_atoms[seq_num];
          return name;
       }
   ],

   ChangeProperty: [
       // mode: 0 replace, 1 prepend, 2 append
       // format: 8/16/32
       function(mode, wid, name, type, units, data)
       {
          var padded4 = (data.length + 3) >> 2;
          var pad = new Buffer( (padded4<<2) - data.length);
          var format = 'CCSLLLCxxxLaa';
          var requestLength = 6 + padded4;
          var dataLenInFormatUnits = data.length / (units >> 3);
          return [format, [18, mode, requestLength, wid, name, type, units, dataLenInFormatUnits, data, pad] ];
       }
   ],

   // TODO: test
   DeleteProperty: [
       function(wid, prop) {
           return [ 'CxSLL', [19, 3, wid, prop] ];
       }
   ],

   GetProperty: [

       function(del, wid, name, type, longOffset, longLength) //  - offest and maxLength in 4-byte units
       {
           return [ 'CCSLLLLL', [20, del, 6, wid, name, type, longOffset, longLength ] ];
       },

       function(buf, format) {
     var res = buf.unpack('LLL');
           var prop = {};
           prop.type = res[0];
           prop.bytesAfter = res[1];
           var len = res[2]*(format >> 3)
           prop.data = buf.slice(24, 24+len);
           return prop;
       }
   ],

   ListProperties: [

       function(wid)
       {
          return ['CxSL', [21, 2, wid]];
       },

       function(buf) {
          var n = buf.unpack('S')[0];
          var i;
          var atoms = [];
          for(i=0; i < n; ++i) {
             atoms.push(buf.unpack('L', 24+4*i)[0]);
             //console.log([n, i, atoms]);
          }
          return atoms;
       }
   ],

   SetSelectionOwner: [
      function(owner, selection, time)
      {
          if (!time)
              time = 0; // current time
          return ['CxSLLL', [22, 4, owner, selection, time]];
      }
   ],

   GetSelectionOwner: [
      function(selection)
      {
          return ['CxSL', [23, 2, selection]];
      },

      function(buf) {
          return buf.unpack('L')[0];
      }
   ],

   ConvertSelection: [
      function(requestor, selection, target, property, time) {
          if (!time)
              time = 0;
          return ['CxSLLLLL', [24, 6, requestor, selection, target, property, time]];
      }
   ],

   SendEvent: [

       function(destination, propagate, eventMask, eventRawData)
       {
           return [ 'CCSLLa', [25, propagate, 11, destination, eventMask, eventRawData] ];
       }
   ],

   GrabPointer: [
       function(wid, ownerEvents, mask, pointerMode, keybMode, confineTo, cursor, time) {
           return [ 'CCSLSCCLLL', [ 26, ownerEvents, 6, wid, mask, pointerMode, keybMode,
                                    confineTo, cursor, time] ];
       },
       function(buf, status) {
           return status;
       }
   ],

   UngrabPointer: [
       function(time) {
           return [ 'CxSL', [ 27, 2, time] ];
       }
   ],

   GrabButton: [
       function(wid, ownerEvents, mask, pointerMode, keybMode, confineTo, cursor, button, modifiers) {
           return [ 'CCSLSCCLLCxS', [ 28, ownerEvents, 6, wid, mask, pointerMode, keybMode, confineTo,
                                      cursor, button, modifiers ] ];
       }
   ],

   UngrabButton: [
       function(wid, button, modifiers) {
           return [ 'CCSLSxx', [ 29, button, 3, wid, modifiers ] ];
       }
   ],

   ChangeActivePointerGrab: [
       function(cursor, time, mask) {
           return [ 'CxSLLSxx', [ 30, 4, cursor, time, mask ] ];
       }
   ],

   GrabKeyboard: [
       function(wid, ownerEvents, time, pointerMode, keybMode) {
           return [ 'CCSLLCCxx', [ 31, ownerEvents, 4, wid, time, pointerMode, keybMode ] ];
       },
       function(buf, status) {
           return status;
       }
   ],

   UngrabKeyboard: [
       function(time) {
           return [ 'CxSL', [ 32, 2, time ] ];
       }
   ],

   GrabKey: [
       function(wid, ownerEvents, modifiers, key, pointerMode, keybMode) {
           return [ 'CCSLSCCCxxx', [ 33, ownerEvents, 4, wid, modifiers, key, pointerMode, keybMode ] ];
       }
   ],

   UngrabKey: [
       function(wid, key, modifiers) {
           return [ 'CCSLSxx', [ 34, key, 3, wid, modifiers ] ];
       }
   ],
   
   AllowEvents: [
       function(mode, ts) {
           return [ 'CCSL', [ 35, mode, 2, ts ] ];
       }
   ],

   GrabServer: [
       [ 'CxS', [36, 1]]
   ],

   UngrabServer: [
       [ 'CxS', [37, 1]]
   ],

   QueryPointer: [
       [ 'CxSL', [38, 2] ],
       function(buf, sameScreen) {
           var res = buf.unpack('LLssssS');
           return {
               root: res[0],
               child: res[1],
               rootX: res[2],
               rootY: res[3],
               childX: res[4],
               childY: res[5],
               keyMask: res[6],
               sameScreen : sameScreen
           };
       }
   ],

   TranslateCoordinates: [
       function(srcWid, dstWid, srcX, srcY) {
           return [ 'CxSLLSS', [ 40, 4, srcWid, dstWid, srcX, srcY ] ];
       },
       function(buf, sameScreen)
       {
           var res = buf.unpack('Lss');
           var ext = {};
           ext.child = res[0];
           ext.destX = res[1];
           ext.destY = res[2];
           ext.sameScreen = sameScreen;
           return ext;
       }
   ],

   WarpPointer: [

      function (srcWin, dstWin, srcX, srcY, srcWidth, srcHeight, dstX, dstY)
      {
          return [ 'CxSLLssSSss', [41, 6, srcWin, dstWin, srcX, srcY, srcWidth, srcHeight, dstX, dstY] ];
      }
   ],

   SetInputFocus: [

      function (wid, revertTo) // revertTo: 0 - None, 1 - PointerRoot, 2 - Parent
      {
          return [ 'CCSLL', [42, revertTo, 3, wid, 0] ];
      }
   ],

   GetInputFocus: [
       function() {
           return [ 'CxS', [ 43, 1 ] ];
       },
       function(buf, revertTo)
       {
           return {
               focus : buf.unpack('L')[0],
               revertTo : revertTo
           };
       }
   ],

   OpenFont: [
       function(name,fid) {
           var padded = xutil.padded_string(name);
           return ['CxSLSxxa', [45, 3+padded.length/4, fid, name.length, padded] ];
       }    
   ],

   ListFonts: [
      function(pattern, max)
      {
          var req_len = 2+xutil.padded_length(pattern.length)/4;
          return [ 'CxSSSp', [49, req_len, max, pattern.length, pattern] ];
      },

      function(buf) {
          console.log(buf);
          // TODO: move to buffer.unpackStringList
          var res = [];
          var off = 24;
          while (off < buf.length)
          {
              var len = buf[off++];
              if (len == 0)
                  break;
              if (off + len > buf.length)
              {
                  len = buf.length - off;
                  if (len <= 0)
                     break;
              }
              res.push(buf.unpackString(len, off));
              off += len;
          }
          return res;
      }
   ],

   CreatePixmap: [
       function(pid, drawable, depth, width, height) {
          return [ 'CCSLLSS', [53, depth, 4, pid, drawable, width, height] ];
       }
   ],

   FreePixmap: [
      function (pixmap) {
          return [ 'CxSL', [54, 2, pixmap] ];
      }
   ],

   CreateCursor: [
       function(cid, source, mask, foreRGB, backRGB, x, y) {
          foreR = foreRGB.R
          foreG = foreRGB.G
          foreB = foreRGB.B

          backR = backRGB.R
          backG = backRGB.G
          backB = backRGB.B
          return [ 'CxSLLLSSSSSSSS', [93, 8, cid, source, mask, foreR, foreG, foreB, backR, backG, backB, x, y] ];
       }
   ],

   // opcode 55
   CreateGC: [
       function(cid, drawable, values) {
           var format = 'CxSLLL';
           var vals = packValueMask('CreateGC', values);
           var packetLength = 4 + (values ? vals[2].length : 0);
           var args = [55, packetLength, cid, drawable];
           format += vals[0]
           args.push(vals[1]);     // values bitmask
           args = args.concat(vals[2])
           return [format, args];
        }
   ],

   ChangeGC: [
       function(cid, values) {
           var format = 'CxSLL';
           var vals = packValueMask('CreateGC', values);
           var packetLength = 3 + (values ? vals[2].length : 0);
           var args = [56, packetLength, cid];
           format += vals[0]
           args.push(vals[1]);     // values bitmask
           args = args.concat(vals[2])
           return [format, args];
        }
   ],

   ClearArea: [
       function(wid, x, y, width, height, exposures) {
          return [ 'CCSLssSS', [61, exposures, 4, wid, x, y, width, height] ];
       }
   ],

   //
   CopyArea: [
       function(srcDrawable, dstDrawable, gc, srcX, srcY, dstX, dstY, width, height) {
          return [ 'CxSLLLssssSS', [62, 7, srcDrawable, dstDrawable, gc, srcX, srcY, dstX, dstY, width, height] ];
       }
   ],


   PolyPoint: [
       function(coordMode, drawable, gc, points)
       {
          var format = 'CCSLL';
          var args = [64, coordMode, 3+points.length/2, drawable, gc];
          for (var i=0; i < points.length; ++i)
          {
              format += 'S';
              args.push(points[i]);
          }
          return [format, args];
       }
   ],

   PolyLine: [
       // TODO: remove copy-paste - exectly same as PolyPoint, only differ with opcode
       function(coordMode, drawable, gc, points)
       {
          var format = 'CCSLL';
          var args = [65, coordMode, 3+points.length/2, drawable, gc];
          for (var i=0; i < points.length; ++i)
          {
              format += 'S';
              args.push(points[i]);
          }
          return [format, args];
       }

   ],

   PolyRectangle: [
      function(drawable, gc, coords) { // x1, y1, w1, h1, x2, y2, w2, h2...
          var format = 'CxSLL';
          var numrects4bytes = coords.length/2;
          var args = [67, 3+numrects4bytes, drawable, gc];
          for (var i=0; i < coords.length; ++i)
          {
              format += 'S';
              args.push(coords[i]);
          }
          return [format, args];
      }
   ],

   PolyArc: [
      function(drawable, gc, coords) { // x1, y1, w1, h1, a11, a12, ...
          var format = 'CxSLL';
          var numrects4bytes = coords.length/2;
          var args = [68, 3+numrects4bytes, drawable, gc];
          for (var i=0; i < coords.length; ++i)
          {
              format += 'S';
              args.push(coords[i]);
          }
          return [format, args];
      }
   ],

   FillPoly: [
       // TODO: remove copy-paste - exectly same as PolyPoint, only differ with opcode
       function(drawable, gc, shape, coordMode, points)
       {
          var format = 'CxSLLCCxx';
          var args = [69, 4+points.length/2, drawable, gc, shape, coordMode];
          for (var i=0; i < points.length; ++i)
          {
              format += 'S';
              args.push(points[i]);
          }
          return [format, args];
       }

   ],

   PolyFillRectangle: [
      function(drawable, gc, coords) { // x1, y1, w1, h1, x2, y2, w2, h2...
          var format = 'CxSLL';
          var numrects4bytes = coords.length/2;
          var args = [70, 3+numrects4bytes, drawable, gc];
          for (var i=0; i < coords.length; ++i)
          {
              format += 'S';
              args.push(coords[i]);
          }
          return [format, args];
      }
   ],

   PolyFillArc: [
      function(drawable, gc, coords) { // x1, y1, w1, h1, a11, a12, ...
          var format = 'CxSLL';
          var numrects4bytes = coords.length/2;
          var args = [71, 3+numrects4bytes, drawable, gc];
          for (var i=0; i < coords.length; ++i)
          {
              format += 'S';
              args.push(coords[i]);
          }
          return [format, args];
      }
   ],


   PutImage: [
      // format:  0 - Bitmap, 1 - XYPixmap, 2 - ZPixmap
      function(format, drawable, gc, width, height, dstX, dstY, leftPad, depth, data) {
          var padded = xutil.padded_length(data.length);
          var reqLen = 6 + padded/4; // (length + 3) >> 2 ???
          var padLength = padded - data.length;
          var pad = new Buffer(padLength); // TODO: new pack format 'X' - skip amount of bytes supplied in numerical argument

          // TODO: move code to calculate reqLength and use BigReq if needed outside of corereq.js
          // NOTE: big req is used here (first 'L' in format, 0 and +1 in params), won't work if not enabled
          return [ 'CCSLLLSSssCCxxaa', [72, format, 0, 1+reqLen, drawable, gc, width, height, dstX, dstY, leftPad, depth, data, pad]];
      }
   ],

   GetImage: [
       function(format, drawable, x, y, width, height, planeMask)
       {
           return [ 'CCSLssSSL', [73, format, 5, drawable, x, y, width, height, planeMask]];
       },
       function(buf, depth)
       {
           var visualId = buf.unpack('L')[0];
           return {
               depth: depth,
               visualId: visualId,
               data: buf.slice(24)
           };
       }
   ],

   PolyText8: [
       function(drawable, gc, x, y, items) {
          var format = 'CxSLLss';
          var numItems = items.length;
          var reqLen = 16;
          var args = [74, 0, drawable, gc, x, y];
          for (var i=0; i < numItems; ++i)
          {
              var it = items[i];
              if (typeof it == 'string')
              {
                  if (it.length > 254) // TODO: split string in set of items
                      throw 'not supported yet';
                  format += 'CCa';
                  args.push(it.length);
                  args.push(0); // delta???
                  args.push(it);
                  reqLen += 2 + it.length;
              } else {
                  throw 'not supported yet';
              }
          }
          var len4 = xutil.padded_length(reqLen)/4;
          var padLen = len4*4 - reqLen;
          args[1] = len4; // set request length to calculated value
          var pad = '';
          for (var i=0; i < padLen; ++i)
             pad += String.fromCharCode(0);
          format += 'a';
          args.push(pad);
          return [format, args];
       }
   ],

   CreateColormap:
   [
       function(cmid, wid, vid, alloc)
       {
           return ['CCSLLL', [78, alloc, 4, cmid, wid, vid]];
       }
   ],

   AllocColor: [
       [ 'CxSLSSSxx', [84, 4] ], // params: colormap, red, green, blue

       function(buf) {
	   var res = buf.unpack('SSSxL');
           var color = {};
           color.red   = res[0];
           color.blue  = res[1];
           color.green = res[2];
           color.pixel = res[3]>>8; // it looks like 3 first bytes contain RGB value in response
           return color;
       }
   ],
   
   QueryExtension: [
       function(name) {
           var padded = xutil.padded_string(name);
           return ['CxSSxxa', [98, 2+padded.length/4, name.length, padded] ];
       },

       function(buf) {
	   var res = buf.unpack('CCCC');
           var ext = {};
           ext.present = res[0];
           ext.majorOpcode = res[1];
           ext.firstEvent = res[2];
           ext.firstError = res[3];
           return ext;
       }

   ],

   ListExtensions: [
       [ 'CxS', [99, 1] ],

       function(buf) {
          // TODO: move to buffer.unpackStringList
          var res = [];
          var off = 24;
          while (off < buf.length)
          {
              var len = buf[off++];
              if (len == 0)
                  break;
              if (off + len > buf.length)
              {
                  len = buf.length - off;
                  if (len <= 0)
                     break;
              }
              res.push(buf.unpackString(len, off));
              off += len;
          }
          return res;
       }
   ],

   GetKeyboardMapping: [
       function(startCode, num) {
           return [ 'CxSCCxx', [101, 2, startCode, num] ]
       },
       function(buff, listLength) {
           var res = [];
           var format = '';
           for (var i=0; i < listLength; ++i)
               format += 'L';
           for (var offset=24; offset < buff.length - 4*listLength; offset += 4*listLength)
                res.push(buff.unpack(format, offset));
           return res;
       }
   ],

        // todo: move up to keep reque
	GetGeometry: [
		function(drawable){
			return ['CxSL', [14, 2, drawable]]
		},
		function(buff, depth)
		{
			var res = buff.unpack('LssSSSx');
			var ext = {};
			ext.windowid = res[0]
			ext.xPos = res[1];
			ext.yPos = res[2];
			ext.width = res[3];
			ext.height = res[4];
			ext.borderWidth = res[5];
			ext.depth = depth;
			return ext;
		}
   ],

   KillClient: [
       function(resource) {
           return [ 'CxSL', [113, 2, resource] ];
       }
   ],

   SetScreenSaver: [
       function(timeout, interval, preferBlanking, allowExposures) {
           return [ 'CxSssCCxx', [107, 3, timeout, interval, preferBlanking, allowExposures]];
       }
   ],

   ForceScreenSaver: [
       function(activate) {
           return [ 'CCS', [115, activate?1:0, 1] ];
       }
   ]
};

templates.KillKlient = templates.KillClient;

module.exports = templates;
