/* Cockos SWELL (Simple/Small Win32 Emulation Layer for Linux/OSX)
   Copyright (C) 2006 and later, Cockos, Inc.

    This software is provided 'as-is', without any express or implied
    warranty.  In no event will the authors be held liable for any damages
    arising from the use of this software.

    Permission is granted to anyone to use this software for any purpose,
    including commercial applications, and to alter it and redistribute it
    freely, subject to the following restrictions:

    1. The origin of this software must not be misrepresented; you must not
       claim that you wrote the original software. If you use this software
       in a product, an acknowledgment in the product documentation would be
       appreciated but is not required.
    2. Altered source versions must be plainly marked as such, and must not be
       misrepresented as being the original software.
    3. This notice may not be removed or altered from any source distribution.
*/

#ifndef SWELL_PROVIDED_BY_APP

#include "swell.h"
#include "swell-dlggen.h"

#include "../ptrlist.h"

static HMENU g_swell_defaultmenu,g_swell_defaultmenumodal;

bool SWELL_owned_windows_levelincrease=false;

#include "swell-internal.h"

static SWELL_DialogResourceIndex *resById(SWELL_DialogResourceIndex *reshead, const char *resid)
{
  SWELL_DialogResourceIndex *p=reshead;
  while (p)
  {
    if (p->resid == resid) return p;
    p=p->_next;
  }
  return 0;
}

// keep list of modal dialogs
struct modalDlgRet { 
  HWND hwnd; 
  bool has_ret;
  int ret;
};


static WDL_PtrList<modalDlgRet> s_modalDialogs;

bool IsModalDialogBox(HWND hwnd)
{
  if (WDL_NOT_NORMALLY(!hwnd)) return false;
  int a = s_modalDialogs.GetSize();
  while (a-- > 0)
  {
    modalDlgRet *r = s_modalDialogs.Get(a);
    if (r && r->hwnd == hwnd) return true;
  }
  return false;
}

HWND DialogBoxIsActive()
{
  int a = s_modalDialogs.GetSize();
  while (a-- > 0)
  {
    modalDlgRet *r = s_modalDialogs.Get(a);
    if (r && !r->has_ret && r->hwnd) return r->hwnd; 
  }
  return NULL;
}

static SWELL_OSWINDOW s_spare;
static RECT s_spare_rect;
static UINT_PTR s_spare_timer;
static int s_spare_style;

void swell_dlg_destroyspare()
{
  if (s_spare_timer)
  {
    KillTimer(NULL,s_spare_timer);
    s_spare_timer=0;
  }
  if (s_spare) 
  { 
#ifdef SWELL_TARGET_GDK
    gdk_window_destroy(s_spare);
#endif
    s_spare=NULL; 
  }
}

static void spareTimer(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwtime)
{
  swell_dlg_destroyspare();
}

static int s_last_dlgret;

void EndDialog(HWND wnd, int ret)
{   
  if (WDL_NOT_NORMALLY(!wnd)) return;
  
  int a = s_modalDialogs.GetSize();
  while (a-->0)
  {
    modalDlgRet *r = s_modalDialogs.Get(a);
    if (r && r->hwnd == wnd)  
    {
      r->ret = ret;
      if (r->has_ret) return;

      r->has_ret=true;
    }
  }

  if (!wnd->m_hashaddestroy)
  {
    void RecurseDestroyWindow(HWND);
    SendMessage(wnd,WM_DESTROY,0,0);
    #ifndef SWELL_NO_SPARE_MODALDLG
      if (wnd->m_oswindow && wnd->m_visible)
      {
        swell_dlg_destroyspare();
        s_spare_rect = wnd->m_position;
        s_spare_style = wnd->m_style;
        s_spare = wnd->m_oswindow;
        wnd->m_oswindow = NULL;
        s_spare_timer = SetTimer(NULL,0,
                             swell_is_app_inactive()>0 ? 500 : 100,
                             spareTimer);
      }
    #endif
    RecurseDestroyWindow(wnd);
  }
  s_last_dlgret = ret;
}

