/*
 * Terra Informatica Lightweight Embeddable HTMLayout control
 * http://terrainformatica.com/htmlayout
 * 
 * HTMLayout DOM implementation. C++ wrapper
 * 
 * The code and information provided "as-is" without
 * warranty of any kind, either expressed or implied.
 * 
 * (C) 2003-2004, Andrew Fedoniouk (andrew@terrainformatica.com)
 */

/**\file
 * \brief \link htmlayout_dom.h DOM \endlink C++ wrapper
 **/

#ifndef __htmlayout_dom_hpp__
#define __htmlayout_dom_hpp__

#include "value.h"

#include "htmlayout_dom.h"
#include "htmlayout_aux.h"

#include "htmlayout_queue.h"

#include <assert.h>
#include <stdio.h> // for vsnprintf


#pragma warning(disable:4786) //identifier was truncated...
#pragma warning(disable:4996) //'strcpy' was declared deprecated
#pragma warning(disable:4100) //unreferenced formal parameter 

#pragma once

/**htmlayout namespace.*/
namespace htmlayout
{

  /**dom namespace.*/
  namespace dom 
  {

	/**callback structure.
	 * Used with #htmlayout::dom::element::select() function. 
	 **/
    struct callback 
    {
	  /**Is called for every element that match criteria specified when calling to #htmlayout::dom::element::select() function.*/
      virtual bool on_element(HELEMENT he) = 0;
    };
    class expando; // DOM element expando structure

	/**DOM element. 
     Smart pointer, pretty much std::shared_ptr thing */
   
    class element
    {
    protected:
      HELEMENT he;

      void use(HELEMENT h) { he = (HTMLayout_UseElement(h) == HLDOM_OK)? h: 0; }
      void unuse() { if(he) HTMLayout_UnuseElement(he); he = 0; }
      void set(HELEMENT h) { unuse(); use(h); }

    public:
	  /**Construct \c undefined element .
	   **/
      element(): he(0) { }

    /**Construct \c element from existing element handle.
	   * \param h \b #HELEMENT
	   **/
      element(HELEMENT h)       { use(h); }

	  /**Copy constructor;
	   * \param e \b #element
	   **/
      element(const element& e) { use(e.he); }

      operator HELEMENT() const { return he; }

	  /**Destructor.*/
      ~element()                { unuse(); }


	  /**Assign \c element an \c #HELEMENT
	   * \param h \b #HELEMENT
	   * \return \b #element&
	   **/
      element& operator = (HELEMENT h) { set(h); return *this; }

	  /**Assign \c element another \c #element
	   * \param e \b #element
	   * \return \b #element&
	   **/
      element& operator = (const element& e) { set(e.he); return *this; }

	  /**Test equality of this and another \c #element's
	   * \param rs \b const \b #element 
	   * \return \b bool, true if elements are equal, false otherwise
	   **/
      bool operator == (const element& rs ) const { return he == rs.he; }
      bool operator == (HELEMENT rs ) const { return he == rs; }

	  /**Test equality of this and another \c #element's
	   * \param rs \b const \b #element 
	   * \return \b bool, true if elements are not equal, false otherwise
	   **/
      bool operator != (const element& rs ) const { return he != rs.he; }

	  /**Test whether element is valid.
	   * \return \b bool, true if element is valid, false otherwise
	   **/
      bool is_valid() const { return he != 0; }

	  /**Tests whether element is memeber of the DOM or is it detached.
	   * \return \b bool, true if element is in the DOM, false otherwise
	   **/
      bool is_alive() const { return root() != 0; }


	  /**Get number of child elements.
	   * \return \b int, number of child elements
	   **/
      unsigned int children_count() const 
      { 
        UINT count = 0;
        HTMLayoutGetChildrenCount(he, &count);
        return count;
      }

	  /**Get Nth child element.
	   * \param index \b unsigned \b int, number of the child element
	   * \return \b #HELEMENT, child element handle
	   **/
      HELEMENT child( unsigned int index ) const 
      { 
        HELEMENT child = 0;
        HTMLayoutGetNthChild(he, index, &child);
        return child;
      }

	  /**Get parent element.
	   * \return \b #HELEMENT, handle of the parent element
	   **/
      HELEMENT parent( ) const 
      { 
        HELEMENT hparent = 0;
        HTMLayoutGetParentElement(he, &hparent);
        return hparent;
      }

	  /**Get index of this element in its parent collection.
	   * \return \b unsigned \b int, index of this element in its parent collection
	   **/
      unsigned int index( ) const 
      { 
        UINT index = 0;
        HTMLayoutGetElementIndex(he, &index);
        return index;
      }

	  /**Get number of the attributes.
	   * \return \b unsigned \b int, number of the attributes
	   **/
      unsigned int get_attribute_count( ) const 
      { 
        UINT n = 0;
        HTMLayoutGetAttributeCount(he, &n);
        return n;
      }

	  /**Get attribute value by its index.
	   * \param n \b unsigned \b int, number of the attribute
	   * \return \b const \b wchar_t*, value of the n-th attribute
	   **/
      const wchar_t* get_attribute( unsigned int n ) const 
      { 
        LPCWSTR lpw = 0;
        HTMLayoutGetNthAttribute(he, n, 0, &lpw);
        return lpw;
      }

	  /**Get attribute name by its index.
	   * \param n \b unsigned \b int, number of the attribute
	   * \return \b const \b char*, name of the n-th attribute
	   **/
      const char* get_attribute_name( unsigned int n ) const 
      { 
        LPCSTR lpc = 0;
        HTMLayoutGetNthAttribute(he, n, &lpc, 0);
        return lpc;
      }

