/** 
 *  Yudit Unicode Editor Source File
 *
 *  GNU Copyright (C) 1997-2023  Gaspar Sinai <gaspar@yudit.org>  
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License, version 2,
 *  dated June 1991. See file COPYYING for details.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "SPanel.h"
#include <swindow/SAwt.h>

#include <stdio.h>

SAwt*   SPanel::awt=0;
int     SPanel::count=0;

static int doubleBufferEnabled = true;

void
SPanel::setAllDoubleBuffered (bool yes)
{
  doubleBufferEnabled = yes;
}

// default is true, this can turn it on/off
void
SPanel::setThisDoubleBuffered (bool yes)
{
  window->setDoubleBuffer (yes);
}

SPanel::SPanel (void) : SComponent ()
{
  modalParent = 0;
  count++;
  if (count == 1) {
    awt = new SAwt();
  }
  window = awt->getWindow (this, "Yudit");
  // For some reason the above is calling component on mac...
  //comp->setWindowInterface (this);
  window->setDoubleBuffer (doubleBufferEnabled);

  size = SDimension (window->getWidth(), window->getHeight());
  location = SLocation (window->getPositionX(), window->getPositionY());
  resizeTimer = 0;
  dropListener = 0;
  hasParent = false;
  layoutOK = true;
}

SPanel::~SPanel ()
{
  count--;
  if (count == 0) {
    delete awt;
  }
  cleanup ();
  delete window;
  if (resizeTimer) delete resizeTimer;
  resizeTimer = 0;
}

void
SPanel::cleanup ()
{
  dropListener = 0;
  unsigned int i;
  for (i=0; i<children.size(); i++) {
    delete children[i];
  }
  for (i=0; i<containers.size(); i++) {
    delete containers[i];
  }
}

/**
 * This is SWindowListener implementation
 */
void
SPanel::redraw(SCanvas* w, int x, int y, unsigned int width ,unsigned int height)
{
  for (unsigned int i=0; i<children.size(); i++)
  {
    children[i]->redraw(w, x, y, width, height);
  }
}

/**
 * SWindowListener
 */
void
SPanel::keyPressed (SWindow * w, SWindowListener::SKey key, const SString& s,
          bool ctrl, bool shift, bool meta)
{
  for (unsigned int i=0; i<children.size(); i++)
  {
    // TODO: Check if it contains child.
    children[i]->keyPressed (w, key, s, ctrl, shift, meta);
  }
}

void
SPanel::keyReleased (SWindow * w, SWindowListener::SKey key, const SString& s,
          bool ctrl, bool shift, bool meta)
{
  for (unsigned int i=0; i<children.size(); i++)
  {
    // TODO: Check if it contains child.
    children[i]->keyReleased (w, key, s, ctrl, shift, meta);
  }
}

void
SPanel::buttonPressed (SWindow * w, int button, int x, int y)
{
  for (unsigned int i=0; i<children.size(); i++)
  {
    // TODO: Check if it contains child.
    children[i]->buttonPressed (w, button, x, y);
  }
}

void
SPanel::buttonReleased (SWindow * w, int button, int x, int y)
{
  for (unsigned int i=0; i<children.size(); i++)
  {
    // TODO: Check if it contains child.
    children[i]->buttonReleased (w, button, x, y);
  }
}

void
SPanel::buttonDragged (SWindow * w, int button, int x, int y)
{
  for (unsigned int i=0; i<children.size(); i++)
  {
    children[i]->buttonDragged (w, button, x, y);
  }
}


/**
 * notification that window got resized.
 */
void
SPanel::resized (SWindow* w, int x, int y, 
  unsigned int width, unsigned int height)
{
  /* nothing to do - we did it already...*/
  if (width == getSize().width && height == getSize().height)
  {
    SLocation lme = SLocation (window->getPositionX(), window->getPositionY());
    SComponent::move (lme);
    return;
  }
  /* you reach this point because window manager changed our size */
  if (hasParent) 
  {
    // to update window sizes we do this.
    //_resized ();
    bool needed = size.width != width|| size.height != height;
    // Mystery. If there are many resize request in X11 queue, 
    // we might not get the latest honoured.
    if (needed) {
//fprintf (stderr, "Forcing re-resize %u,%u.\n", size.width, size.height);
       window->resize (size.width, size.height);
       window->redraw (true, 0, 0, size.width, size.height);
    }
    return;
  }
  if (!window->isShown() ||  !window->isVisible ())
  {
     _resized ();
     return;
  }
  // if we dont resize right away it will cause redrawing artefacts 
  // when window is resized vertically.
#ifdef USE_WINAPI
  _resized ();
   return;
#else
# ifdef USE_OSX
  _resized ();
   return;
# else
  /* 
   * Resize after 100 ms.
   */
  if (resizeTimer)
  {
     return;
  }
  resizeTimer = STimer::newTimer(100, this);
# endif
#endif
}

