Node.prototype.___add = DocumentFragment.prototype.___add = function()
{
  if(arguments.length)
  {
    if( arguments[0] )
    {
      var doc = this.nodeType == 9 ? this : this.ownerDocument;
      var i = 0, ele = null, ns = null, prefixPos = 0, nodeName = '';
      var prop='', is_array=false, arg=null;
      if( typeof arguments[0] == 'string' )
      {
        nodeName = arguments[0];
        if( ( prefixPos = nodeName.indexOf(':') ) != -1 )
        {
          ns = doc.lookupNamespaceURI(nodeName.slice(0, prefixPos));
          if( !ns )
          {
            throw('namespace not defined in call Node.prototype.___add')
          }
          
        }
        else
        {
         ns = doc.documentElement.namespaceURI;
        }
        ele = doc.createElementNS(ns, nodeName.slice(prefixPos + 1));
        i++;
      }
      else
      {
        ele = this; 
      }
      arg = arguments[i];
      while(
        ( is_array = arg instanceof  Array ) ||
        ( ( typeof arg == 'string' ) && ( ( ( arguments.length - i) % 2) || arguments[i+1] instanceof  Array ) )
      )      
      {
        if( arg instanceof  Array ) 
        {
          ele.___add.apply(ele, arg); 
        }
        else if( arg ) 
        {
          ele.appendChild(doc.createTextNode(arg));
        }
        arg = arguments[++i];
      }
      for( ; arguments[i] ; i += 2 )
      {
        if( typeof arguments[i+1] == 'string' )
        {
          ele.setAttribute(arguments[i], arguments[i+1]);
        }
        else
        {
          ele[arguments[i]] = arguments[i+1];
        }
      }
      if( this.nodeType == 1 && ( this != ele ) )
      {
        this.appendChild(ele);
      }
      return ele;
    }
    else
    {
      return this.appendChild(doc.createTextNode(arguments[1]));
    }
  }
  return null;
}

Node.prototype.render=function(template)
{
  return this.___add.apply(this, template);
};

Node.prototype.hasClass=function(name)
{
  var re = new RegExp( name + ' +| +' + name + '|^' + name + '$');
  return re.test(this.className);
}

Node.controls = Node.controls || {};

