#if defined(_WIN32) && !defined(WDL_DISABLE_IACCESSIBLE)

#include <windows.h>

#include <oleacc.h>
#if !defined(_MSC_VER) || _MSC_VER < 1600
#include <winable.h>
#else
#include <WinUser.h>
#endif


#include "virtwnd-controls.h"
#include "../wdltypes.h"
#include "../wdlcstring.h"

static BSTR SysAllocStringUTF8(const char *str)
{
  WCHAR tmp[1024];
  const int slen = (int)strlen(str)+1;
  WCHAR *wstr = slen < 1000 ? tmp : (WCHAR*)malloc(2*slen+32);
  if (!wstr) return NULL;

  wstr[0]=0;
  int a=MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,str,slen,wstr, slen<1000?1024:slen+16);
  if (!a)
  {
    wstr[0]=0;
    a=MultiByteToWideChar(CP_ACP,MB_ERR_INVALID_CHARS,str,slen,wstr,slen<1000?1024:slen+16);
  }

  BSTR ret = SysAllocString(wstr);
  if (wstr != tmp) free(wstr);
  return ret;
}

class CVWndAccessible;
class VWndBridge : public WDL_VWnd_IAccessibleBridge
{
public:
  VWndBridge() { }
  virtual ~VWndBridge() { }
  virtual void Release() {  vwnd=0; }

  virtual void OnFocused()
  {
    DoNotify(EVENT_OBJECT_FOCUS);
  }
  virtual void OnStateChange() 
  {
    DoNotify(EVENT_OBJECT_VALUECHANGE);
  }
  void DoNotify(int mode)
  {
    if (vwnd)
    {
      HWND hwnd = vwnd->GetRealParent();
      if (hwnd)
      {
        int idx = CHILDID_SELF;
        WDL_VWnd *lpar = vwnd, *par = NULL;
        // todo: better handle hierarchy?
        if (lpar) while ((par=lpar->GetParent()))
        {
          if (!par->GetParent()) break;
          lpar=par;
        }
        if (par)
        {
          for (idx=0; idx < par->GetNumChildren(); idx++) if (par->EnumChildren(idx)==lpar) break;
          if (idx >= par->GetNumChildren()) idx = CHILDID_SELF;
          else idx++;
        }
        NotifyWinEvent(mode,hwnd,OBJID_CLIENT,idx); 
      }
    }
  }

  CVWndAccessible *par;
  WDL_VWnd *vwnd;

};
static IAccessible *GetVWndIAccessible(WDL_VWnd *vwnd);

static int g_freelist_acc_size;
static CVWndAccessible *g_freelist_acc;

static HRESULT (WINAPI *__CreateStdAccessibleObject)(
  HWND hwnd,
  LONG idObject,
  REFIID riidInterface,
  void **ppvObject
);


static int allocated_cnt;
class CVWndAccessible : public IAccessible
{
public:
  CVWndAccessible(WDL_VWnd *vwnd) 
  { 
    m_br.vwnd=vwnd;
    m_br.par = this;
    m_refCnt = 1;
    allocated_cnt++;
  }
  virtual ~CVWndAccessible() 
  { 
    allocated_cnt--;
    //char buf[512];
    //sprintf(buf,"allocated total = %d\n",allocated_cnt);
  //  OutputDebugString(buf);
  }

 //IUnknown interface 
  STDMETHOD_(HRESULT, QueryInterface)(REFIID riid , void **ppObj)
  {
    if (IsEqualIID(riid, IID_IUnknown))
    {
      *ppObj = (IUnknown*)this;
    }
    else if (IsEqualIID(riid, IID_IAccessible))
    {
      *ppObj = (IAccessible*)this;
    }
    else if (IsEqualIID(riid, IID_IDispatch))
    {
      *ppObj = (IDispatch*)this;
    }
    else
    {
      *ppObj = NULL;
      return E_NOINTERFACE;
    }
    AddRef();
    return S_OK;
  }
  STDMETHOD_(ULONG, AddRef)()
  {
    return InterlockedIncrement(&m_refCnt);
  }
  STDMETHOD_(ULONG, Release)()
  {
    LONG nRefCount=0;
    nRefCount=InterlockedDecrement(&m_refCnt) ;
    if (nRefCount == 0) 
    {
      if (m_br.vwnd)
      {
        m_br.vwnd->SetAccessibilityBridge(NULL);
        m_br.vwnd=0;
      }

      if (g_freelist_acc_size<2048)
      {
        g_freelist_acc_size++;
        _freelist_next = g_freelist_acc;
        g_freelist_acc = this;
      }
      else
      {
        delete this;
      }
    }
    return nRefCount;
  }