bool
SPanel::timeout (const SEventSource* s)
{
  if (!resizeTimer) return false;
  delete resizeTimer;
  resizeTimer = 0;
  //if (hasParent) return false;
  _resized ();
  return false;
}


/**
 * Set the size tothe current window size.
 */
void 
SPanel::_resized () 
{
  SDimension dme = SDimension (window->getWidth(), window->getHeight());
  SLocation lme = SLocation (window->getPositionX(), window->getPositionY());
  /* if this came from a timer this may be different */
  SComponent::resize (dme);
  SComponent::move (lme);
/*
   fprintf (stderr, "SPanel%lx ::_resized %u,%u\n", 
     window->getWindowID(), dme.width, dme.height);
*/
//if (dme.width == 12 && dme.height == 12) {
//   return;
//}

  for (unsigned int i=0; i<containers.size(); i++)
  {
    SPanel* p = containers[i];
    if (p->layout.isEmpty())
    {
       //fprintf (stderr, "Empty Layout!\n");
       continue;
    }
    SLocation l = p->layout.getLocation (layout, dme);
    SDimension d = p->layout.getDimension (layout, dme);
    d = d.maximize (SDimension (15000, 15000));
    d = d.minimize (SDimension (2, 2));
    l = l.maximize (SLocation (15000, 15000));
    l = l.minimize (SLocation (-15000, -15000));
    p->move (l);
    p->resize (d);
    p->layoutOK = true;
  }
  layoutOK = true;
}

/**
 * This is SWindowListener implementation
 */
bool
SPanel::windowClose (SWindow* w)
{
  cleanup();
  return true;
}

/**
 * Add a new component.
 * This component will be destroyed by the panel.
 * @param comp is the component to add.
 */
void
SPanel::add (SComponent* comp)
{
  children.append (comp);
  comp->setWindowInterface (this);
  SSlidable* slidable = comp->setSliderListener(this);
  if (slidable)
  {
    slidables.append (slidable);
    slidableComponents.append (comp);
  }
}

/**
 * Add a new component.
 * This panel will be destroyed by the panel.
 * @param panel is the component to add.
 */
void
SPanel::add (SPanel* panel)
{
  panel->hasParent = true;
  containers.append (panel);
  SLayout oldLayout = layout;
  layout += panel->layout;
  SDimension minimumSize = layout.getDimension ();
  if (layout != oldLayout)
  {
    //setMinimumSize (minimumSize);
  }
  SWindow *child = panel->window;
  child->setParent (window, panel->getLocation().x, panel->getLocation().y);
  SSlidable* slidable = panel->setSliderListener(this);
  if (slidable)
  {
    slidables.append (slidable);
    slidableComponents.append (panel);
  }
  panel->window->show();
  layoutOK = false;
  //panel->show();
}

/**
 * should be called after all layout is set 
 */
void
SPanel::setMinimumSize (const SDimension& minimumSize)
{
  window->setMinimumSize (minimumSize.width, minimumSize.height);
}

/**
 * This is the SWindowInterface
 * @return this window
 */
SWindow*
SPanel::getComponentWindow ()
{
  return window;
}
/**
 * Resize the component. This should have immediate effect on size.
 * @param d is the new size
 */
void 
SPanel::resize(const SDimension& d)
{
  bool needed = window->getWidth() != d.width||window->getHeight() != d.height;
  if (needed)
  {
    /* this will generate an event if really needed. */
    window->resize (d.width, d.height);
    _resized ();
  }
  else if (d != size) /* never happens */
  {
     _resized ();
  }
}

/**
 * Resize the component. This should have immediate effect on size.
 * @param l is the new location
 */