	  /**Get attribute value by name.
	   * \param name \b const \b char*, name of the attribute
	   * \return \b const \b wchar_t*, value of the n-th attribute
	   **/
      const wchar_t* get_attribute( const char* name ) const 
      { 
        LPCWSTR lpw = 0;
        HTMLayoutGetAttributeByName(he, name, &lpw);
        return lpw;
      }

	  /**Add or replace attribute.
	   * \param name \b const \b char*, name of the attribute
	   * \param value \b const \b wchar_t*, name of the attribute
	   **/
	  void set_attribute( const char* name, const wchar_t* value )
      { 
        HTMLayoutSetAttributeByName(he, name, value);
      }

	  /**Get attribute integer value by name.
	   * \param name \b const \b char*, name of the attribute
	   * \return \b int , value of the attribute
	   **/
      int get_attribute_int( const char* name, int def_val = 0 ) const 
      { 
        const wchar_t* txt = get_attribute(name);
        if(!txt) return def_val;
        return _wtoi(txt);
      }

	  /**Special form of get attribute value by name, it tries to get value from attribute 
     * collection and if fails then from custom style attribute (that has to start from '-')
	   * \param name \b const \b char*, name of the attribute that shall start from "-" sign
	   * \return \b const wchar_t* , value of the attribute
	   **/
      const wchar_t* attribute( const char* name, const wchar_t* default_value ) const 
      { 
        assert(name[0] == '-');
        const wchar_t* txt = get_attribute(name+1);
        if(txt) return txt;
        txt = get_style_attribute(name);
        if(txt) return txt;
        return default_value;
      }
      int attribute( const char* name, int default_value ) const 
      { 
        wchar_t none[1] = {0};        
        const wchar_t* txt = attribute( name, none );
        if(txt == none) return default_value;
        return aux::wtoi(txt,default_value);
      }

      color attribute( const char* name, color default_value ) const 
      { 
        wchar_t none[1] = {0};
        const wchar_t* txt = attribute( name, none );
        if(txt == none) return default_value;
        return color::parse(aux::chars_of(txt), default_value);
      }


      
	  /**Remove attribute.
	   * \param name \b const \b char*, name of the attribute
	   **/
	  void remove_attribute( const char* name ) 
      { 
        HTMLayoutSetAttributeByName(he, name, 0);
      }
      

	  /**Get style attribute of the element by its name.
	   * \param name \b const \b char*, name of the style attribute, e.g. "background-color"
	   * \return \b const \b wchar_t*, value of the style attribute
	   *
	   * Also all style attributes of the element are available in "style" attribute of the element.
	   **/
	  const wchar_t* get_style_attribute( const char* name ) const 
      { 
        LPCWSTR lpw = 0;
        HTMLayoutGetStyleAttribute(he, name, &lpw);
        return lpw;
      }

	  /**Set style attribute.
	   * \param name \b const \b char*, name of the style attribute
	   * \param value \b const \b wchar_t*, value of the style attribute
	   *
	   * \par Example:
	   * \code e.set_style_attribute("background-color", L"red"); \endcode
	   **/
	  void set_style_attribute( const char* name, const wchar_t* value )  
      { 
        HTMLayoutSetStyleAttribute(he, name, value);
      }

	  /** Clear style attribute that was defined by set_style_attribute.
	   * \param name \b const \b char*, name of the style attribute
	   *
	   * \par Example:
	   * \code e.clear_style_attribute("background-color"); \endcode
	   **/
	  void clear_style_attribute( const char* name )  
      { 
        HTMLayoutSetStyleAttribute(he, name, 0);
      }

	  /** Clear all style attribute that was defined by set_style_attribute.
	   */
	  void clear_all_style_attributes() 
      { 
        HTMLayoutSetStyleAttribute(he, 0, 0);
      }


	  /**Get root DOM element of the HTMLayout document.
	   * \param hHTMLayoutWnd \b HWND, HTMLayout window
	   * \return \b #HELEMENT, root element
     * \see also \b #root
	   **/
      static HELEMENT root_element(HWND hHTMLayoutWnd)
      {
        HELEMENT h = 0;
        HTMLayoutGetRootElement(hHTMLayoutWnd,&h);
        return h;
      }

	  /**Get focus DOM element of the HTMLayout document.
	   * \param hHTMLayoutWnd \b HWND, HTMLayout window
	   * \return \b #HELEMENT, focus element
     *
     * COMMENT: to set focus use: set_state(STATE_FOCUS)
     *
	   **/
      static HELEMENT focus_element(HWND hHTMLayoutWnd)
      {
        HELEMENT h = 0;
        HTMLayoutGetFocusElement(hHTMLayoutWnd,&h);
        return h;
      }
     

	  /**Find DOM element of the HTMLayout document by coordinates.
	   * \param hHTMLayoutWnd \b HWND, HTMLayout window
     * \param clientPt \b POINT,  coordinates.
	   * \return \b #HELEMENT, found element handle or zero
	   **/
      static HELEMENT find_element(HWND hHTMLayoutWnd, POINT clientPt)
      {
        HELEMENT h = 0;
        HTMLayoutFindElement(hHTMLayoutWnd, clientPt, &h);
        return h;
      }

     
	  /**Set mouse capture.
	   * After call to this function all mouse events will be targeted to this element.
	   * To remove mouse capture call #htmlayout::dom::element::release_capture().
	   **/
      void set_capture() { HTMLayoutSetCapture(he); }