  //IDispatch
  STDMETHOD(GetTypeInfoCount)(unsigned int FAR* pctinfo )
  {
    *pctinfo=0;
    return E_NOTIMPL;
  }
  STDMETHOD(GetTypeInfo)(unsigned int iTInfo, LCID lcid, ITypeInfo FAR* FAR* ppTInfo)
  {
    return E_NOTIMPL;
  }
  STDMETHOD( GetIDsOfNames)( 
    REFIID riid, 
    OLECHAR FAR* FAR* rgszNames, 
    unsigned int cNames, 
    LCID lcid, 
    DISPID FAR* rgDispId 
  )
  {
    *rgDispId=0;
    return E_NOTIMPL;
  }

  STDMETHOD(Invoke)( 
    DISPID dispIdMember, 
    REFIID riid, 
    LCID lcid, 
    WORD wFlags, 
    DISPPARAMS FAR* pDispParams, 
    VARIANT FAR* pVarResult, 
    EXCEPINFO FAR* pExcepInfo, 
    unsigned int FAR* puArgErr 
  )
  {
    return E_NOTIMPL;
  }

  // IAccessible


  STDMETHOD(get_accParent)(THIS_ IDispatch * FAR* ppdispParent) 
  {
    WDL_VWnd *par=m_br.vwnd ? m_br.vwnd->GetParent() : NULL;
    if (par)
    {
      *ppdispParent = GetVWndIAccessible(par);
      if (*ppdispParent) return S_OK;
    }

    *ppdispParent = NULL;

    if (__CreateStdAccessibleObject && m_br.vwnd)
    {
      HWND realparent = m_br.vwnd->GetRealParent();
      if (realparent)
      {
        HRESULT res = __CreateStdAccessibleObject(realparent,OBJID_WINDOW,IID_IAccessible,(void**)ppdispParent); // should this be OBJID_CLIENT?
        if (SUCCEEDED(res))
          return S_OK;
      }
    }

    return S_FALSE;
  }
#define ISVWNDLIST(x) ((x)&&!strcmp((x)->GetType(),"vwnd_listbox"))
  STDMETHOD(get_accChildCount)(THIS_ long FAR* pChildCount) 
  {
    *pChildCount = m_br.vwnd ? m_br.vwnd->GetNumChildren() : 0;
    HWND realparent;
    if (__CreateStdAccessibleObject && m_br.vwnd && !m_br.vwnd->GetParent() && (realparent=m_br.vwnd->GetRealParent()))
    { 
      HWND h=GetWindow(realparent,GW_CHILD);
      while (h)
      {
        (*pChildCount) += 1;
        h=GetWindow(h,GW_HWNDNEXT);
      }
    }

    if (ISVWNDLIST(m_br.vwnd))
    {
      WDL_VirtualListBox *list=(WDL_VirtualListBox*)m_br.vwnd;
      int c = 0;
      if (list->m_GetItemInfo) c=list->m_GetItemInfo(list,-1,NULL,0,NULL,NULL);
      if(c<0)c=0;
      *pChildCount += c+2;
    }
    return S_OK;
  }
  STDMETHOD(get_accChild)(THIS_ VARIANT varChildIndex, IDispatch * FAR* ppdispChild) 
  {
    *ppdispChild=0;
    if (!m_br.vwnd || varChildIndex.vt != VT_I4) return E_INVALIDARG;

    WDL_VWnd *vw = m_br.vwnd->EnumChildren(varChildIndex.lVal-1);
    if (vw)
    {
      *ppdispChild=GetVWndIAccessible(vw);
      if (*ppdispChild) return S_OK;
    }

    int index =  varChildIndex.lVal-1 - m_br.vwnd->GetNumChildren();

    if (ISVWNDLIST(m_br.vwnd))
    {
      WDL_VirtualListBox *list=(WDL_VirtualListBox*)m_br.vwnd;
      int c = 0;
      if (list->m_GetItemInfo) c=list->m_GetItemInfo(list,-1,NULL,0,NULL,NULL);
      if(c<0)c=0;
      index -= c+2;
    }


    HWND realparent;
    if (index >= 0 && __CreateStdAccessibleObject && m_br.vwnd && !m_br.vwnd->GetParent() && (realparent=m_br.vwnd->GetRealParent()))
    {      
      HWND h=GetWindow(realparent,GW_CHILD);
      while (h)
      {
        if (!index) break;
        index--;
        h=GetWindow(h,GW_HWNDNEXT);
      }

      if (h)
      {
        *ppdispChild=0;
        HRESULT res = __CreateStdAccessibleObject(h,OBJID_CLIENT,IID_IAccessible,(void**)ppdispChild);
        if (SUCCEEDED(res)) 
        {
          return S_OK;
        }

      }
    }

    
    return S_FALSE;
  }