Node.controls.templates =
{
  'hue-saturation-quadrat': function()
  {
    return ['hue-saturation-quadrat',
      ['svg:svg', 
        ['svg:defs',
          ['svg:linearGradient',  
            this.linearGradient('0%', '#f00'),
            this.linearGradient('16.666%', '#ff0'),
            this.linearGradient('33.333%', '#0f0'),
            this.linearGradient('50%', '#0ff'),
            this.linearGradient('66.666%', '#00f'),
            this.linearGradient('83.333%', '#f0f'),
            this.linearGradient('100%', '#f00'),
            'id', 'color', 'gradientUnits', 'objectBoundingBox'
          ],
          ['svg:linearGradient', 
            this.linearGradient('0%', '#808080'), // #c0c0c0 | 808080
            this.linearGradient('100%', '#808080', '0'),
            'id', 'white', 
            'gradientUnits', 'objectBoundingBox', 
            'x1', '50%', 'y1', '100%', 'x2', '50%', 'y2', '0%'
          ]
        ],
        this.rect(0, 0, 1000, 1000, 0, 0, 'url(#color)'),
        this.rect(0, 0, 1000, 1000, 0, 0, 'url(#white)'),
        'viewBox', '0 0 1000 1000'
      ],
      ['pointer-2-d',
        ['svg:svg',
          ['svg:g', 
            this.rect(-37, -2.5, 30, 5, 2),
            this.rect(7, -2.5, 30, 5, 2),
            this.rect(-2.5, -37, 5, 30, 2),
            this.rect(-2.5, 7, 5, 30, 2),
            'transform', 'translate(50,50)'
          ],
          'viewBox', '0 0 100 100'
        ]
      ],
      ['handler', 'class', 'x-y']
    ]
  },
  
  'segment': function(hex_color, rotation)
  {
    return ['svg:path',
      'd', 'M 0 0 l -7 -498  a 498 498 0 0 1 14 0 z',
      'fill', hex_color,
      'transform', 'rotate(' + rotation + ')'
    ]
  },
  
  '2d-pointer': function()
  {
    return ['pointer-2-d',
          ['svg:svg',
            ['svg:g', 
              this.rect(-37, -2.5, 30, 5, 2),
              this.rect(7, -2.5, 30, 5, 2),
              this.rect(-2.5, -37, 5, 30, 2),
              this.rect(-2.5, 7, 5, 30, 2),
              'transform', 'translate(50,50)'
            ],
            'viewBox', '0 0 100 100'
          ]
        ]
  },
  
  'hue-saturation-circel': function()
  {
    var color_converter = new Colors();
    color_converter.setSaturation(100);
    color_converter.setLuminosity(50);
    var defs = ['svg:defs', 
      ['svg:radialGradient',
        ['svg:stop', 'offset', '0%', 'stop-color', '#808080'],
        ['svg:stop', 'offset', '100%', 'stop-color', '#808080', 'stop-opacity', '0'],
        'id', 'neutral-gradient', 
        'gradientUnits', 'userSpaceOnUse',
        'cx', '500', 
        'cy', '500', 
        'r', '500'
      ]
    ]
    var ret =[];
    var segments = [];
    var i = 0;
    for( ; i <360; i++)
    {
      color_converter.setHue(i);
      segments[segments.length] = this.segment('#' +color_converter.getHex(), i);
    }
    return  ['hue-saturation-circel',
      ['svg:svg',
        defs,
        ['svg:g', 
          ['svg:circle', 
            'cx', '500', 
            'cy', '500', 
            'r', '500', 
            'stroke', '#333', 
            'stroke-width', '1'
          ]
        ],
        ['svg:g', segments, 'transform', 'translate(500 500)'],
        ['svg:g', 
          ['svg:rect', 
            'fill', 'url(#neutral-gradient)', 
            'x', '0', 
            'y', '0', 
            'width', '1000', 
            'height', '1000'
          ]
        ],
        'viewBox', '0 0 1000 1000'
      ],
      this['2d-pointer'](),
      ['handler', 'class', 'x-y']
    ]
  },

  'luminosity-axis': function()
  {
    return ['luminosity-axis',
      ['svg:svg', 
        ['svg:defs',
          ['svg:linearGradient',  
            this.linearGradient('0%', '#000'),
            this.linearGradient('50%', '#000', '0'),
            this.linearGradient('50%', '#fff', '0'),
            this.linearGradient('100%', '#fff'),
            'id', 'lumination', 'gradientUnits', 'objectBoundingBox',
            'x1', '50%', 'y1', '100%', 'x2', '50%', 'y2', '0%'
          ]
        ],
        this.rect(0, 0, 100, 1000, 0, 0, '#f00'),
        this.rect(0, 0, 100, 1000, 0, 0, 'url(#lumination)'),
        'viewBox', '0 0 100 1000'
      ],
      ['pointer-1-d',
        ['svg:svg',
          ['svg:g', 
            this.rect(-37, -2.5, 30, 5, 2),
            this.rect(7, -2.5, 30, 5, 2),
            'transform', 'translate(50,50)'
          ],
          'viewBox', '0 0 100 100'
        ]
      ],
      ['handler', 'class', 'z'],
    ]
  },

  linearGradient: function(offset, stop_color, stop_opacity)
  {
    return ['svg:stop', 'offset', offset, 'stop-color', stop_color].
      concat( stop_opacity ? ['stop-opacity', stop_opacity] : [] );
  },

  rect: function(x, y, width, height, rx, ry, fill)
  {
    var ret = ['svg:rect', 'x', x.toString(), 'y', y.toString(), 'width', width.toString(), 'height', height.toString()];
    if( rx ) ret = ret.concat(['rx', rx.toString()]);
    if( ry ) ret = ret.concat(['ry', ry.toString()]);
    if( fill ) ret = ret.concat(['fill', fill]);
    return ret;
  }
};