	  /**Release mouse capture.
	   * Mouse capture can be set with #element:set_capture()
	   **/
      static void release_capture() { ReleaseCapture(); }

      inline static BOOL CALLBACK callback_func( HELEMENT he, LPVOID param )
      {
        callback *pcall = (callback *)param;
        return pcall->on_element(he)? TRUE:FALSE; // TRUE - stop enumeration
      }

	  /**Enumerate all descendant elements.
	   * \param pcall \b #htmlayout::dom::callback*, callback structure. Its member function #htmlayout::dom::callback::on_element() is called for every enumerated element.
	   * \param tag_name \b const \b char*, comma separated list of tag names to search, e.g. "div", "p", "div,p" etc. Can be NULL.
	   * \param attr_name \b const \b char*, name of attribute, can contain wildcard characters, see below. Can be NULL.
	   * \param attr_value \b const \b char*, name of attribute, can contain wildcard characters, see below. Can be NULL.
	   * \param depth \b int, depth - depth of search. 0 means all descendants, 1 - direct children only, 
	   * 2 - children and their children and so on.
	   *
	   * Wildcard characters in attr_name and attr_value:
	   * - '*' - any substring
	   * - '?' - any one char
	   * - '['char set']' = any one char in set
	   *
	   * \par Example:
	   * - [a-z] - all lowercase letters
       * - [a-zA-Z] - all letters
	   * - [abd-z] - all lowercase letters except of 'c'
	   * - [-a-z] - all lowercase letters and '-'
	   *
	   * \par Example:
	   * \code document.select(mycallback, "a", "href", "http:*.php"); \endcode
       * will call mycallback.on_element() on each <A> element in the document 
       *           having 'href' attribute with value
       *           starting from "http:" and ending with ".php"
	   *
	   * \par Example:
	   * \code document.select(mycallback); \endcode
	   * will enumerate ALL elements in the document.
	   **/
      inline void select( callback *pcall,  
        const char* tag_name = 0, 
        const char* attr_name = 0, 
        const wchar_t* attr_value = 0, 
        int depth = 0) const
      {
        HTMLayoutVisitElements( he, tag_name, attr_name, attr_value, callback_func, pcall, depth);
      }

      inline void select_elements( callback *pcall,  
                          const char* selectors // CSS selectors, comma separated list
                        ) const
      {
        HTMLayoutSelectElements( he, selectors, callback_func, pcall);
      }

      inline void select_elements( callback *pcall,  
                          const wchar_t* selectors // CSS selectors, comma separated list
                        ) const
      {
        HTMLayoutSelectElementsW( he, selectors, callback_func, pcall);
      }

 
     /**Get element by id.
 	   * \param id \b char*, value of the "id" attribute.
	   * \return \b #HELEMENT, handle of the first element with the "id" attribute equal to given.
 	   **/
       HELEMENT get_element_by_id(const char* id) const
       {
         find_first_callback cb;
         select(&cb,0,"id",aux::a2w(id));
         return cb.hfound;
       }

       HELEMENT get_element_by_id(const wchar_t* id) const
       {
         find_first_callback cb;
         select(&cb,0,"id",id);
         return cb.hfound;
       }

 
 	  /** Request update of styles and/or position of the element and refresh element area in its window.
 	   * \param[in] remeasure \b bool, true if method element will be redrawn immediately.
     *
     * This method is optional since v 3.3.0.4   
     * Engine will call update internaly when handling DOM mutating methods.  
     *
 	   **/
       void update( bool remeasure = false ) const 
       { 
          HTMLayoutUpdateElement(he, remeasure? TRUE:FALSE); 
       }

 	  /**Apply changes and refresh element area in its window.
 	   * \param[in] render_now \b bool, if true element will be redrawn immediately.
 	   **/
       void update( int mode ) const 
       { 
          HTMLayoutUpdateElementEx(he, mode); 
       }

 
 	  /**Get next sibling element.
 	   * \return \b #HELEMENT, handle of the next sibling element if it exists or 0 otherwise
 	   **/
       HELEMENT next_sibling() const 
       {
         unsigned int idx = index() + 1;
         element pel = parent();
         if(!pel.is_valid())
          return 0;
        if( idx >= pel.children_count() )
          return 0;
        return pel.child(idx);
      }

 	  /**Get previous sibling element.
 	   * \return \b #HELEMENT, handle of previous sibling element if it exists or 0 otherwise
 	   **/
      HELEMENT prev_sibling() const 
      {
        int idx = (int)index() - 1;
        element pel = parent();
        if(!pel.is_valid())
          return 0;
        if( idx < 0 )
          return 0;
        return pel.child(idx);
      }

 	  /**Get first sibling element.
 	   * \return \b #HELEMENT, handle of the first sibling element if it exists or 0 otherwise
 	   **/
       HELEMENT first_sibling() const 
       {
         element pel = parent();
         if(!pel.is_valid())
          return 0;
        return pel.child(0);
      }

 	  /**Get last sibling element.
 	   * \return \b #HELEMENT, handle of last sibling element if it exists or 0 otherwise
 	   **/
      HELEMENT last_sibling() const 
      {
         element pel = parent();
         if(!pel.is_valid())
          return 0;
        return pel.child(pel.children_count() - 1);
      }