  STDMETHOD(get_accName)(THIS_ VARIANT varChild, BSTR* pszOut) 
  {
    *pszOut=NULL;
    if (!m_br.vwnd || varChild.vt != VT_I4) 
    {
      return E_INVALIDARG;
    }
    WDL_VWnd *vw = varChild.lVal == CHILDID_SELF ? m_br.vwnd : m_br.vwnd->EnumChildren(varChild.lVal-1);

    if (vw)
    {
      const char *txt=NULL;
      const char *ctltype = vw->GetType();
      if (!strcmp(ctltype,"vwnd_iconbutton"))
        txt = ((WDL_VirtualIconButton*)vw)->GetTextLabel();
      else if (!strcmp(ctltype,"vwnd_statictext"))
        txt = ((WDL_VirtualStaticText*)vw)->GetText();
      else if (!strcmp(ctltype,"vwnd_combobox"))
        txt = ((WDL_VirtualComboBox*)vw)->GetItem(((WDL_VirtualComboBox*)vw)->GetCurSel());

      const char *p = vw->GetAccessDesc();
      if (p && *p && txt && *txt)
      {
        char buf[1024];
        if (!strcmp(ctltype,"vwnd_iconbutton"))
          snprintf(buf,sizeof(buf),"%.500s %.500s",txt,p);
        else
          snprintf(buf,sizeof(buf),"%.500s %.500s",p,txt);
        *pszOut= SysAllocStringUTF8(buf);
        if (!*pszOut) return E_OUTOFMEMORY;
      }
      else if (txt && *txt)
      {
        *pszOut= SysAllocStringUTF8(txt);
        if (!*pszOut) return E_OUTOFMEMORY;
      }
      else if (p && *p)
      {
        *pszOut = SysAllocStringUTF8(p);
        if (!*pszOut) return E_OUTOFMEMORY;
      }
    }
    else if (ISVWNDLIST(m_br.vwnd))
    {
      WDL_VirtualListBox *list=(WDL_VirtualListBox*)m_br.vwnd;
      if (list->m_GetItemInfo)
      {
        int idx = varChild.lVal-1 - m_br.vwnd->GetNumChildren();
        int ni = list->m_GetItemInfo(list,-1,NULL,0,NULL,NULL);
        char buf[2048];
        buf[0]=0;
        if (idx>=0&&idx<ni)
          list->m_GetItemInfo(list,idx,buf,512,NULL,NULL);
        else if (idx==ni||idx==ni+1)
        {
          strcpy(buf,idx==ni?"Scroll previous" : "Scroll next");
        }

        // we put this in the desc field instead
        /*const char *txt1 = list->GetAccessDesc();
        if (txt1)
        {
          if (buf[0]) strcat(buf," ");
          lstrcpyn_safe(buf+strlen(buf),txt1,512);
        }*/

//          OutputDebugString(buf);
        if (buf[0])
        {
          *pszOut = SysAllocStringUTF8(buf);
          if (!*pszOut) return E_OUTOFMEMORY;
        }
      }
    }
    return S_OK;

  }
  STDMETHOD(get_accValue)(THIS_ VARIANT varChild, BSTR* pszValue)
  {
    *pszValue=NULL;
    if (!m_br.vwnd || varChild.vt != VT_I4) 
    {
      return E_INVALIDARG;
    }
    WDL_VWnd *vw = varChild.lVal == CHILDID_SELF ? m_br.vwnd : m_br.vwnd->EnumChildren(varChild.lVal-1);
    if (vw && !strcmp(vw->GetType(),"vwnd_slider"))
    {
      char buf[1024];
      buf[0]=0;
      if (vw->GetAccessValueDesc(buf,sizeof(buf)) && buf[0])
      {
        *pszValue = SysAllocStringUTF8(buf);
        if (!*pszValue) return E_OUTOFMEMORY;
        return S_OK;
      }
    }
    return DISP_E_MEMBERNOTFOUND;
  }
  STDMETHOD(get_accDescription)(THIS_ VARIANT varChild, BSTR FAR* pszOut)
  {
    *pszOut=NULL;
    if (!m_br.vwnd || varChild.vt != VT_I4) 
    {
      return E_INVALIDARG;
    }
    WDL_VWnd *vw = varChild.lVal == CHILDID_SELF ? m_br.vwnd : m_br.vwnd->EnumChildren(varChild.lVal-1);
    if (vw) 
    {
      return S_FALSE;
    }
    else if (ISVWNDLIST(m_br.vwnd))
    {
      WDL_VirtualListBox *list=(WDL_VirtualListBox*)m_br.vwnd;
      if (list->m_GetItemInfo)
      {
        const char *txt = list->GetAccessDesc();
        if (txt)
        {
          *pszOut = SysAllocStringUTF8(txt);
          if (!*pszOut) return E_OUTOFMEMORY;
          return S_OK;
        }
      }
    }
    return E_INVALIDARG;
  }