void 
SPanel::move(const SLocation& l)
{
  bool needed = window->getPositionX() != l.x || window->getPositionY() != l.y;
needed = true;
  if (needed)
  {
    window->move (l.x, l.y);
    _resized ();
  }
  else if (l != getLocation()) /* never happens */
  {
     _resized ();
  }
}

void
SPanel::setBackground (const SColor& bg)
{
 SComponent::setBackground(bg);
 window->setBackground (bg);
 unsigned int i;
 for (i=0; i<children.size(); i++)
 {
   children[i]->setBackground (bg);
 }
 for (i=0; i<containers.size(); i++) {
   containers[i]->setBackground (bg);
 }
}

void 
SPanel::lostKeyboardFocus (SWindow* w)
{
}

bool 
SPanel::gainedKeyboardFocus (SWindow* w)
{
    return false;
}

void
SPanel::lostClipSelection (SWindow* w)
{
}

void
SPanel::forceLayout (const SLayout& _layout)
{
  layout = _layout;
  if (layout == _layout) return;
  layoutOK = false;
}

void
SPanel::setTitle (const SString& title)
{
  window->setTitle(title);
}

void
SPanel::valueChanged (SSlidable* slidable, SSlideType type)
{
  /* notify everybody except this one. */
  for (unsigned int i=0; i<slidables.size(); i++)
  {
    if (slidables[i] == slidable) continue;
    slidableComponents[i]->valueChanged (slidable, type);
  }
}

/**
 * Listener!
 */
bool
SPanel::drop (SWindow* w, const SString& mimetype, const SString& data)
{
  if (dropListener) return dropListener->dropped (this, mimetype, data);
  return false;
}

void
SPanel::setDropListener (SDropListener*ls, const SStringVector& mimes)
{
  dropListener = ls;
  window->setDroppable (mimes);
}

SDropListener::SDropListener(void)
{
}

SDropListener::~SDropListener()
{
}
/**
 * Adjust the layout of all children, assuming their layout were 
 * calculated using original dimension as parent.
 * This is the preferred way to set a new layout.
 * Don't do anything with children layout unless this layout is set.
 * @param original is the original layout.
 */
void
SPanel::setLayout (const SLayout& _layout)
{
  /* nothing to do. */
  if (layout.isEmpty ())
  {
    layout  = _layout;
    layoutOK = false;
    return;
  }
  if (layout == _layout && isLayoutOK()) return;

  /* new dimension */
  /* adjust containers - set their new layout */
  for (unsigned int i=0; i<containers.size(); i++)
  {
    SPanel* p = containers[i];
    if (p->layout.isEmpty())
    {
      //fprintf (stderr, "Empty Layout!\n");
      continue;
    }
    SLayout newlayout = p->layout;
    newlayout.setLayout (layout, _layout);
    p->setLayout (newlayout); 
  }
  layout = _layout;
  layoutOK = false;
}

void
SPanel::setModal (SPanel* _parent, bool decorated)
{
  if (_parent)
  {
    window->setModal (_parent->window, decorated);
  }
  else
  {
    window->setModal (0, decorated);
  }
  modalParent = _parent;
}
/**
 * Pop up the window
 */
void
SPanel::show()
{
  window->show();
}
/**
 * Pop down the window
 */
void
SPanel::hide()
{
  window->hide();
}

void
SPanel::center ()
{
  window->center ((modalParent==0)?0:modalParent->window);
}

void
SPanel::wait ()
{
  window->wait();
}

bool
SPanel::isShown()
{
  return window->isShown();
}

const SLayout&
SPanel::getLayout () const
{
  return layout;
}

/**
 * Traverse children and figure out if layout is ok
 * A alyout is ok if the resize already worked and there is no
 * layout change.
 * If one of them is false return false.
 * Skip children with no layout.
 */
bool
SPanel::isLayoutOK() const
{
  bool isOK = layoutOK;
  if (!isOK) return false;

  for (unsigned int i=0; i<containers.size(); i++)
  {
    SPanel* p = containers[i];
    if (p->layout.isEmpty()) continue;
    if (!p->isLayoutOK())
    {
       return false;
    }
  }
  return true;
}

void
SPanel::enterWindow (SWindow* w)
{
}

void
SPanel::leaveWindow (SWindow* w)
{
}