 	  /**Get root of the element
 	   * \return \b #HELEMENT, handle of document root element (html)
 	   **/
      HELEMENT root() const 
      {
        element pel = parent();
        if(pel.is_valid()) return pel.root();
        return he;
      }


	  /**Get bounding rectangle of the element.
	   * \param root_relative \b bool, if true function returns location of the 
	   * element relative to HTMLayout window, otherwise the location is given 
	   * relative to first scrollable container.
	   * \return \b RECT, bounding rectangle of the element.
	   **/
      RECT get_location(unsigned int area = ROOT_RELATIVE | CONTENT_BOX) const
      {
        RECT rc = {0,0,0,0};
        HTMLayoutGetElementLocation(he,&rc, area);
        return rc;
      }

      /** Test if point is inside shape rectangle of the element.
        client_pt - client rect relative point          
       **/
      bool is_inside( POINT client_pt ) const
      {
        RECT rc = get_location(ROOT_RELATIVE | BORDER_BOX);
        return PtInRect(&rc,client_pt) != FALSE;
      }


	    /**Scroll this element to view.
	     **/
      void scroll_to_view(bool toTopOfView = false, bool smooth = false)
      {
        UINT flags = 0;
        if(toTopOfView) flags |= SCROLL_TO_TOP;
        if(smooth) flags |= SCROLL_SMOOTH;
        HTMLayoutScrollToView(he,flags);
      }

      void get_scroll_info(POINT& scroll_pos, RECT& view_rect, SIZE& content_size)
      {
        HLDOM_RESULT r = HTMLayoutGetScrollInfo(he, &scroll_pos, &view_rect, &content_size);
        assert(r == HLDOM_OK); r;
      }
      void set_scroll_pos(POINT scroll_pos, bool smooth = true)
      {
        HLDOM_RESULT r = HTMLayoutSetScrollPos(he, scroll_pos, BOOL(smooth));
        assert(r == HLDOM_OK); r;
      }


      /**Get element's type.
	   * \return \b const \b char*, name of the elements type
	   * 
       * \par Example:
       * For &lt;div&gt; tag function will return "div".
	   **/
      const char* get_element_type() const
      {
        LPCSTR str = 0;
        HTMLayoutGetElementType(he,&str);
        return str;
      }

    /**Get HWND of containing window.
	   * \param root_window \b bool, handle of which window to get:
	   * - true - HTMLayout window
	   * - false - nearest parent element having overflow:auto or :scroll
	   * \return \b HWND
	   **/
      HWND get_element_hwnd(bool root_window)
      {
        HWND hwnd = 0;
        HTMLayoutGetElementHwnd(he,&hwnd, root_window? TRUE : FALSE);
        return hwnd;
      }

    /**Get element UID - identifier suitable for storage.
	   * \return \b UID
	   **/
      UINT get_element_uid()
      {
        UINT uid = 0;
        HTMLayoutGetElementUID(he,&uid);
        return uid;
      }

 	  /**Get element handle by its UID.
	   * \param hHTMLayoutWnd \b HWND, HTMLayout window
     * \param uid \b UINT, uid of the element
	   * \return \b #HELEMENT, handle of element with the given uid or 0 if not found
	   **/
      static HELEMENT element_by_uid(HWND hHTMLayoutWnd, UINT uid)
      {
        HELEMENT h = 0;
        HTMLayoutGetElementByUID(hHTMLayoutWnd, uid,&h);
        return h;
      }

	  /**Combine given URL with URL of the document element belongs to.
	   * \param[in, out] inOutURL \b LPWSTR, at input this buffer contains 
	   * zero-terminated URL to be combined, after function call it contains 
	   * zero-terminated combined URL
	   * \param bufferSize \b UINT, size of the buffer pointed by \c inOutURL
       **/
      void combine_url(LPWSTR inOutURL, UINT bufferSize)
      {
        HTMLayoutCombineURL(he,inOutURL,bufferSize);
      }

	  /**Set inner or outer html of the element.
	   * \param html \b const \b unsigned \b char*, UTF-8 encoded string containing html text
	   * \param html_length \b size_t, length in bytes of \c html
	   * \param where \b int, possible values are:
	   * - SIH_REPLACE_CONTENT - replace content of the element
	   * - SIH_INSERT_AT_START - insert html before first child of the element
	   * - SIH_APPEND_AFTER_LAST - insert html after last child of the element
	   **/
      void set_html( const unsigned char* html, size_t html_length, int where = SIH_REPLACE_CONTENT)
      { 
        if(html == 0 || html_length == 0)
          clear();
        else
        {
          HLDOM_RESULT r = HTMLayoutSetElementHtml(he, html, DWORD(html_length), where);
          assert(r == HLDOM_OK); r;
        }
      }

      const unsigned char* 
          get_html( bool outer = true) const 
      { 
        unsigned char* utf8bytes = 0;
        HLDOM_RESULT r = HTMLayoutGetElementHtml(he, &utf8bytes, outer? TRUE:FALSE);
        assert(r == HLDOM_OK); r;
        return utf8bytes;
      }

      // get text as utf8 bytes
      const unsigned char* get_text() const
      {
        unsigned char* utf8bytes = 0;
        HLDOM_RESULT r = HTMLayoutGetElementInnerText(he, &utf8bytes);
        assert(r == HLDOM_OK); r;
        return utf8bytes;
      }

      // get text as wchar_t words 
      const wchar_t* text() const
      {
        wchar_t* utf16words = 0;
        HLDOM_RESULT r = HTMLayoutGetElementInnerText16(he, &utf16words);
        assert(r == HLDOM_OK); r;
        return utf16words;
      }