  STDMETHOD(get_accRole)(THIS_ VARIANT varChild, VARIANT *pvarRole) 
  {
    if (!m_br.vwnd || varChild.vt != VT_I4) 
    {
      pvarRole->vt = VT_EMPTY;
      return E_INVALIDARG;
    }
    WDL_VWnd *vw = varChild.lVal == CHILDID_SELF ? m_br.vwnd : m_br.vwnd->EnumChildren(varChild.lVal-1);

    if (!vw && ISVWNDLIST(m_br.vwnd))
    {
      WDL_VirtualListBox *list=(WDL_VirtualListBox*)m_br.vwnd;
      if (list->m_GetItemInfo)
      {
        int idx = varChild.lVal-1 - m_br.vwnd->GetNumChildren();
        int ni = list->m_GetItemInfo(list,-1,NULL,0,NULL,NULL);
        if (idx>=0&&idx<ni)
        {
          pvarRole->vt = VT_I4;
          pvarRole->lVal = ROLE_SYSTEM_LISTITEM;
          return S_OK;
        }
        else if (idx==ni || idx==ni+1)
        {
          pvarRole->vt = VT_I4;
          pvarRole->lVal = ROLE_SYSTEM_PUSHBUTTON;
          return S_OK;          
        }
      }
    }

    if (!vw) 
    {
      pvarRole->vt = VT_EMPTY;
      return E_INVALIDARG;
    }

    pvarRole->vt = VT_I4;
    const char *type = vw->GetType();

    if (!strcmp(type,"vwnd_iconbutton")) 
    {
      WDL_VirtualIconButton *vb = (WDL_VirtualIconButton*)vw;
      if (vb->GetIsButton())
      {
        if (vb->GetCheckState()>=0) pvarRole->lVal = ROLE_SYSTEM_CHECKBUTTON;
        else pvarRole->lVal = ROLE_SYSTEM_PUSHBUTTON;
      }
      else
        pvarRole->lVal = ROLE_SYSTEM_STATICTEXT;       
    }
    else if (!strcmp(type,"vwnd_statictext"))  pvarRole->lVal = ROLE_SYSTEM_STATICTEXT;
    else if (!strcmp(type,"vwnd_combobox"))  pvarRole->lVal = ROLE_SYSTEM_COMBOBOX;
    else if (!strcmp(type,"vwnd_slider"))  pvarRole->lVal = ROLE_SYSTEM_SLIDER;
    else if (!strcmp(type,"vwnd_tabctrl_proxy"))  pvarRole->lVal = ROLE_SYSTEM_PAGETABLIST;
    else if (!strcmp(type,"vwnd_tabctrl_child"))  pvarRole->lVal = ROLE_SYSTEM_PAGETAB;
    else if (!strcmp(type,"vwnd_listbox"))  pvarRole->lVal = ROLE_SYSTEM_LIST;
    else if (vw->GetNumChildren()) pvarRole->lVal =  ROLE_SYSTEM_GROUPING;
    else pvarRole->lVal=ROLE_SYSTEM_CLIENT;

    return S_OK;
  }