int SWELL_DialogBox(SWELL_DialogResourceIndex *reshead, const char *resid, HWND parent,  DLGPROC dlgproc, LPARAM param)
{
  SWELL_DialogResourceIndex *p=resById(reshead,resid);
  if (resid) // allow modal dialogs to be created without template
  {
    if (!p||(p->windowTypeFlags&SWELL_DLG_WS_CHILD)) return -1;
  }
  else if (parent)
  {
    resid = (const char *)(INT_PTR)(0x400002); // force non-child, force no minimize box
  }


  int ret=-1;
  s_last_dlgret = -1;
  HWND hwnd = SWELL_CreateDialog(reshead,resid,parent,dlgproc,param);
  // create dialog
  if (hwnd)
  {
    hwnd->Retain();

    void SWELL_OnNavigationFocus(HWND ch);
    HWND SWELL_GetFocusedChild(HWND h);
    SWELL_OnNavigationFocus(SWELL_GetFocusedChild(hwnd));

    ReleaseCapture(); // force end of any captures

    WDL_PtrKeyedArray<int> restwnds;
    extern HWND__ *SWELL_topwindows;
    HWND a = SWELL_topwindows;
    while (a)
    {
      if (a!=hwnd) 
      {
        int f=0;
        if (a->m_enabled) { EnableWindow(a,FALSE); f|=1; }
        if (a->m_israised) { SWELL_SetWindowLevel(a,0); f|=2; }
        if (f) restwnds.AddUnsorted((INT_PTR)a,f);
      }
      a = a->m_next;
    }
    restwnds.Resort();
    SWELL_SetWindowLevel(hwnd,1);

    modalDlgRet r = { hwnd,false, -1 };
    s_modalDialogs.Add(&r);

    if (s_spare && s_spare_style == hwnd->m_style)
    {
      if (s_spare_timer) 
      {
        KillTimer(NULL,s_spare_timer);
        s_spare_timer = 0;
      }
      SWELL_OSWINDOW w = s_spare;
      s_spare = NULL;

      int flags = 0;
      const int dw = (hwnd->m_position.right-hwnd->m_position.left) -
                      (s_spare_rect.right - s_spare_rect.left);
      const int dh = (hwnd->m_position.bottom-hwnd->m_position.top) -
                      (s_spare_rect.bottom - s_spare_rect.top);

      if (hwnd->m_has_had_position) flags |= 1;
      if (dw || dh) flags |= 2;

      if (flags == 2)
      {
        // center on the old window
        hwnd->m_position.right -= hwnd->m_position.left;
        hwnd->m_position.bottom -= hwnd->m_position.top;
        hwnd->m_position.left = s_spare_rect.left - dw/2;
        hwnd->m_position.top = s_spare_rect.top - dh/2;
        hwnd->m_position.right += hwnd->m_position.left;
        hwnd->m_position.bottom += hwnd->m_position.top;
        flags = 3;
      }
          
      if (flags)
      {
        if (flags&2) swell_oswindow_begin_resize(w);
        swell_oswindow_resize(w, flags, hwnd->m_position);
      }
      hwnd->m_oswindow = w;

      if (!flags)
      {
        hwnd->m_has_had_position = true;
        hwnd->m_position = s_spare_rect;
        if (!hwnd->m_hashaddestroy)
        {
          void swell_recalcMinMaxInfo(HWND hwnd);
          swell_recalcMinMaxInfo(hwnd);
        }
      }
      swell_oswindow_focus(hwnd);

      ShowWindow(hwnd,SW_SHOWNA);
      InvalidateRect(hwnd,NULL,FALSE);
    }
    else  
    {
      swell_dlg_destroyspare();
      ShowWindow(hwnd,SW_SHOW);
    }
 
    while (!r.has_ret && !hwnd->m_hashaddestroy)
    {
      void SWELL_RunMessageLoop();
      SWELL_RunMessageLoop();
      Sleep(10);
    }
    ret=r.ret;
    s_modalDialogs.DeletePtr(&r);

    a = SWELL_topwindows;
    while (a)
    {
      if (a != hwnd) 
      {
        int f = restwnds.Get((INT_PTR)a);
        if (!a->m_enabled && (f&1)) EnableWindow(a,TRUE);
        if (!a->m_israised && (f&2)) SWELL_SetWindowLevel(a,1);
      }
      a = a->m_next;
    }
    hwnd->Release();
  }
  else 
  {
    ret = s_last_dlgret; // SWELL_CreateDialog() failed, implies WM_INITDIALOG could have called EndDialog()
  }
  // while in list, do something
  return ret;
}