      void  set_text(const wchar_t* utf16, size_t utf16_length)
      {
        HLDOM_RESULT r = HTMLayoutSetElementInnerText16(he, utf16, (UINT)utf16_length);
        assert(r == HLDOM_OK); r;
      }

      void  set_text(const wchar_t* t)
      {
        assert(t);
        if( t ) set_text( t, wcslen(t) );
      }

      void clear() // clears content of the element
      {
        HLDOM_RESULT r = HTMLayoutSetElementInnerText16(he, L"", 0);
        assert(r == HLDOM_OK); r;
      }


	  /**Delete element.
	   * This function removes element from the DOM tree and then deletes it.
	   **/
      void destroy() 
      {
        HTMLayoutDeleteElement(he);
        unuse();
      }

      // Find first child element matching the selector
      // :root is the element itself
      HELEMENT find_first( const char* selector, ... ) const
      {
        char buffer[2049]; buffer[0]=0;
        va_list args;
        va_start ( args, selector );
        _vsnprintf( buffer, 2048, selector, args );
        va_end ( args );
        find_first_callback find_first;
        select_elements( &find_first, buffer); // find first element satisfying given CSS selector
        //assert(find_first.hfound);
        return find_first.hfound;
      }

      // Find first child element matching the selector
      // :root is the element itself
      HELEMENT find_first( const wchar_t* selector, ... ) const
      {
        wchar_t buffer[2049]; buffer[0]=0;
        va_list args;
        va_start ( args, selector );
        _vsnwprintf( buffer, 2048, selector, args );
        va_end ( args );
        find_first_callback find_first;
        select_elements( &find_first, aux::w2a(buffer)); // find first element satisfying given CSS selector
        //assert(find_first.hfound);
        return find_first.hfound;
      }

      // Find all child elements matching the selector
      // :root is the element itself
      void find_all( callback* cb, const char* selector, ... ) const
      {
        char buffer[2049]; buffer[0]=0;
        va_list args;
        va_start ( args, selector );
        _vsnprintf( buffer, 2048, selector, args );
        va_end ( args );
        select_elements( cb, buffer); // find all elements satisfying given CSS selector
        //assert(find_first.hfound);
      }

      // Find all child elements matching the selector
      // :root is the element itself
      void find_all( callback* cb, const wchar_t* selector, ... ) const
      {
        wchar_t buffer[2049]; buffer[0]=0;
        va_list args;
        va_start ( args, selector );
        _vsnwprintf( buffer, 2048, selector, args );
        va_end ( args );
        select_elements( cb, buffer); // find all elements satisfying given CSS selector
        //assert(find_first.hfound);
      }

      // will find first parent satisfying given css selector(s), will check element itself
      // :root is document root object
      HELEMENT find_nearest_parent(const char* selector, ...) const
      {
        char buffer[2049]; buffer[0]=0;
        va_list args;
        va_start ( args, selector );
        _vsnprintf( buffer, 2048, selector, args );
        va_end ( args );
        
        HELEMENT heFound = 0;
        HLDOM_RESULT r = HTMLayoutSelectParent(he, buffer, 0, &heFound);
        assert(r == HLDOM_OK); r;
        return heFound;
      }

      // will find first parent satisfying given css selector(s), will check element itself
      // :root is document root object
      HELEMENT find_nearest_parent(const wchar_t* selector, ...) const
      {
        wchar_t buffer[2049]; buffer[0]=0;
        va_list args;
        va_start ( args, selector );
        _vsnwprintf( buffer, 2048, selector, args );
        va_end ( args );
        
        HELEMENT heFound = 0;
        HLDOM_RESULT r = HTMLayoutSelectParentW(he, buffer, 0, &heFound);
        assert(r == HLDOM_OK); r;
        return heFound;
      }

      // test this element against CSS selector(s) 
      // :root is document root object
      bool test(const char* selector, ...) const
      {
        char buffer[2049]; buffer[0]=0;
        va_list args;
        va_start ( args, selector );
        _vsnprintf( buffer, 2048, selector, args );
        va_end ( args );
        HELEMENT heFound = 0;
        HLDOM_RESULT r = HTMLayoutSelectParent(he, buffer, 1, &heFound);
        assert(r == HLDOM_OK); r;
        return heFound != 0;
      }

      // test this element against CSS selector(s) 
      // :root is document root object
      bool test(const wchar_t* selector, ...) const
      {
        wchar_t buffer[2049]; buffer[0]=0;
        va_list args;
        va_start ( args, selector );
        _vsnwprintf( buffer, 2048, selector, args );
        va_end ( args );
        HELEMENT heFound = 0;
        HLDOM_RESULT r = HTMLayoutSelectParentW(he, buffer, 1, &heFound);
        assert(r == HLDOM_OK); r;
        return heFound != 0;
      }


    /**Get UI state bits of the element as set of ELEMENT_STATE_BITS 
	   **/
      unsigned int get_state() const
      {
        UINT state = 0;
        HLDOM_RESULT r = HTMLayoutGetElementState(he,&state);
        assert(r == HLDOM_OK); r;
        return (ELEMENT_STATE_BITS) state;
      }

    /**Checks if particular UI state bits are set in the element.
	   **/
      bool get_state(/*ELEMENT_STATE_BITS*/ unsigned int bits) const
      {
        UINT state = 0;
        HLDOM_RESULT r = HTMLayoutGetElementState(he,&state);
        assert(r == HLDOM_OK); r;
        return (state & bits) != 0;
      }