Node.controls.defaultStyles = Node.controls.defaultStyles || {};

Node.controls.defaultStyles = 
{
  'hue-saturation': "\
    hue-saturation-quadrat, hue-saturation-circel {\
      display:block;\
      position:absolute;\
      left:30px;\
      top:30px;\
      width:440px;\
      height:440px;\
      border:1px solid #333;\
      }\
      \
    hue-saturation-circel {\
      left:0;\
      top:0;  \
      border:none;\
      }\
      \
    pointer-2-d {\
      position:absolute;\
      display:block;\
      left:-40px;\
      top:-40px;\
      width:80px;\
      height:80px;\
      }\
      \
    pointer-2-d rect {\
      fill: #333;\
      }\
      \
    handler.x-y {\
      position:absolute;\
      left:0;\
      top:0;\
      width:100%;\
      height:100%;\
      cursor:crosshair;\
      }\
      ",
      
  'luminosity-axis': "\
    luminosity-axis {\
      display:block;\
      position:absolute;\
      left:520px;\
      top:30px;\
      width:44px;\
      height:440px;\
      border:1px solid #333;\
      }\
      \
    luminosity-axis pointer-1-d {\
      position:absolute;\
      display:block;\
      left:-18px;\
      top:-40px;\
      width:80px;\
      height:80px;\
      }\
      \
    luminosity-axis pointer-1-d rect {\
      fill: #333;\
      }\
      \
    luminosity-axis handler.z {\
      position:absolute;\
      left:-50%;\
      top:0;\
      width:200%;\
      height:100%;\
      cursor:crosshair;\
      }\
      "
}

Node.controls.addDefaultStyle = function(name)
{
  var has_default_style = false;

  var styles = document.getElementsByTagName('style'), p = null, i = 0;
  for( ; ( p = styles[i] ) && !( has_default_style = p.hasClass(name) ); i++);
  if( !has_default_style )
  {
    var head = document.getElementsByTagName('head')[0];
    if (head && head.firstChild)
    {
      p = head.insertBefore( document.render(['style', Node.controls.defaultStyles[name]]) , head.firstChild);
    }
  }
}

Node.controls['hue-saturation-quadrat'] = function()
{
  var color_control = document.render(Node.controls.templates['hue-saturation-quadrat']());

  var height = 0;
  var top = 0;
  var left = 0;
  var scale = 0;
  var pointer = null;
  var height_pointer = 0;

  Node.controls.addDefaultStyle('hue-saturation');

  var set_x = function(x)
  {
    var left = ( x * 1000 / 360 ) / scale;
    if( pointer )
    {
      pointer.style.left = ( left - height_pointer ) +'px';
    }
  }

  var set_y = function(y)
  {
    var top = ( ( 100 - y ) * 1000 / 100 ) / scale;
    if( pointer )
    {
      pointer.style.top = ( top - height_pointer ) +'px';
    }
  }

  var set_properties = function()
  {
   this.__defineSetter__('value_x', set_x);
   this.__defineSetter__('value_y', set_y);
  }

  var clickHandler = function(event)
  {
    var offsetX = event.offsetX || event.layerX;
    var offsetY = event.offsetY || event.layerY;
    var top = offsetY * scale;
    var left = offsetX * scale;
    pointer.style.left = ( offsetX - height_pointer ) +'px';
    pointer.style.top = ( offsetY - height_pointer ) +'px';
    var event=document.createEvent('Events');
    event.initEvent('input-color', true, false);
    event.value_x = ( left / 1000 * 360 ) >> 0;
    event.value_y = 100 - top / 1000 * 100  >> 0;
    color_control.dispatchEvent(event);
  }

  var init = function(event)
  {
    setTimeout(function(){ 
      if(event.target == color_control )
      {
        document.removeEventListener('DOMNodeInserted', arguments.callee, false);
        var comp_style = window.getComputedStyle(color_control, null);
        height = parseInt(comp_style.getPropertyValue('height'));
        scale = 1000 / height;
        pointer = color_control.getElementsByTagName('pointer-2-d')[0];
        comp_style = window.getComputedStyle(pointer, null);
        height_pointer = parseInt(comp_style.getPropertyValue('height')) / 2 >> 0;

        
        var handler = color_control.getElementsByTagName('handler')[0];
        if( handler )
        {
          handler.addEventListener('click', clickHandler, false);
        }
      }
    }, 0);
  }

  set_properties.apply(color_control);
  document.addEventListener('DOMNodeInserted', init, false);
  return color_control;

}