  STDMETHOD(get_accState)(THIS_ VARIANT varChild, VARIANT *pvarState) 
  {
    if (!m_br.vwnd || varChild.vt != VT_I4) 
    {
      pvarState->vt = VT_EMPTY;
      return E_INVALIDARG;
    }
    WDL_VWnd *vw = varChild.lVal == CHILDID_SELF ? m_br.vwnd : m_br.vwnd->EnumChildren(varChild.lVal-1);

    if (!vw) 
    {
      if (ISVWNDLIST(m_br.vwnd))
      {
        WDL_VirtualListBox *list=(WDL_VirtualListBox*)m_br.vwnd;
        if (list->m_GetItemInfo) 
        {
          int index = varChild.lVal-1 - m_br.vwnd->GetNumChildren();
          int c=list->m_GetItemInfo(list,-1,NULL,0,NULL,NULL);
          if (index>=0&&index<c)
          {
            pvarState->vt = VT_I4;
            pvarState->lVal = 0;
            RECT r;
            if (list->GetItemRect(index,&r))
            {
            }
            else 
              pvarState->lVal|=STATE_SYSTEM_INVISIBLE;

            return S_OK;
          }          
          else if (index==c||index==c+1)
          {
            pvarState->vt = VT_I4;
            pvarState->lVal = 0;
            if (!list->GetScrollButtonRect(index==c+1))
              pvarState->lVal |= STATE_SYSTEM_UNAVAILABLE;
            return S_OK;

          }
        }
      }

      pvarState->vt = VT_EMPTY;
      return E_INVALIDARG;
    }

    const char *type = vw->GetType();
    pvarState->vt = VT_I4;
    pvarState->lVal = 0;
    if (!vw->IsVisible()) pvarState->lVal |= STATE_SYSTEM_INVISIBLE;
    else
    {
      WDL_VWnd *par = vw->GetParent();
      if (!par || par->m_focused_child != -2)
        pvarState->lVal |= STATE_SYSTEM_FOCUSABLE;

      if (vw->GetRealParent() && GetFocus() == vw->GetRealParent() && (!par || par->EnumChildren(par->m_focused_child) == vw))
        pvarState->lVal |= STATE_SYSTEM_FOCUSED;
    }

    if (!strcmp(type,"vwnd_iconbutton")) 
    {
      WDL_VirtualIconButton *vb = (WDL_VirtualIconButton*)vw;
      if (vb->GetIsButton())
      {
        if (vb->GetCheckState()>0) pvarState->lVal |= STATE_SYSTEM_CHECKED;
      }
    }

    return S_OK;
  }
  STDMETHOD(get_accHelp)(THIS_ VARIANT varChild, BSTR* pszHelp) 
  {
    *pszHelp=NULL;
    if (!m_br.vwnd || varChild.vt != VT_I4) 
    {
      return E_INVALIDARG;
    }
    WDL_VWnd *vw = varChild.lVal == CHILDID_SELF ? m_br.vwnd : m_br.vwnd->EnumChildren(varChild.lVal-1);

    if (!vw) 
    {
      return E_INVALIDARG;
    }
    return S_FALSE;
  }
  STDMETHOD(get_accHelpTopic)(THIS_ BSTR* pszHelpFile, VARIANT varChild, long* pidTopic)
  {
    return DISP_E_MEMBERNOTFOUND;
  }
  STDMETHOD(get_accKeyboardShortcut)(THIS_ VARIANT varChild, BSTR* pszKeyboardShortcut) 
  {
    *pszKeyboardShortcut=NULL;

    if (!m_br.vwnd || varChild.vt != VT_I4) 
    {
      return E_INVALIDARG;
    }
    WDL_VWnd *vw = varChild.lVal == CHILDID_SELF ? m_br.vwnd : m_br.vwnd->EnumChildren(varChild.lVal-1);

    if (!vw) 
    {
      return E_INVALIDARG;
    }
    return S_FALSE;
  }
  STDMETHOD(get_accFocus)(THIS_ VARIANT FAR * pvarFocusChild)
  {
    pvarFocusChild->vt= VT_EMPTY;
    if (!m_br.vwnd) 
    {
      return S_FALSE;
    }
    //return DISP_E_MEMBERNOTFOUND;

    if (!m_br.vwnd->GetParent() &&
        m_br.vwnd->GetRealParent() && 
        GetFocus()==m_br.vwnd->GetRealParent())
    {
      pvarFocusChild->vt=VT_I4;
      if (m_br.vwnd->m_focused_child >= 0)
        pvarFocusChild->lVal = 1+m_br.vwnd->m_focused_child;
      else
        pvarFocusChild->lVal = CHILDID_SELF;
    }
    return S_OK;
  }
  STDMETHOD(get_accSelection)(THIS_ VARIANT FAR * pvarSelectedChildren) 
  {
    pvarSelectedChildren->vt= VT_EMPTY;
    if (!m_br.vwnd) 
    {
      return S_FALSE;
    }
    return S_OK;
  }
  STDMETHOD(get_accDefaultAction)(THIS_ VARIANT varChild, BSTR* pszDefaultAction) 
  {
    *pszDefaultAction=NULL;

    if (!m_br.vwnd || varChild.vt != VT_I4) 
    {
      return E_INVALIDARG;
    }
    WDL_VWnd *vw = varChild.lVal == CHILDID_SELF ? m_br.vwnd : m_br.vwnd->EnumChildren(varChild.lVal-1);

    if (!vw) return E_INVALIDARG;

    const char *type = vw->GetType();
    if (type)
    {
      const char *str=NULL;
      if (!strcmp(type,"vwnd_combobox")) str = "Activate";
      else if (!strcmp(type,"vwnd_iconbutton")) str="Click";
      else if (!strcmp(type,"vwnd_tabctrl_child")) str="Select";
      else if (!strcmp(type,"vwnd_statictext")) str="Click";
      if (str)
      {
        *pszDefaultAction = SysAllocStringUTF8(str);
        if (!*pszDefaultAction) return E_OUTOFMEMORY;
        return S_OK;
      }
    }

    return S_FALSE;
  }