    /**Set UI state of the element with optional view update.
	   **/
      void set_state(
        /*ELEMENT_STATE_BITS*/ unsigned int bitsToSet, 
        /*ELEMENT_STATE_BITS*/ unsigned int bitsToClear = 0, bool update = true )
      {
        HLDOM_RESULT r = HTMLayoutSetElementState(he,bitsToSet,bitsToClear, update?TRUE:FALSE);
        assert(r == HLDOM_OK); r;
      }

      void toggle_state(/*ELEMENT_STATE_BITS*/ unsigned int bitsToSet, bool onOff)
      {
        HLDOM_RESULT r = HTMLayoutSetElementState(he,onOff?bitsToSet:0, onOff?0:bitsToSet, TRUE);
        assert(r == HLDOM_OK); r;
      }


      void start_timer(UINT millis, UINT tid = 0)
      {
        HLDOM_RESULT r = HTMLayoutSetTimer(he, millis);
        //HTMLayoutSetTimerEx(he, millis, tid);
        assert(r == HLDOM_OK); r;
      }

      void stop_timer(UINT tid = 0)
      {
        HLDOM_RESULT r = HTMLayoutSetTimer(he, 0);
        //HTMLayoutSetTimerEx(he, 0, tid);
        assert(r == HLDOM_OK); r;
      }

      /** "deeply enabled" **/
      bool enabled() 
      {
        BOOL b = FALSE;
        HLDOM_RESULT r = HTMLayoutIsElementEnabled(he,&b);
        assert(r == HLDOM_OK); r;
        return b != 0;
      }

      /** "deeply visible" **/
      bool visible() 
      {
        BOOL b = FALSE;
        HLDOM_RESULT r = HTMLayoutIsElementVisible(he,&b);
        assert(r == HLDOM_OK); r;
        return b != 0;
      }


    /** create brand new element with text (optional).
        Example:
           element div = element::create("div");
        - will create DIV element,
           element opt = element::create("option",L"Europe");
        - will create OPTION element with text "Europe" in it.
	   **/
      static element create(const char* tagname, const wchar_t* text = 0)
      {
         element e(0);
         HLDOM_RESULT r = HTMLayoutCreateElement( tagname, text, &e.he ); // don't need 'use' here, as it is already "addrefed" 
         assert(r == HLDOM_OK); r;
         return e;
      }

    /** create brand new copy of this element. Element will be created disconected.
        You need to call insert to inject it in some container.
        Example:
           element select = ...;
           element option1 = ...;
           element option2 = option1.clone();
            select.insert(option2, option1.index() + 1);
        - will create copy of option1 element (option2) and insert it after option1,
	   **/
      element clone()
      {
         element e(0);
         HLDOM_RESULT r = HTMLayoutCloneElement( he, &e.he ); // don't need 'use' here, as it is already "addrefed" 
         assert(r == HLDOM_OK); r;
         return e;
      }


    /** Insert element e at \i index position of this element.
     **/
      void insert( const element& e, unsigned int index )
      {
         HLDOM_RESULT r = HTMLayoutInsertElement( e.he, this->he, index );
         assert(r == HLDOM_OK); r;
      }

    /** Append element e as last child of this element.
     **/
      void append( const element& e ) { insert(e,0x7FFFFFFF); }


     /** detach - remove this element from its parent
      **/
      void detach()
      {
        HLDOM_RESULT r = HTMLayoutDetachElement( he );
        assert(r == HLDOM_OK); r;
      }

     /** swap two elements in the DOM
      **/
      void swap(HELEMENT with)
      {
         HTMLayoutSwapElements(he, with);
      }

      /** traverse event - send it by sinking/bubbling on the 
       * parent/child chain of this element
       **/
      bool send_event(unsigned int event_code, UINT_PTR reason = 0, HELEMENT heSource = 0)
      {
        BOOL handled = FALSE;
        HLDOM_RESULT r = HTMLayoutSendEvent(he, event_code, heSource? heSource: he, reason, &handled);
        assert(r == HLDOM_OK); r;
        return handled != 0;
      }

      /** post event - post it in the queue for later sinking/bubbling on the 
       * parent/child chain of this element.
       * method returns immediately
       **/
      void post_event(unsigned int event_code, unsigned int reason = 0, HELEMENT heSource = 0)
      {
        HLDOM_RESULT r = HTMLayoutPostEvent(he, event_code, heSource? heSource: he, reason);
        assert(r == HLDOM_OK); r;
      }

      /** move element to new location given by x_view_rel, y_view_rel - view relative coordinates.
       *  Method defines local styles, so to "stick" it back to the original location you
       *  should call element::clear_all_style_attributes().
       **/
      void move(int x_view_rel, int y_view_rel)
      {
        HLDOM_RESULT r = HTMLayoutMoveElement(he, x_view_rel, y_view_rel);
        assert(r == HLDOM_OK); r;
      }

      /** attach event handler to the element
       **/
      void attach(event_handler* pevth)
      {
        htmlayout::attach_event_handler(he,pevth);
      }
      /** remove event handler from the list of event handlers of the element.
       **/
      void detach(event_handler* pevth)
      {
        htmlayout::detach_event_handler(he,pevth);
      }

      /** call method, invokes method in all event handlers attached to the element
       **/
      bool call_behavior_method(METHOD_PARAMS* p)
      {
        if(!is_valid())
          return false;
        return ::HTMLayoutCallBehaviorMethod(he,p) == HLDOM_OK;
      }