Node.controls['luminosity-axis'] = function()
{
  var color_control = document.render(Node.controls.templates['luminosity-axis']());

  var height = 0;
  var top = 0;
  var left = 0;
  var scale = 0;
  var pointer = null;
  var color_rect = null;
  var delta = 0;
  var pointer_height = 0;
  var color_converter = new Colors();

  Node.controls.addDefaultStyle('luminosity-axis');

  var set_z = function(z)
  {
    var top = ( ( 100 - z ) * 1000 / 100 ) / scale;
    if( pointer )
    {
      pointer.style.top = ( top - pointer_height ) +'px';
    }
  }

  var set_hue = function(hsl_color)
  {
    color_converter.setHue(hsl_color[0]);
    color_converter.setSaturation(hsl_color[1]);
    color_converter.setLuminosity(50);
    if( color_rect )
    {
      color_rect.setAttribute('fill', '#' + color_converter.getHex());
    }
  }

  var set_properties = function()
  {
   this.__defineSetter__('value_z', set_z);
   this.__defineSetter__('color', set_hue);
  }

  var releaseEvent = function(top)
  {
    var event=document.createEvent('Events');
    event.initEvent('input-color', true, false);
    event.value_z = 100 - top / 1000 * 100  >> 0;
    color_control.dispatchEvent(event);
  }
  
  var mousemoveHandler = function(event)
  {
    var top = ( event.pageY - delta ) * scale;
    if( top >= 0 && top <= 1000 )
    {
      pointer.style.top = ( event.pageY - delta - pointer_height ) +'px';
      releaseEvent(top);
    }
    else if ( top < 0)
    {
      pointer.style.top = '-' + pointer_height + 'px';
      releaseEvent(0);
    }
    else
    {
      pointer.style.top = ( height - pointer_height ) +'px';
      releaseEvent(1000);
    }
    return false;
  }

  var mouseupHandler = function(event)
  {
    document.removeEventListener('mousemove', mousemoveHandler, false);
    document.removeEventListener('mouseup', mouseupHandler, false);
  }

  var clickHandler = function(event)
  {
    var offsetY = event.offsetY || event.layerY;
    var top = offsetY * scale;
    delta = event.pageY - offsetY;
    document.addEventListener('mousemove', mousemoveHandler, false);
    document.addEventListener('mouseup', mouseupHandler, false);
    pointer.style.top = ( offsetY - pointer_height ) +'px';
    releaseEvent(top);
  }

  var init = function(event)
  {
    setTimeout( function(){
      if(event.target == color_control )
      {
        document.removeEventListener('DOMNodeInserted', arguments.callee, false);
        var comp_style = window.getComputedStyle(color_control, null);
        height = parseInt(comp_style.getPropertyValue('height'));
        scale = 1000 / height;
        pointer = color_control.getElementsByTagName('pointer-1-d')[0];
        comp_style = window.getComputedStyle(pointer, null);
        pointer_height = parseInt(comp_style.getPropertyValue('height')) / 2 >> 0;
        color_rect = color_control.getElementsByTagName('rect')[0];
        var handler = color_control.getElementsByTagName('handler')[0];
        if( handler )
        {
          handler.addEventListener('mousedown', clickHandler, false);
        }
      }
    }, 0)
  }

  set_properties.apply(color_control);
  document.addEventListener('DOMNodeInserted', init, false);
  return color_control;

}