  STDMETHOD(accSelect)(THIS_ long flagsSelect, VARIANT varChild) 
  {
    return S_FALSE;
  }
  STDMETHOD(accLocation)(THIS_ long* pxLeft, long* pyTop, long* pcxWidth, long* pcyHeight, VARIANT varChild) 
  {
    *pxLeft=*pyTop=*pcxWidth=*pcyHeight=0;
    if (!m_br.vwnd || varChild.vt != VT_I4) 
    {
      return E_INVALIDARG;
    }
    WDL_VWnd *vw = varChild.lVal == CHILDID_SELF ? m_br.vwnd : m_br.vwnd->EnumChildren(varChild.lVal-1);

    if (!vw && ISVWNDLIST(m_br.vwnd))
    {
      WDL_VirtualListBox *list=(WDL_VirtualListBox*)m_br.vwnd;
      if (list->m_GetItemInfo)
      {
        int idx = varChild.lVal-1 - m_br.vwnd->GetNumChildren();
        int ni = list->m_GetItemInfo(list,-1,NULL,0,NULL,NULL);
        if (idx>=0&&idx<ni)
        {
          RECT r2={0,0,0,0};
          if (list->GetItemRect(idx,&r2))
          {
            HWND h = list->GetRealParent();
            RECT r;
            list->GetPositionInTopVWnd(&r);
            ClientToScreen(h,(LPPOINT)&r);
            ClientToScreen(h,((LPPOINT)&r)+1);
            *pxLeft=r.left+r2.left;
            *pyTop=r.top+r2.top;
            *pcxWidth=r2.right-r2.left;
            *pcyHeight=r2.bottom-r2.top;
          }
          else *pxLeft=*pyTop=*pcxWidth=*pcyHeight=0; 
          return S_OK;
        }
        else if (idx==ni||idx==ni+1)
        {
          RECT *rr = list->GetScrollButtonRect(idx==ni+1);
          if (rr)
          {
            HWND h = list->GetRealParent();
            RECT r;
            list->GetPositionInTopVWnd(&r);
            ClientToScreen(h,(LPPOINT)&r);
            ClientToScreen(h,((LPPOINT)&r)+1);
            *pxLeft=r.left+rr->left;
            *pyTop=r.top+rr->top;
            *pcxWidth=rr->right-rr->left;
            *pcyHeight=rr->bottom-rr->top;
          }
          else *pxLeft=*pyTop=*pcxWidth=*pcyHeight=0; 
          return S_OK;

        }
      }
    }
    if (!vw) return E_INVALIDARG;

    HWND h = vw->GetRealParent();
    if (h)
    {
      RECT r;
      vw->GetPositionInTopVWnd(&r);
      ClientToScreen(h,(LPPOINT)&r);
      ClientToScreen(h,((LPPOINT)&r)+1);
      *pxLeft=r.left;
      *pyTop=r.top;
      *pcxWidth=r.right-r.left;
      *pcyHeight=r.bottom-r.top;
    }
    return S_OK;   
  }