      json::value xcall(const char* name, 
              json::value p0 = json::value(),
              json::value p1 = json::value(),
              json::value p2 = json::value(),
              json::value p3 = json::value(),
              json::value p4 = json::value(),
              json::value p5 = json::value(),
              json::value p6 = json::value(),
              json::value p7 = json::value())
      {
        XCALL_PARAMS prm( name );
        json::value argv[8];
        prm.argv = argv;
        do 
        {
          if( p0.is_undefined()) break; argv[prm.argc++] = p0; 
          if( p1.is_undefined()) break; argv[prm.argc++] = p1; 
          if( p2.is_undefined()) break; argv[prm.argc++] = p2; 
          if( p3.is_undefined()) break; argv[prm.argc++] = p3; 
          if( p4.is_undefined()) break; argv[prm.argc++] = p4; 
          if( p5.is_undefined()) break; argv[prm.argc++] = p5; 
          if( p6.is_undefined()) break; argv[prm.argc++] = p6; 
          if( p7.is_undefined()) break; argv[prm.argc++] = p7; 
        } while(false);
        bool r = call_behavior_method(&prm);
        assert(r); r;
        return prm.retval;
      }

      void load_html(const wchar_t* url, HELEMENT initiator = 0)
      {
        HLDOM_RESULT r = HTMLayoutRequestElementData(he,url,HLRT_DATA_HTML,initiator);
        assert(r == HLDOM_OK); r;
      }

      struct comparator 
      {
        virtual int compare(const htmlayout::dom::element& e1, const htmlayout::dom::element& e2) = 0;

        static INT CALLBACK scmp( HELEMENT he1, HELEMENT he2, LPVOID param )
        {
          htmlayout::dom::element::comparator* self = 
            static_cast<htmlayout::dom::element::comparator*>(param);

          htmlayout::dom::element e1 = he1;
          htmlayout::dom::element e2 = he2;

          return self->compare( e1,e2 );
        }
      };

      /** reorders children of the element using sorting order defined by cmp 
       **/
      void sort( comparator& cmp, int start = 0, int end = -1 )
      {
        if (end == -1)
          end = children_count();

        HLDOM_RESULT r = HTMLayoutSortElements(he, start, end, &comparator::scmp, &cmp);
        assert(r == HLDOM_OK); r;
      }

      CTL_TYPE get_ctl_type() const
      {
        UINT t = 0;
        HLDOM_RESULT r = ::HTMLayoutControlGetType(he,&t);
        assert(r == HLDOM_OK); r;
        return CTL_TYPE(t);
      }

      json::value get_value() const
      {
        json::value v;
        HLDOM_RESULT r = ::HTMLayoutControlGetValue(he,&v);
        assert(r == HLDOM_OK); r;
        return v;
      }

      void set_value(const json::value& v)
      {
        HLDOM_RESULT r = ::HTMLayoutControlSetValue(he,&v);
        assert(r == HLDOM_OK); r;
      }

      void     set_expando( expando* exp );  
      expando* get_expando();

     private:
      struct find_first_callback: callback 
      {
        HELEMENT hfound;
        find_first_callback():hfound(0) {}
        inline bool on_element(HELEMENT he) { hfound = he; return true; /*stop enumeration*/ }
      };

    };

    /**Expando - structure that can be associated with the DOM element. 
      *
      * In other words: DOM element can be expanded by this structure
      *
      * Derive your own class/structure from this one if you need some data to be associated with DOM element.
      * 
      * If you want to handle events here then additionally derive your structure from event_handler and 
      * call both: attach_event_handler and set_expando with it.
      *
      **/
    class expando: public HTMLayoutElementExpando
    {
      static void CALLBACK _finalizer(HTMLayoutElementExpando* pexp, HELEMENT he) { static_cast<expando*>(pexp)->finalize(); }
    protected:
      virtual void finalize() { delete this; }
    public:
      expando() { finalizer = _finalizer; }
      virtual ~expando() {}
    };

    inline void element::set_expando( expando* exp )
    {
      HLDOM_RESULT r = HTMLayoutElementSetExpando(he, static_cast<HTMLayoutElementExpando*>(exp));
      assert(r == HLDOM_OK); r;
    }
   
    inline expando* element::get_expando()
    {
      HTMLayoutElementExpando* pexp = 0;
      HLDOM_RESULT r = HTMLayoutElementGetExpando(he, &pexp);
      assert(r == HLDOM_OK); r;
      return static_cast<expando*>(pexp);
    }


    #define STD_CTORS(T,PT) \
      T() { } \
      T(HELEMENT h): PT(h) { } \
      T(const element& e): PT(e) { } \
      T& operator = (HELEMENT h) { set(h); return *this; } \
      T& operator = (const element& e) { set(e); return *this; }


    struct text_selection_params: public TEXT_SELECTION_PARAMS
    {
        pod::buffer<wchar_t>& _wos;
        static BOOL CALLBACK ctl(text_selection_params* prms, UINT data ) { prms->_wos.push(data);  return 1; }
        text_selection_params(pod::buffer<wchar_t>& wos): TEXT_SELECTION_PARAMS(false), _wos(wos)  { outs = (OutputStreamProc*)ctl; }
    };
    struct html_selection_params: public TEXT_SELECTION_PARAMS
    {
        pod::buffer<byte>& _bos;
        static BOOL CALLBACK ctl(html_selection_params* prms, UINT data ) { prms->_bos.push(data);  return 1; }
        html_selection_params(pod::buffer<byte>& bos): TEXT_SELECTION_PARAMS(true), _bos(bos)  { outs = (OutputStreamProc*)ctl; }
    };