Node.controls['hue-saturation-circel'] = function()
{
  var color_control = document.render(Node.controls.templates['hue-saturation-circel']());

  var height = 0;
  var top = 0;
  var left = 0;
  var scale = 0;
  var pointer = null;
  var height_pointer = 0;

  Node.controls.addDefaultStyle('hue-saturation');

  var set_x = function(x)
  {
    var left = ( x * 1000 / 360 ) / scale;
    if( pointer )
    {
      pointer.style.left = ( left - height_pointer ) +'px';
    }
  }

  var set_pos = function(arr)
  {
    setPointer(arr[0], arr[1])
  }

  var set_properties = function()
  {
   this.__defineSetter__('position', set_pos);
  }

  var setPointer = function(hue, sat)
  {
    var left =0, top = 0, x = 0, y = 0;
    sat *= 5;
    x = sat * Math.sin(hue / 180 * Math.PI);
    y = sat * Math.cos(hue / 180 * Math.PI);
    left = 500 + x;
    top = 500 - y;
    left /= scale;
    top /= scale;
    pointer.style.left = ( left - height_pointer ) +'px';
    pointer.style.top = ( top - height_pointer ) +'px';
  }
  var clickHandler = function(event)
  {
    var offsetX = event.offsetX || event.layerX || 0;
    var offsetY = event.offsetY || event.layerY || 0;
    var top = offsetY * scale;
    var left = offsetX * scale;
    var x = 0, y = 0, hue = 0, sat = 0;
    if( left >= 500 )
    {
      left -= 500; 
      if( top >= 500 )
      {
        top -= 500;
        x = top;
        y = left;
        hue = 90;
      }
      else
      {
        x = left;
        y = 500 - top;
      }
    }
    else
    {
      if( top >= 500 )
      {
        top -= 500;
        x = 500 - left;
        y = top;
        hue = 180; 
      }
      else
      {
        x = 500 - top;
        y = 500 - left;
        hue = 270;
      }   
    }
    hue += Math.atan(x/y) / Math.PI * 180;
    sat = Math.pow(x*x + y*y, .5) / 5;
    if( sat > 100 ) sat = 100;
    setPointer(hue, sat);
    var event=document.createEvent('Events');
    event.initEvent('input-color', true, false);
    event.value_x = hue;
    event.value_y = sat;
    color_control.dispatchEvent(event);
  }

  var init = function(event)
  {
    setTimeout(function(){ 
      if(event.target == color_control )
      {
        document.removeEventListener('DOMNodeInserted', arguments.callee, false);
        var comp_style = window.getComputedStyle(color_control, null);
        height = parseInt(comp_style.getPropertyValue('height'));
        scale = 1000 / height;
        pointer = color_control.getElementsByTagName('pointer-2-d')[0];
        comp_style = window.getComputedStyle(pointer, null);
        height_pointer = parseInt(comp_style.getPropertyValue('height')) / 2 >> 0;
        clickHandler({offsetX:500/scale, offsetY:0})
        
        var handler = color_control.getElementsByTagName('handler')[0];
        if( handler )
        {
          handler.addEventListener('click', clickHandler, false);
        }
      }
    }, 0);
  }

  set_properties.apply(color_control);
  document.addEventListener('DOMNodeInserted', init, false);
  
  return color_control;

}

Document.prototype.createControl = function(name)
{
  return ( name in Node.controls ) ? new Node.controls[name]() : null;
}

var setCookie = function(name, value, time) 
{
	document.cookie = name+"="+encodeURIComponent(value)+
		"; expires="+(new Date(new Date().getTime()+time)).toGMTString()+"; path=/";
}

var getCookie = function(name) {
	if( new RegExp(name+'\=([^;]*);','').test(document.cookie+';') ) return decodeURIComponent(RegExp.$1);
	return null;
}