  STDMETHOD(accNavigate)(THIS_ long navDir, VARIANT varStart, VARIANT * pvarEndUpAt) 
  {
    if (!pvarEndUpAt) return E_INVALIDARG;

    if (!m_br.vwnd || varStart.vt != VT_I4) 
    {
      return DISP_E_MEMBERNOTFOUND;
    }
    pvarEndUpAt->vt = VT_I4;
    pvarEndUpAt->lVal = VT_EMPTY;

    WDL_VWnd *vw = varStart.lVal == CHILDID_SELF ? m_br.vwnd : m_br.vwnd->EnumChildren(varStart.lVal-1);
    if (!vw) return S_FALSE;

    if (navDir == NAVDIR_FIRSTCHILD || navDir == NAVDIR_LASTCHILD)
    {
      int n = vw->GetNumChildren();
      if (ISVWNDLIST(vw))
      {
        WDL_VirtualListBox *list=(WDL_VirtualListBox*)m_br.vwnd;
        if (list->m_GetItemInfo) n += list->m_GetItemInfo(list,-1,NULL,0,NULL,NULL);
      }

      if (n<1) return S_FALSE;
      pvarEndUpAt->vt = VT_I4;
      pvarEndUpAt->lVal = navDir == NAVDIR_FIRSTCHILD ? 1 : n;
      return S_OK;
    }

    if (navDir == NAVDIR_NEXT || navDir == NAVDIR_PREVIOUS)
    {
      if (varStart.lVal != CHILDID_SELF)
      {
        int n = m_br.vwnd->GetNumChildren();
        if (ISVWNDLIST(m_br.vwnd))
        {
          WDL_VirtualListBox *list=(WDL_VirtualListBox*)m_br.vwnd;
          if (list->m_GetItemInfo) n += list->m_GetItemInfo(list,-1,NULL,0,NULL,NULL);
        }
        int x = varStart.lVal - 1;
        if (navDir == NAVDIR_NEXT)
        {
          if (++x >= n) return S_FALSE;
        }
        else
        {
          if (--x<0) return S_FALSE;
        }
        pvarEndUpAt->vt = VT_I4;
        pvarEndUpAt->lVal = 1 + x;
        return S_OK;
      }

      // passed CHILDID_SELF, need to scan to find index
      WDL_VWnd *par = vw->GetParent();
      if (par)
      {
        const int n = par->GetNumChildren();
        int x;
        for (x=0;x < n;x++) 
        {
          WDL_VWnd *c = par->EnumChildren(x);
          if (c == vw) 
          {
            if (navDir == NAVDIR_NEXT) x++;
            else x--;

            WDL_VWnd *hit = par->EnumChildren(x);
            if (!hit) break;

            IAccessible *pac = GetVWndIAccessible(hit);
            if (pac)
            {
              pvarEndUpAt->vt = VT_DISPATCH;
              pvarEndUpAt->pdispVal = (IDispatch *)pac;
            }
            else
            {
              pvarEndUpAt->vt = VT_I4;
              pvarEndUpAt->lVal = 1 + x;
            }
            return S_OK;
          }
        }
      }
      return S_FALSE;
    }

    return E_INVALIDARG;
  }
  STDMETHOD(accHitTest)(THIS_ long xLeft, long yTop, VARIANT * pvarChildAtPoint) 
  {
    pvarChildAtPoint->vt = VT_EMPTY;

    if (!m_br.vwnd) 
    {
      return E_INVALIDARG;
    }

    HWND h = m_br.vwnd->GetRealParent();
    if (!h) return S_FALSE;

    POINT p={xLeft,yTop};
    ScreenToClient(h,&p);
    if (!m_br.vwnd->GetParent() && __CreateStdAccessibleObject)
    {
      HWND hhit = ChildWindowFromPoint(h,p);
      if (hhit && hhit != h)
      {
        pvarChildAtPoint->pdispVal=0;
        HRESULT res = __CreateStdAccessibleObject(hhit,OBJID_CLIENT,IID_IAccessible,(void**)&pvarChildAtPoint->pdispVal);
        if (SUCCEEDED(res)) 
        {
          pvarChildAtPoint->vt = VT_DISPATCH;
          return S_OK;
        }
      }
    }

    RECT r;
    m_br.vwnd->GetPositionInTopVWnd(&r);
    if (!PtInRect(&r,p)) return S_FALSE;

    WDL_VWnd *vw = m_br.vwnd->VirtWndFromPoint(p.x-r.left,p.y-r.top,0);
    if (vw&&vw != m_br.vwnd)
    {
      IAccessible *pac = GetVWndIAccessible(vw);
      if (pac)
      {
        pvarChildAtPoint->vt = VT_DISPATCH;
        pvarChildAtPoint->pdispVal = pac;
        return S_OK;
      }
    }
    else if (ISVWNDLIST(m_br.vwnd))
    {
      WDL_VirtualListBox *list=(WDL_VirtualListBox*)m_br.vwnd;
      if (list->m_GetItemInfo)
      {
        int c = list->m_GetItemInfo(list,-1,NULL,0,NULL,NULL);
        if (c<0)c=0;
        int a= list->IndexFromPt(p.x-r.left,p.y-r.top);
        if (a>=0 && a<c)
        {
          pvarChildAtPoint->vt = VT_I4;
          pvarChildAtPoint->lVal = 1+list->GetNumChildren()+a;;
          return S_OK;
        }
        else
        {
          POINT pp = { p.x-r.left, p.y-r.top};
          int x;
          for(x=0;x<2;x++)
          {
            RECT *rr = list->GetScrollButtonRect(!!x);
            if (rr && PtInRect(rr,pp))
            {
              pvarChildAtPoint->vt = VT_I4;
              pvarChildAtPoint->lVal = 1+list->GetNumChildren()+c+x;;
              return S_OK;
            }
          }
        }
      }
    }

    pvarChildAtPoint->vt = VT_I4;
    pvarChildAtPoint->lVal = CHILDID_SELF;
    return S_OK;
  }
  STDMETHOD(accDoDefaultAction)(THIS_ VARIANT varChild) 
  {
    if (!m_br.vwnd || varChild.vt != VT_I4) 
    {
      return E_INVALIDARG;
    }
    WDL_VWnd *vw = varChild.lVal == CHILDID_SELF ? m_br.vwnd : m_br.vwnd->EnumChildren(varChild.lVal-1);

    if (!vw) return E_INVALIDARG;

    const char *type = vw->GetType();
    if (type)
    {
      if (!strcmp(type,"vwnd_combobox") ||
          !strcmp(type,"vwnd_iconbutton") ||
          !strcmp(type,"vwnd_tabctrl_child"))
      {
        vw->OnMouseDown(0,0);
        vw->OnMouseUp(0,0);      
        return S_OK;
      }
      else if (!strcmp(type,"vwnd_statictext"))
      {
        vw->OnMouseDblClick(0,0);
        return S_OK;
      }
    }

    return S_FALSE;
  }