    class editbox: public element
    {
        
      public: 
        STD_CTORS(editbox, element)
        
        bool selection( int& start, int& end )
        {
          TEXT_EDIT_SELECTION_PARAMS sp(false);
          if(!call_behavior_method(&sp))
            return false;
          start = sp.selection_start;
          end = sp.selection_end;
          return true;
        }
        bool select( int start = 0, int end = 0x7FFFFFFF )
        {
          TEXT_EDIT_SELECTION_PARAMS sp(true);
          sp.selection_start = start;
          sp.selection_end = end;
          return call_behavior_method(&sp);
        }
        bool replace(const wchar_t* text, size_t text_length)
        {
          TEXT_EDIT_REPLACE_SELECTION_PARAMS sp;
          sp.text = text;
          sp.text_length = UINT(text_length);
          return call_behavior_method(&sp);
        }

        std::wstring text_value() const
        {
          TEXT_VALUE_PARAMS sp(false);
          if( const_cast<editbox*>(this)->call_behavior_method(&sp) && sp.text && sp.length)
          {
              return std::wstring(sp.text, sp.length);
          }
          return std::wstring();
        }

        void text_value(const std::wstring& s)
        {
          text_value(s.c_str(), s.length());
        }
        void text_value(const wchar_t* text, size_t length)
        {
          TEXT_VALUE_PARAMS sp(true);
          sp.text = text;
          sp.length = UINT(length);
          call_behavior_method(&sp);
          update();
        }

        void text_value(const wchar_t* text)
        {
          TEXT_VALUE_PARAMS sp(true);
          sp.text = text;
          sp.length = text? UINT(wcslen(text)):0;
          call_behavior_method(&sp);
          update();
        }

        void caret_position(int& x, int& y, int& w, int& h)
        {
          TEXT_CARET_POSITION_PARAMS cpp;
          x = y = w = h = 0;
          if(call_behavior_method(&cpp))
          {
            x = cpp.left; y = cpp.top;
            w = cpp.width; h = cpp.height;
          }
        }

        void int_value( int v )
        {
           wchar_t buf[64]; int n = _snwprintf(buf,63,L"%d", v); buf[63] = 0;
           text_value(buf,n);
        }
        
        int int_value( ) const
        {
           return aux::wtoi( text_value().c_str() );
        }

        std::wstring selected_text()
        {
          pod::buffer<wchar_t> wos;
          get_selected_text(wos);
          return std::wstring(wos.data(), wos.length());
        }

        void get_selected_text(pod::buffer<wchar_t>& wos)
        {
          text_selection_params s(wos); 
          call_behavior_method(&s);
        }


        void get_selected_html(pod::buffer<byte>& bos)
        {
          html_selection_params s(bos); 
          call_behavior_method(&s);
        }

    };

    class scrollbar: public element
    {
      public: 
        STD_CTORS(scrollbar, element)

        void get_values( int& val, int& min_val, int& max_val, int& page_val, int& step_val )
        {
          SCROLLBAR_VALUE_PARAMS sbvp;
          call_behavior_method(&sbvp);
          val       = sbvp.value;
          min_val   = sbvp.min_value;
          max_val   = sbvp.max_value;
          page_val  = sbvp.page_value; // page increment
          step_val  = sbvp.step_value; // step increment (arrow button click) 
        }

        void set_values( int val, int min_val, int max_val, int page_val, int step_val )
        {
          SCROLLBAR_VALUE_PARAMS sbvp(true);
          sbvp.value       = val;
          sbvp.min_value   = min_val;
          sbvp.max_value   = max_val; 
          sbvp.page_value  = page_val;
          sbvp.step_value  = step_val;
          call_behavior_method(&sbvp);
        }

        int   get_value()
        {
          SCROLLBAR_VALUE_PARAMS sbvp;
          call_behavior_method(&sbvp);
          return sbvp.value;
        }

        void  set_value( int v )
        {
          SCROLLBAR_VALUE_PARAMS sbvp;
          call_behavior_method(&sbvp);
          sbvp.value = v;
          sbvp.methodID = SCROLL_BAR_SET_VALUE;
          call_behavior_method(&sbvp);
        }
    };


    struct enumerator
    {
      enumerator() {}
      virtual ~enumerator() {} // to make G++ happy.

              void ramble(HELEMENT what, bool forward = true)
              {
                HLDOM_RESULT r = HTMLayoutEnumerate(what,&_cb,this,forward);
                assert(r == HLDOM_OK); r;
              }

      virtual bool on_element_head( HELEMENT )                    { return false; }
      virtual bool on_element_tail( HELEMENT )                    { return false; }
      virtual bool on_char( HELEMENT text, int pos,  WCHAR code ) { return false; }

      inline static BOOL CALLBACK _cb( LPVOID p, HELEMENT he, int pos, int postype, WCHAR code )
      {
        enumerator* pe = (enumerator*)p;
        switch(postype)
        {
          case 0: // - he element head position.
            return pe->on_element_head( he ); 
          case 1: // - he element tail position.
            return pe->on_element_tail( he ); 
          case 2:// - character position.
            return pe->on_char( he, pos, code ); 
        }
        assert(false);
        return false;
      }
    };


  } // dom namespace

} // htmlayout namespace

#endif