HWND SWELL_CreateDialog(SWELL_DialogResourceIndex *reshead, const char *resid, HWND parent, DLGPROC dlgproc, LPARAM param)
{
  int forceStyles=0; // 1=resizable, 2=no minimize, 4=no close
  bool forceNonChild=false;
  if ((((INT_PTR)resid)&~0xf)==0x400000)
  {
    forceStyles = (int) (((INT_PTR)resid)&0xf);
    if (forceStyles) forceNonChild=true;
    resid=0;
  }
  SWELL_DialogResourceIndex *p=resById(reshead,resid);
  if (!p&&resid) return 0;
  
  // SWELL_DLG_WS_DEFAULT_SCALING is not used on -generic, because it is not needed (DPI-scaling instead)
  RECT r={0,0,SWELL_UI_SCALE(p ? p->width : 300), SWELL_UI_SCALE(p ? p->height : 200) };
  HWND owner=NULL;

  if (!forceNonChild && parent && (!p || (p->windowTypeFlags&SWELL_DLG_WS_CHILD)))
  {
  } 
  else 
  {
    owner = parent;
    parent = NULL; // top level window
  }

  HWND__ *h = new HWND__(parent,0,&r,NULL,false,NULL,NULL, owner);
  if (forceNonChild || (p && !(p->windowTypeFlags&SWELL_DLG_WS_CHILD)))
  {
    if ((forceStyles&1) || (p && (p->windowTypeFlags&SWELL_DLG_WS_RESIZABLE))) 
      h->m_style |= WS_THICKFRAME|WS_CAPTION;
    else h->m_style |= WS_CAPTION;
  }
  else if (!p && !parent) h->m_style |= WS_CAPTION;
  else if (parent && (!p || (p->windowTypeFlags&SWELL_DLG_WS_CHILD))) h->m_style |= WS_CHILD;

  if (p)
  {
    h->m_style |= p->windowTypeFlags & (WS_CLIPSIBLINGS);
    if (p->windowTypeFlags&SWELL_DLG_WS_DROPTARGET)
      h->m_exstyle|=WS_EX_ACCEPTFILES;
  }

  h->Retain();

  if (p)
  {
    p->createFunc(h,p->windowTypeFlags);
    if (p->title) SetWindowText(h,p->title);

    h->m_dlgproc = dlgproc;
    h->m_wndproc = SwellDialogDefaultWindowProc;

    HWND hFoc=h->m_children;
    while (hFoc)
    {
      if (hFoc->m_wantfocus && hFoc->m_visible && hFoc->m_enabled) 
      {
        h->m_focused_child = hFoc; // default focus to hFoc, but set focus more aggressively after WM_INITDIALOG if the dlgproc returns 1
        break;
      }
      hFoc=hFoc->m_next;
    }

    if (hFoc) hFoc->Retain();

    if (h->m_dlgproc(h,WM_INITDIALOG,(WPARAM)hFoc,param))
    {
      if (hFoc && hFoc->m_wantfocus && hFoc->m_visible && hFoc->m_enabled)
      {
        if (!h->m_hashaddestroy && !hFoc->m_hashaddestroy)
          SetFocus(hFoc);
      }
    }

    if (hFoc) hFoc->Release();
  }
  else
  {
    h->m_wndproc = (WNDPROC)dlgproc;
    h->m_wndproc(h,WM_CREATE,0,param);
  }

  HWND rv = h->m_hashaddestroy ? NULL : h;
  h->Release();
  return rv;
}


HMENU SWELL_GetDefaultWindowMenu() { return g_swell_defaultmenu; }
void SWELL_SetDefaultWindowMenu(HMENU menu)
{
  g_swell_defaultmenu=menu;
}
HMENU SWELL_GetDefaultModalWindowMenu() 
{ 
  return g_swell_defaultmenumodal; 
}
void SWELL_SetDefaultModalWindowMenu(HMENU menu)
{
  g_swell_defaultmenumodal=menu;
}



SWELL_DialogResourceIndex *SWELL_curmodule_dialogresource_head; // this eventually will go into a per-module stub file

#endif