    STDMETHOD(put_accName)(THIS_ VARIANT varChild, BSTR szName) 
    {
      return E_NOTIMPL;
    }

    STDMETHOD(put_accValue)(THIS_ VARIANT varChild, BSTR pszValue) 
    {
      return E_NOTIMPL;
    }

  VWndBridge m_br;
  LONG m_refCnt;

  CVWndAccessible *_freelist_next;

};


static IAccessible *GetVWndIAccessible(WDL_VWnd *vwnd)
{
  if (!vwnd) return 0;

  WDL_VWnd_IAccessibleBridge *br = vwnd->GetAccessibilityBridge();
  if (!br)
  {
    CVWndAccessible *acc;
    if (g_freelist_acc)
    {
      g_freelist_acc_size--;
      acc=g_freelist_acc;
      g_freelist_acc=acc->_freelist_next;

      acc->m_br.vwnd = vwnd;
      acc->m_refCnt = 1;
    }
    else 
      acc = new CVWndAccessible(vwnd);

    br = &acc->m_br;
    vwnd->SetAccessibilityBridge(br);
  }
  else ((VWndBridge*)br)->par->AddRef();

  return ((VWndBridge*)br)->par;
}


LRESULT WDL_AccessibilityHandleForVWnd(bool isDialog, HWND hwnd, WDL_VWnd *vw, WPARAM wParam, LPARAM lParam)
{
  if (vw)
  {
    if ((DWORD)lParam != (DWORD)OBJID_CLIENT) return 0;

    static LRESULT (WINAPI *__LresultFromObject)(REFIID riid, WPARAM, LPUNKNOWN);

    static int init;
    if (!init)
    {
      init=1;
      HINSTANCE hInst = LoadLibrary("oleacc.dll");
      if (hInst)
      {
        *(FARPROC *)&__LresultFromObject = GetProcAddress(hInst,"LresultFromObject");
        *(FARPROC *)&__CreateStdAccessibleObject = GetProcAddress(hInst,"CreateStdAccessibleObject");
      }
    }
    if (!__LresultFromObject) return 0;

    IAccessible *ac = GetVWndIAccessible(vw);
    if (!ac) return 0;


    LRESULT res = __LresultFromObject(IID_IAccessible,wParam,ac); // lresultfromobject retains?
    ac->Release();
    if (isDialog)
    {
      SetWindowLongPtr(hwnd,DWLP_MSGRESULT,res);
      return 1;
    }
    return res;
  }
  return 0;
}

#else

#ifdef _WIN32
#include <windows.h>
#else
#include "../swell/swell.h"
#endif 

#include "virtwnd.h"

#ifdef __APPLE__

// see virtwnd-nsaccessibility.mm

#else

LRESULT WDL_AccessibilityHandleForVWnd(bool isDialog, HWND hwnd, WDL_VWnd *vw, WPARAM wParam, LPARAM lParam)
{
  return 0;
}

#endif

#endif
