/*
 *
 */

#include "gm/gm.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkFont.h"
#include "include/core/SkFontTypes.h"
#include "include/core/SkPaint.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
#include "include/core/SkSize.h"
#include "include/core/SkString.h"
#include "include/core/SkTextBlob.h"
#include "include/core/SkTypeface.h"
#include "tools/ToolUtils.h"

#include <string.h>

namespace skiagm {
class TextBlobTransforms : public GM {
public:
    // This gm tests that textblobs can be translated, rotated, and scaled

protected:
    void onOnceBeforeDraw() override {
        SkTextBlobBuilder builder;

        // make textblob.  To stress distance fields, we choose sizes appropriately
        SkFont font(ToolUtils::create_portable_typeface(), 162);
        font.setEdging(SkFont::Edging::kAlias);
        const char* text = "A";

        SkRect bounds;
        font.measureText(text, strlen(text), SkTextEncoding::kUTF8, &bounds);
        ToolUtils::add_to_text_blob(&builder, text, font, 0, 0);

        // Medium
        SkScalar xOffset = bounds.width() + 5;
        font.setSize(72);
        text = "B";
        ToolUtils::add_to_text_blob(&builder, text, font, xOffset, 0);

        font.measureText(text, strlen(text), SkTextEncoding::kUTF8, &bounds);
        SkScalar yOffset = bounds.height();

        // Small
        font.setSize(32);
        text = "C";
        ToolUtils::add_to_text_blob(&builder, text, font, xOffset, -yOffset - 10);

        // build
        fBlob = builder.make();
    }

    SkString onShortName() override {
        return SkString("textblobtransforms");
    }

    SkISize onISize() override {
        return SkISize::Make(kWidth, kHeight);
    }

    void onDraw(SkCanvas* canvas) override {

        canvas->drawColor(SK_ColorGRAY);

        SkPaint paint;

        SkRect bounds = fBlob->bounds();
        canvas->translate(20, 20);

        // Colors were chosen to map to pairs of canonical colors.  The GPU Backend will cache A8
        // Texture Blobs based on the canonical color they map to.  Canonical colors are used to
        // create masks.  For A8 there are 8 of them.

        SkScalar xOffset = SkScalarCeilToScalar(bounds.width());
        SkScalar yOffset = SkScalarCeilToScalar(bounds.height());
        // first translate
        canvas->translate(xOffset, 2 * yOffset);
        canvas->drawTextBlob(fBlob, 0, 0, paint);
        canvas->translate(-xOffset, 0);
        canvas->drawTextBlob(fBlob, 0, 0, paint);
        canvas->translate(2 * xOffset, 0);
        canvas->drawTextBlob(fBlob, 0, 0, paint);
        canvas->translate(-xOffset, -yOffset);
        canvas->drawTextBlob(fBlob, 0, 0, paint);
        canvas->translate(0, 2 * yOffset);
        canvas->drawTextBlob(fBlob, 0, 0, paint);

        // now rotate
        canvas->translate(4 * xOffset, -yOffset);
        canvas->rotate(180.f);
        canvas->drawTextBlob(fBlob, 0, 0, paint);
        canvas->rotate(-180.f);
        canvas->translate(0, -yOffset);
        canvas->rotate(-180.f);
        canvas->drawTextBlob(fBlob, 0, 0, paint);
        canvas->rotate(270.f);
        canvas->drawTextBlob(fBlob, 0, 0, paint);
        canvas->rotate(-90.f);
        canvas->translate(-xOffset, yOffset);
        canvas->rotate(-90.f);
        canvas->drawTextBlob(fBlob, 0, 0, paint);
        canvas->rotate(90.f);

        // and scales
        canvas->translate(- 3 * xOffset, 3 * yOffset);
        canvas->scale(1.5f, 1.5f);
        canvas->drawTextBlob(fBlob, 0, 0, paint);
        canvas->translate(xOffset, 0);
        canvas->scale(.25f, .25f);
        canvas->drawTextBlob(fBlob, 0, 0, paint);
        canvas->translate(xOffset, 0);
        canvas->scale(3.f, 2.f);
        canvas->drawTextBlob(fBlob, 0, 0, paint);

        // finally rotates, scales, and translates together
        canvas->translate(xOffset, 0);
        canvas->rotate(23.f);
        canvas->scale(.33f, .5f);
        canvas->drawTextBlob(fBlob, 0, 0, paint);

        canvas->rotate(-46.f);
        canvas->translate(xOffset, 0);
        canvas->scale(1.2f, 1.1f);
        canvas->drawTextBlob(fBlob, 0, 0, paint);

        canvas->rotate(46.f);
        canvas->translate(xOffset, 0);
        canvas->scale(1.1f, 1.2f);
        canvas->drawTextBlob(fBlob, 0, 0, paint);

        canvas->rotate(46.f);
        canvas->translate(xOffset, 0);
        canvas->scale(.95f, 1.1f);
        canvas->drawTextBlob(fBlob, 0, 0, paint);

        canvas->rotate(46.f);
        canvas->translate(xOffset, 0);
        canvas->scale(1.3f, .7f);
        canvas->drawTextBlob(fBlob, 0, 0, paint);

        canvas->rotate(46.f);
        canvas->translate(xOffset, 0);
        canvas->scale(.8f, 1.1f);
        canvas->drawTextBlob(fBlob, 0, 0, paint);

        canvas->rotate(10.f);
        canvas->translate(xOffset, 0);
        canvas->scale(1.f, 5.f);
        canvas->drawTextBlob(fBlob, 0, 0, paint);

        canvas->rotate(5.f);
        canvas->translate(xOffset, 0);
        canvas->scale(5.f, 1.f);
        canvas->drawTextBlob(fBlob, 0, 0, paint);
    }

private:
    sk_sp<SkTextBlob> fBlob;

    inline static constexpr int kWidth = 1000;
    inline static constexpr int kHeight = 1200;

    using INHERITED = GM;
};

DEF_GM(return new TextBlobTransforms;)
}  // namespace skiagm


WindowManager::~WindowManager()
{
  _WMExists = false;
}

/*
 *
 */

#include <QMouseEvent>
#include <QApplication>
#include <QMainWindow>
#include <QDialog>
#include <QLabel>
#include <QMenuBar>
#include <QToolBar>
#include <QStatusBar>
#include <QToolButton>
#include <QCheckBox>
#include <QRadioButton>
#include <QGroupBox>
#include <QTabBar>
#include <QListView>
#include <QTreeView>
#include <QGraphicsView>
#include <QDockWidget>
#include <QMdiArea>
#include <QMdiSubWindow>
#include "windowmanager.h"

namespace Kvantum {

static bool _WMExists = false;

static inline bool isPrimaryToolBar (QWidget *w)
{
  if (w == nullptr) return false;
  QToolBar *tb = qobject_cast<QToolBar*>(w);
  if (tb || strcmp (w->metaObject()->className(), "ToolBar") == 0)
  {
    if (tb == nullptr || Qt::Horizontal == tb->orientation())
    {
      QWidget *p = w->parentWidget();
      if (p != w->window()) return false; // inside a dock

      if (0 == w->pos().y())
        return true;

      if (QMainWindow *mw = qobject_cast<QMainWindow *>(p))
      {
        if (QWidget *menuW = mw->menuWidget())
          return menuW->isVisible() && w->pos().y() <= menuW->height()+1;
      }
    }
  }
  return false;
}

static inline QWidget* toolbarContainer (QWidget *w)
{
  if (w == nullptr || qobject_cast<const QToolBar*>(w))
    return nullptr;
  QWidget *window = w->window();
  if (window == w) return nullptr;
  if (qobject_cast<const QToolBar*>(window)) // detached toolbar
    return window;
  const QList<QToolBar*> toolbars = window->findChildren<QToolBar*>(QString(), Qt::FindDirectChildrenOnly);
  for (QToolBar *tb : toolbars)
  {
    if (tb->isAncestorOf (w))
      return tb;
  }
  return nullptr;
}

WindowManager::WindowManager (QObject *parent, Drag drag, bool dragFromBtns) :
               QObject (parent),
               enabled_ (true),
               dragDistance_ (qMax (QApplication::startDragDistance(), 10)),
               dragDelay_ (qMax (QApplication::startDragTime(), 500)),
               doubleClickInterval_ (QApplication::doubleClickInterval()),
               isDelayed_ (false),
               dragAboutToStart_ (false),
               dragInProgress_ (false),
               locked_ (false),
               dragFromBtns_ (dragFromBtns),
               drag_ (drag)
{
  _appEventFilter = new AppEventFilter (this);
  qApp->installEventFilter (_appEventFilter);
}
/*************************/

/*************************/
void WindowManager::initialize (const QStringList &blackList)
{
  setEnabled (true);
  initializeBlackList (blackList);
}
/*************************/
void WindowManager::registerWidget (QWidget *widget)
{
  if (!widget || !widget->isWindow()) return;
  Qt::WindowType type = widget->windowType();
  if (type != Qt::Window && type != Qt::Dialog
      && type != Qt::Sheet) // a Qt5 bug on Linux
  {
    return;
  }
  if (QWindow *w = widget->windowHandle())
  {
    w->removeEventFilter (this);
    w->installEventFilter (this);
  }
  else
  { // wait until the window ID is changed (see WindowManager::eventFilter)
    widget->removeEventFilter (this);
    widget->installEventFilter (this);
  }
}
/*************************/
void WindowManager::unregisterWidget (QWidget *widget)
{
  if (!widget) return;
  widget->removeEventFilter (this);
  if (widget->isWindow())
  {
    if (QWindow *w = widget->windowHandle())
      w->removeEventFilter (this);
  }
}
/*************************/
void WindowManager::initializeBlackList (const QStringList &list)
{
  blackList_.clear();
  blackList_.insert (ExceptionId (QStringLiteral ("CustomTrackView@kdenlive")));
  blackList_.insert (ExceptionId (QStringLiteral ("MuseScore")));
  blackList_.insert (ExceptionId (QStringLiteral ("KGameCanvasWidget")));
  blackList_.insert (ExceptionId (QStringLiteral ("QQuickWidget")));
  blackList_.insert (ExceptionId (QStringLiteral ("*@soffice.bin")));
  for (const QString& exception : list)
  {
    ExceptionId id (exception);
    if (!id.className().isEmpty())
      blackList_.insert (exception);
  }
}
/*************************/
bool WindowManager::eventFilter (QObject *object, QEvent *event)
{
  if (!enabled()) return false;

  switch (event->type())
  {
    case QEvent::MouseButtonPress:
      return mousePressEvent (object, event);
      break;

    case QEvent::MouseMove:
      if (object == winTarget_.data())
        return mouseMoveEvent (event);
      break;

    case QEvent::MouseButtonRelease:
      if (object == winTarget_.data())
        return mouseReleaseEvent (event);
      break;

    case QEvent::WindowBlocked:
    case QEvent::Leave:
    case QEvent::Hide:
      if (object == winTarget_.data())
        return leavingWindow();
      break;

    case QEvent::WinIdChange: {
      QWidget *widget = qobject_cast<QWidget*>(object);
      if (!widget || !widget->isWindow()) break;
      Qt::WindowType type = widget->windowType();
      if (type != Qt::Window && type != Qt::Dialog && type != Qt::Sheet
          && type != Qt::Tool) // an exception; see WindowManager::canDrag()
      {
        break;
      }
      if (QWindow *w = widget->windowHandle())
      {
        w->removeEventFilter (this);
        w->installEventFilter (this);
      }
      break;
    }

    default:
      break;
  }

  return false;
}
/*************************/
void WindowManager::timerEvent (QTimerEvent *event)
{
  QObject::timerEvent (event);
  if (event->timerId() == dragTimer_.timerId())
  {
    dragTimer_.stop();
    if (winTarget_)
    {
      if (qApp->activePopupWidget()
          || !(QGuiApplication::mouseButtons() & Qt::LeftButton))
      {
        /* WARNING: Due to a Qt bug, that exists under X11 and Wayland alike,
                    the drag may be started even when an active popup is shown.
                    As a workaround, we don't start dragging in this case.

                    Also, rarely, the left mouse button may have been released
                    inside a popup, without sending an event to the window. */
        winTarget_.data()->unsetCursor();
        resetDrag();
        unlock();
        isDelayed_ = false;
      }
      else if (isDelayed_)
      {
        /* NOTE: Under X11, if dragging is started with a delay and the left
                 mouse button is released shortly after it, it will continue
                 until a mouse button is pressed or the mouse wheel is turned.
                 As a workaround, we don't start dragging with a delay but
                 only change and restore the window cursor appropriately. */
        winTarget_.data()->setCursor (Qt::OpenHandCursor);
        isDelayed_ = false;
      }
      else
      {
        winTarget_.data()->unsetCursor();
        _WMExists = true;
        if (widgetTarget_)
        {
          /* NOTE: On starting the drag, we release the mouse outside the widget to
                   prevent a click and also counterbalance the mouse press that was
                   sent by WindowManager::mousePressEvent(). In this way, it's also
                   possible to drag from button-like widgets safely (as in GTK). */
          QMouseEvent e (QEvent::MouseButtonRelease,
                         QPoint (-1, -1),
                         Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
          qApp->sendEvent (widgetTarget_.data(), &e);
        }
        if (!_WMExists) return; // see mousePressEvent for the reason
        if (winTarget_)
          dragInProgress_ = winTarget_.data()->startSystemMove();
        resetDrag(); // clear the drag info, showing that the drag is started
      }
    }
  }
  else if (event->timerId() == doubleClickTimer_.timerId())
    doubleClickTimer_.stop();
}
/*************************/
bool WindowManager::mousePressEvent (QObject *object, QEvent *event)
{
  QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
  if (!(mouseEvent->modifiers() == Qt::NoModifier && mouseEvent->button() == Qt::LeftButton))
    return false;

  /* find the window and its drag point */
  QWindow *w = qobject_cast<QWindow*>(object);
  if (!w) return false;
  QPoint winDragPoint = mouseEvent->pos();

  if (doubleClickTimer_.isActive())
  {
    doubleClickTimer_.stop();
    if (lastWin_.data() == w
        && QPoint (winDragPoint - lastWinDragPoint_).manhattanLength() < dragDistance_)
    { // don't drag by double clicking
      resetDrag();
      unlock();
      return false;
    }
  }
  doubleClickTimer_.start (doubleClickInterval_, this);

  if (qApp->activePopupWidget())
  { // -> the workaround in timerEvent()
    resetDrag();
    unlock();
    return false;
  }

  /* check the lock and drag state */
  if (isLocked() || dragInProgress_)
  {
    resetDrag();
    unlock();
    if (lastWin_.data() == w)
      return false;
    /* if the window is changed, start a new drag */
  }

  /* remember the window and its drag point */
  lastWin_ = w;
  lastWinDragPoint_ = winDragPoint;

  /* find the widget */
  QWidget *widget = nullptr;
  QWidget *activeWin = qApp->activeWindow();
  if (!activeWin)
  {
    /* This can happen under Wayland if the window is inactive before being
       dragged. QApplication::widgetAt() is fast because the window can't have
       "Qt::WA_TransparentForMouseEvents" (-> Qt -> QApplication::widgetAt). */
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
    widget = qApp->widgetAt (mouseEvent->globalPos());
#else
    widget = qApp->widgetAt (mouseEvent->globalPosition().toPoint());
#endif
  }
  else
  {
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
    widget = activeWin->childAt (activeWin->mapFromGlobal (mouseEvent->globalPos()));
#else
    widget = activeWin->childAt (activeWin->mapFromGlobal (mouseEvent->globalPosition()).toPoint());
#endif
    if (!widget)
      widget = activeWin;
  }
  if (!widget) return false;

#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
  widgetDragPoint_ = widget->mapFromGlobal (mouseEvent->globalPos()); // needed by canDrag()
#else
  widgetDragPoint_ = widget->mapFromGlobal (mouseEvent->globalPosition().toPoint()); // needed by canDrag()
#endif

  /* check if the widget can be dragged */
  dragDistance_ = qMax (QApplication::startDragDistance(), 10);
  dragDelay_ = qMax (QApplication::startDragTime(), 500);
  if (isBlackListed (widget) || !canDrag (widget))
    return false;

  /* save some targets and drag points */
  winTarget_ = w;
  widgetTarget_ = widget;
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
  globalDragPoint_ = mouseEvent->globalPos();
#else
  globalDragPoint_ = mouseEvent->globalPosition().toPoint();
#endif
  dragAboutToStart_ = true;

  /* clear the info about pressing the mouse button */
  pressedWidget_.clear();
  lastPressedWidget_.clear();

  _WMExists = true;

  /* Because the widget may react to mouse press events,
     we first send a press event to it. */
  QMouseEvent mousePress (QEvent::MouseButtonPress, widgetDragPoint_, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
  qApp->sendEvent (widget, &mousePress);

  /* Theoretically, it's possible that the WindowManager instance
     gets deleted as the result of the above mouse press event. */
  if (!_WMExists)
    return true;

  /* If the target window is left, blocked or hidden now, the drag
     is cancelled (-> leavingWindow). Steal the press event! */
  if (winTarget_ == nullptr)
    return true;

  if (widgetTarget_ == nullptr // the widget has been deleted
      || !widget->isVisible()) // as with Fm::PathBar
  {
    resetDrag();
    return true;
  }

  /* don't start dragging if the mouse press is accepted
     but allow dragging from inside some widget types */
  if (mousePress.isAccepted())
  {
    /* the last pressed widget may do something with the press event */
    if (!isDraggable (lastPressedWidget_ ? lastPressedWidget_.data() : widget))
    {
      resetDrag();
      if (qApp->activePopupWidget()) // steals mouse events
        return true;
      /* The event shouldn't be consumed because it may start a DND or click
         but a press event will be sent to the widget immediately after "false"
         is returned here. Therefore, we need to remember the wdget in order to
         prevent a double press in WindowManager::AppEventFilter::eventFilter(). */
      pressedWidget_ = widget;
      return false;
    }
  }

  locked_ = true;

  /* Send a move event to the target window with the same position.
     If received, it is caught to actually start the drag. */
  QMouseEvent mouseMove (QEvent::MouseMove, winDragPoint, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
  qApp->sendEvent (w, &mouseMove);

  /* NOTE: The event should be consumed; otherwise, mouseover effects
           won't work after dragging (until a mouse button is pressed). */
  return true;
}
/*************************/
bool WindowManager::mouseMoveEvent (QEvent *event)
{
  /* make sure that the left mouse button is still pressed */
  if (!(static_cast<QMouseEvent*>(event)->buttons() & Qt::LeftButton))
    return false;

  if (!dragInProgress_)
  {
    QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
    if (dragAboutToStart_)
    {
      dragAboutToStart_ = false;
      if (dragTimer_.isActive())
        dragTimer_.stop();
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
      if (QPoint (mouseEvent->globalPos() - globalDragPoint_).manhattanLength() < dragDistance_)
#else
      if (QPoint (mouseEvent->globalPosition().toPoint() - globalDragPoint_).manhattanLength() < dragDistance_)
#endif
      {
        isDelayed_ = true;
        dragTimer_.start (dragDelay_, this);
      }
      else
      {
        /* the cursor moved too fast; perhaps the window was
           inactive before the left mouse button was pressed */
        isDelayed_ = false;
        dragTimer_.start (0, this);
      }
    }
    else if (!dragTimer_.isActive() // drag timeout
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
             || QPoint (mouseEvent->globalPos() - globalDragPoint_).manhattanLength() >= dragDistance_)
#else
             || QPoint (mouseEvent->globalPosition().toPoint() - globalDragPoint_).manhattanLength() >= dragDistance_)
#endif
    {
      if (dragTimer_.isActive())
        dragTimer_.stop();
      isDelayed_ = false;
      dragTimer_.start (0, this);
    }

    return true;
  }
  return false;
}
/*************************/
bool WindowManager::mouseReleaseEvent (QEvent *event)
{
  /* unlock the window and click the widget if the
     left mouse button is released before dragging */
  if (!dragInProgress_ && widgetTarget_)
  {
    QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
    if (mouseEvent->button() == Qt::LeftButton)
    {
      auto e = new QMouseEvent (QEvent::MouseButtonRelease,
                                widgetDragPoint_,
                                Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
      qApp->postEvent (widgetTarget_.data(), e);
      resetDrag();
      unlock();
    }
    return true; // the press event was consumed
  }
  return false;
}
/*************************/
bool WindowManager::leavingWindow()
{
  if (!dragInProgress_ && widgetTarget_)
  {
    resetDrag(); // left, blocked or hidden before the drag
    unlock();
  }
  return false;
}
/*************************/
bool WindowManager::isBlackListed (QWidget *widget)
{
  /* check against noAnimations propery */
  QVariant propertyValue (widget->property ("_kde_no_window_grab"));
  if (propertyValue.isValid() && propertyValue.toBool())
    return true;

  /* list-based blacklisted widgets */
  QString appName (qApp->applicationName());
  for (const ExceptionId& id : static_cast<const ExceptionSet&>(blackList_))
  {
    if (!id.appName().isEmpty() && id.appName() != appName)
      continue;
    if (id.className() == "*" && !id.appName().isEmpty())
    {
      /* if application name matches and all classes are selected,
         disable the grabbing entirely */
      setEnabled (false);
      return true;
    }
    if (widget->inherits (id.className().toLatin1().data()))
      return true;
  }
  return false;
}
/*************************/
bool WindowManager::canDrag (QWidget *widget)
{
  if (!widget || !enabled()
      || drag_ == DRAG_NONE) // impossible
  {
    return false;
  }

  if (QWidget::mouseGrabber()) return false;

  /* assume that a changed cursor means that some action is in progress
     and should prevent the drag */
  if (widget->cursor().shape() != Qt::ArrowCursor)
    return false;

  QWidget *win = widget->window();
  if (win == widget
      && !qobject_cast<QMainWindow*>(widget)
      && !qobject_cast<QDialog*>(widget))
  {
    return false;
  }
  if (win->testAttribute (Qt::WA_X11NetWmWindowTypeDesktop))
    return false;
  /* the window type may have changed but we accept Qt::Tool (as in Krita) */
  Qt::WindowType type = win->windowType();
  if (type != Qt::Window && type != Qt::Dialog && type != Qt::Sheet
      && type != Qt::Tool)
  {
    return false;
  }
  /* X11BypassWindowManagerHint can be used to fix the position */
  if (win->windowFlags().testFlag (Qt::X11BypassWindowManagerHint)
      || win->windowFlags().testFlag (Qt::WindowDoesNotAcceptFocus))
  {
    return false;
  }

  QWidget *parent = widget;
  while (parent)
  {
    if (qobject_cast<QMdiSubWindow*>(parent))
      return false;
    if (parent->isWindow()) break; // not inside a subwindow
    parent = parent->parentWidget();
  }

  if (qobject_cast<QMdiArea*>(widget->parentWidget()))
    return false;

  if (QMenuBar *menuBar = qobject_cast<QMenuBar*>(widget))
  {
    if (menuBar->activeAction() && menuBar->activeAction()->isEnabled())
      return false;
    if (QAction *action = menuBar->actionAt (widgetDragPoint_))
    {
      if (action->isSeparator()) return true;
      if (action->isEnabled()) return false;
    }
    return true;
  }

  /* toolbar */
  if (drag_ == DRAG_ALL && qobject_cast<QToolBar*>(widget))
    return true;
  if (drag_ < DRAG_ALL)
  {
    if (drag_ == DRAG_MENUBAR_ONLY) return false;

    if (isPrimaryToolBar (widget)) return true;

    QWidget *tb = toolbarContainer (widget);
    if (tb == nullptr) return false;

    /* consider some widgets inside toolbars */
    bool draggedBtn (dragFromBtns_ && qobject_cast<QAbstractButton*>(widget));
    bool allowedBtn = false;
    if (QToolButton *toolButton = qobject_cast<QToolButton*>(widget))
      allowedBtn = toolButton->autoRaise() && !toolButton->isEnabled();
    if ((!draggedBtn && !allowedBtn && widget->testAttribute (Qt::WA_Hover))
        || widget->testAttribute (Qt::WA_SetCursor))
    {
      return false;
    }
    if (QLabel *label = qobject_cast<QLabel*>(widget))
    {
      if (label->textInteractionFlags().testFlag (Qt::TextSelectableByMouse))
        return false;
    }

    if (!isPrimaryToolBar (tb)) return false;
    if (draggedBtn)
    { // to make pressing buttons easier
      dragDistance_ *= 1.5;
      dragDelay_ *= 2;
    }
    return true;
  }

  if (QTabBar *tabBar = qobject_cast<QTabBar*>(widget))
    return tabBar->tabAt (widgetDragPoint_) == -1;

  if (qobject_cast<QStatusBar*>(widget))
    return true;

  /* pay attention to some details of item views */
  QAbstractItemView *itemView (nullptr);
  bool draggable (false);
  if ((itemView = qobject_cast<QListView*>(widget->parentWidget()))
      || (itemView = qobject_cast<QTreeView*>(widget->parentWidget())))
  {
    if (widget == itemView->viewport())
    {
      if (isBlackListed (itemView))
        return false;
      if (itemView->frameShape() != QFrame::NoFrame)
        return false;
      if (itemView->selectionMode() != QAbstractItemView::NoSelection
          && itemView->selectionMode() != QAbstractItemView::SingleSelection
          && itemView->model() && itemView->model()->rowCount())
      {
        return false;
      }
      if (itemView->model() && itemView->indexAt (widgetDragPoint_).isValid())
        return false;
      draggable = true;
    }
  }
  else if ((itemView = qobject_cast<QAbstractItemView*>(widget->parentWidget())))
  {
    if (widget == itemView->viewport())
    {
      if (isBlackListed (itemView))
        return false;
      if (itemView->frameShape() != QFrame::NoFrame)
        return false;
      if (itemView->indexAt (widgetDragPoint_).isValid())
        return false;
      draggable = true;
    }
  }
  else if (QGraphicsView *graphicsView = qobject_cast<QGraphicsView*>(widget->parentWidget()))
  {
    if (widget == graphicsView->viewport())
    {
      return false; // can be troublesome for users of apps like kpat
      /*if (isBlackListed (graphicsView))
        return false;
      if (graphicsView->frameShape() != QFrame::NoFrame)
        return false;
      if (graphicsView->dragMode() != QGraphicsView::NoDrag)
        return false;
      if (graphicsView->itemAt (widgetDragPoint_))
        return false;
      draggable = true;*/
    }
  }
  if (draggable)
  {
    if (widget->focusPolicy() > Qt::TabFocus
        || (widget->focusProxy() && widget->focusProxy()->focusPolicy() > Qt::TabFocus))
    { // focus the widget if it's an item view that accepts focus by clicking
      widget->setFocus (Qt::MouseFocusReason);
    }
    return true;
  }

  /* allow dragging from outside focus rectangles and indicators
     of check boxes and radio buttons */
  if (QCheckBox *b = qobject_cast<QCheckBox*>(widget))
  {
    QStyleOptionButton opt;
    opt.initFrom (b);
    opt.text = b->text();
    opt.icon = b->icon();
    if (widget->style()->subElementRect (QStyle::SE_CheckBoxFocusRect, &opt, b)
        .united (widget->style()->subElementRect (QStyle::SE_CheckBoxIndicator, &opt, b))
        .contains (widgetDragPoint_))
    {
      return false;
    }
    return true;
  }
  if (QRadioButton *b = qobject_cast<QRadioButton*>(widget))
  {
    QStyleOptionButton opt;
    opt.initFrom (b);
    opt.text = b->text();
    opt.icon = b->icon();
    if (widget->style()->subElementRect (QStyle::SE_RadioButtonFocusRect, &opt, b)
        .united (widget->style()->subElementRect (QStyle::SE_RadioButtonIndicator, &opt, b))
        .contains (widgetDragPoint_))
    {
      return false;
    }
    return true;
  }

  /* allow dragging from outside focus rectangles and indicators of group boxes */
  if (QGroupBox *groupBox = qobject_cast<QGroupBox*>(widget))
  {
    if (!groupBox->isCheckable()) return true;
    QStyleOptionGroupBox opt;
    opt.initFrom (groupBox);
    if (groupBox->isFlat())
      opt.features |= QStyleOptionFrame::Flat;
    opt.lineWidth = 1;
    opt.midLineWidth = 0;
    opt.text = groupBox->title();
    opt.textAlignment = groupBox->alignment();
    opt.subControls = (QStyle::SC_GroupBoxFrame | QStyle::SC_GroupBoxCheckBox);
    if (!groupBox->title().isEmpty())
      opt.subControls |= QStyle::SC_GroupBoxLabel;
    QRect r = groupBox->style()->subControlRect (QStyle::CC_GroupBox, &opt, QStyle::SC_GroupBoxCheckBox, groupBox);
    if (!groupBox->title().isEmpty())
      r = r.united (groupBox->style()->subControlRect (QStyle::CC_GroupBox, &opt, QStyle::SC_GroupBoxLabel, groupBox));
    return !r.contains (widgetDragPoint_);
  }

  if (QDockWidget *dw = qobject_cast<QDockWidget*>(widget))
  {
    if (dw->allowedAreas() == (Qt::DockWidgetArea_Mask | Qt::AllDockWidgetAreas))
      return true;
  }
  else if (qobject_cast<QDockWidget*>(widget->parentWidget()))
    return !qobject_cast<QAbstractButton*>(widget); // not a titlebar button

  bool draggedBtn (dragFromBtns_ && qobject_cast<QAbstractButton*>(widget));
  bool allowedBtn = false;
  if (QToolButton *toolButton = qobject_cast<QToolButton*>(widget))
    allowedBtn = toolButton->autoRaise() && !toolButton->isEnabled();
  if ((!draggedBtn && !allowedBtn && widget->testAttribute (Qt::WA_Hover))
      || widget->testAttribute (Qt::WA_SetCursor))
  {
    return false;
  }

  /* interacting labels shouldn't be dragged */
  if (QLabel *label = qobject_cast<QLabel*>(widget))
  {
    if (label->textInteractionFlags().testFlag (Qt::TextSelectableByMouse))
      return false;
  }

  if (widget->focusPolicy() > Qt::TabFocus
      || (widget->focusProxy() && widget->focusProxy()->focusPolicy() > Qt::TabFocus))
  { // like an ordinary mouse press
    widget->setFocus (Qt::MouseFocusReason);
  }

  if (draggedBtn)
  { // to make pressing buttons easier
    dragDistance_ *= 1.5;
    dragDelay_ *= 2;
  }
  return true;
}
/*************************/
bool WindowManager::isDraggable (QWidget *widget)
{
  if (!widget || QWidget::mouseGrabber())
    return false;

  if (qobject_cast<QAbstractButton*>(widget))
  {
    if (dragFromBtns_)
      return true;
    if (QToolButton *toolButton = qobject_cast<QToolButton*>(widget))
    {
      if (toolButton->autoRaise() && !toolButton->isEnabled())
        return true;
    }
  }

  if (widget->isWindow()
      && (qobject_cast<QMainWindow*>(widget)
          || qobject_cast<QDialog*>(widget)))
  {
    return true;
  }

  if (qobject_cast<QMenuBar*>(widget)
      || qobject_cast<QTabBar*>(widget)
      || qobject_cast<QStatusBar*>(widget)
      || qobject_cast<QToolBar*>(widget))
  {
    return true;
  }

  if (QListView *listView = qobject_cast<QListView*>(widget->parentWidget()))
  {
    if (listView->viewport() == widget && !isBlackListed (listView))
      return true;
  }
  else if (QTreeView *treeView = qobject_cast<QTreeView*>(widget->parentWidget()))
  {
    if (treeView->viewport() == widget && !isBlackListed (treeView))
      return true;
  }
  /*else if (QGraphicsView *graphicsView = qobject_cast<QGraphicsView*>(widget->parentWidget()))
  {
    if (graphicsView->viewport() == widget && !isBlackListed (graphicsView))
      return true;
  }*/

  return false;
}
/*************************/
void WindowManager::resetDrag()
{
  if (winTarget_)
    winTarget_.data()->unsetCursor();
  winTarget_.clear();
  widgetTarget_.clear();
  pressedWidget_.clear();
  if (dragTimer_.isActive())
    dragTimer_.stop();
  widgetDragPoint_ = QPoint();
  globalDragPoint_ = QPoint();
  dragAboutToStart_ = false;
}
/*************************/
bool WindowManager::AppEventFilter::eventFilter (QObject *object, QEvent *event)
{
  if (event->type() == QEvent::MouseButtonPress && !parent_->isLocked())
  {
    QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
    if (object == parent_->pressedWidget_.data())
    { // no double press; the widget was pressed in WindowManager::mousePressEvent()
      parent_->pressedWidget_.clear();
      if (mouseEvent->modifiers() == Qt::NoModifier && mouseEvent->button() == Qt::LeftButton)
        return true;
    }
    else if (parent_->dragAboutToStart_)
    { // find the last pressed widget in WindowManager::mousePressEvent()
      if (QWidget *widget = qobject_cast<QWidget*>(object))
      {
        if (mouseEvent->modifiers() == Qt::NoModifier && mouseEvent->button() == Qt::LeftButton)
          parent_->lastPressedWidget_ = widget;
      }
    }
    return false;
  }

  /* If a drag is in progress, no event will be received. Therefore,
     we wait for the first mouse move or press event that is received
     by any object in the application to unclock the window. */
  if (parent_->enabled()
      && parent_->isLocked()
      && !parent_->winTarget_ // drag was started (-> WindowManager::timerEvent)
      && (event->type() == QEvent::MouseMove
          || event->type() == QEvent::MouseButtonPress))
  {
    parent_->unlock();
  }

  return false;
}

}


static unsigned hash(const char* s)
    {
        unsigned h = WTF::stringHashingStartValue;
        for (;;) {
            char c = *s++;
            if (!c) {
                h += (h << 3);
                h ^= (h >> 11);
                h += (h << 15);
                return h;
            }
            h += toASCIILower(c);
            h += (h << 10);
            h ^= (h >> 6);
        }
    }

/*
 *
 */

#include "config.h"
#include "TextEncodingRegistry.h"

#include "TextCodecICU.h"
#include "TextCodecLatin1.h"
#include "TextCodecReplacement.h"
#include "TextCodecUTF16.h"
#include "TextCodecUTF8.h"
#include "TextCodecUserDefined.h"
#include "TextEncoding.h"
#include <mutex>
#include <wtf/ASCIICType.h>
#include <wtf/CheckedArithmetic.h>
#include <wtf/HashMap.h>
#include <wtf/HashSet.h>
#include <wtf/Lock.h>
#include <wtf/MainThread.h>
#include <wtf/StdLibExtras.h>
#include <wtf/text/CString.h>

#if USE(JAVA_UNICODE)
#include "java/TextCodecJava.h"
#endif

namespace WebCore {

const size_t maxEncodingNameLength = 63;

// Hash for all-ASCII strings that does case folding.
struct TextEncodingNameHash {
    static bool equal(const char* s1, const char* s2)
    {
        char c1;
        char c2;
        do {
            c1 = *s1++;
            c2 = *s2++;
            if (toASCIILower(c1) != toASCIILower(c2))
                return false;
        } while (c1 && c2);
        return !c1 && !c2;
    }

    // This algorithm is the one-at-a-time hash from:

    static const bool safeToCompareToEmptyOrDeleted = false;
};

using TextEncodingNameMap = HashMap<const char*, const char*, TextEncodingNameHash>;
using TextCodecMap = HashMap<const char*, NewTextCodecFunction>;

static Lock encodingRegistryMutex;

static TextEncodingNameMap* textEncodingNameMap;
static TextCodecMap* textCodecMap;
static bool didExtendTextCodecMaps;
static HashSet<const char*>* japaneseEncodings;
static HashSet<const char*>* nonBackslashEncodings;

static const char* const textEncodingNameBlacklist[] = { "UTF-7", "BOCU-1", "SCSU" };

static bool isUndesiredAlias(const char* alias)
{
    // Reject aliases with version numbers that are supported by some back-ends (such as "ISO_2022,locale=ja,version=0" in ICU).
    for (const char* p = alias; *p; ++p) {
        if (*p == ',')
            return true;
    }
    // 8859_1 is known to (at least) ICU, but other browsers don't support this name - and having it caused a compatibility
    if (0 == strcmp(alias, "8859_1"))
        return true;
    return false;
}

static void addToTextEncodingNameMap(const char* alias, const char* name)
{
    ASSERT(strlen(alias) <= maxEncodingNameLength);
    if (isUndesiredAlias(alias))
        return;
    const char* atomicName = textEncodingNameMap->get(name);
    ASSERT(strcmp(alias, name) == 0 || atomicName);
    if (!atomicName)
        atomicName = name;

    ASSERT_WITH_MESSAGE(!textEncodingNameMap->get(alias), "Duplicate text encoding name %s for %s (previously registered as %s)", alias, atomicName, textEncodingNameMap->get(alias));

    textEncodingNameMap->add(alias, atomicName);
}

static void addToTextCodecMap(const char* name, NewTextCodecFunction&& function)
{
    const char* atomicName = textEncodingNameMap->get(name);
    ASSERT(atomicName);
    textCodecMap->add(atomicName, WTFMove(function));
}

static void pruneBlacklistedCodecs()
{
    for (auto& nameFromBlacklist : textEncodingNameBlacklist) {
        auto* atomicName = textEncodingNameMap->get(nameFromBlacklist);
        if (!atomicName)
            continue;

        Vector<const char*> names;
        for (auto& entry : *textEncodingNameMap) {
            if (entry.value == atomicName)
                names.append(entry.key);
        }

        for (auto* name : names)
            textEncodingNameMap->remove(name);

        textCodecMap->remove(atomicName);
    }
}

static void buildBaseTextCodecMaps(const std::lock_guard<Lock>&)
{
    ASSERT(!textCodecMap);
    ASSERT(!textEncodingNameMap);

    textCodecMap = new TextCodecMap;
    textEncodingNameMap = new TextEncodingNameMap;

    TextCodecLatin1::registerEncodingNames(addToTextEncodingNameMap);
    TextCodecLatin1::registerCodecs(addToTextCodecMap);

    TextCodecUTF8::registerEncodingNames(addToTextEncodingNameMap);
    TextCodecUTF8::registerCodecs(addToTextCodecMap);

    TextCodecUTF16::registerEncodingNames(addToTextEncodingNameMap);
    TextCodecUTF16::registerCodecs(addToTextCodecMap);

    TextCodecUserDefined::registerEncodingNames(addToTextEncodingNameMap);
    TextCodecUserDefined::registerCodecs(addToTextCodecMap);
}

static void addEncodingName(HashSet<const char*>* set, const char* name)
{
    // We must not use atomicCanonicalTextEncodingName() because this function is called in it.
    const char* atomicName = textEncodingNameMap->get(name);
    if (atomicName)
        set->add(atomicName);
}

static void buildQuirksSets()
{
    // and initializing the sets for them in TextEncodingRegistry.cpp look strange.

    ASSERT(!japaneseEncodings);
    ASSERT(!nonBackslashEncodings);

    japaneseEncodings = new HashSet<const char*>;
    addEncodingName(japaneseEncodings, "EUC-JP");
    addEncodingName(japaneseEncodings, "ISO-2022-JP");
    addEncodingName(japaneseEncodings, "ISO-2022-JP-1");
    addEncodingName(japaneseEncodings, "ISO-2022-JP-2");
    addEncodingName(japaneseEncodings, "ISO-2022-JP-3");
    addEncodingName(japaneseEncodings, "JIS_C6226-1978");
    addEncodingName(japaneseEncodings, "JIS_X0201");
    addEncodingName(japaneseEncodings, "JIS_X0208-1983");
    addEncodingName(japaneseEncodings, "JIS_X0208-1990");
    addEncodingName(japaneseEncodings, "JIS_X0212-1990");
    addEncodingName(japaneseEncodings, "Shift_JIS");
    addEncodingName(japaneseEncodings, "Shift_JIS_X0213-2000");
    addEncodingName(japaneseEncodings, "cp932");
    addEncodingName(japaneseEncodings, "x-mac-japanese");

    nonBackslashEncodings = new HashSet<const char*>;
    // The text encodings below treat backslash as a currency symbol for IE compatibility.
    addEncodingName(nonBackslashEncodings, "x-mac-japanese");
    addEncodingName(nonBackslashEncodings, "ISO-2022-JP");
    addEncodingName(nonBackslashEncodings, "EUC-JP");
    // Shift_JIS_X0213-2000 is not the same encoding as Shift_JIS on Mac. We need to register both of them.
    addEncodingName(nonBackslashEncodings, "Shift_JIS");
    addEncodingName(nonBackslashEncodings, "Shift_JIS_X0213-2000");
}

bool isJapaneseEncoding(const char* canonicalEncodingName)
{
    return canonicalEncodingName && japaneseEncodings && japaneseEncodings->contains(canonicalEncodingName);
}

bool shouldShowBackslashAsCurrencySymbolIn(const char* canonicalEncodingName)
{
    return canonicalEncodingName && nonBackslashEncodings && nonBackslashEncodings->contains(canonicalEncodingName);
}

static void extendTextCodecMaps()
{
    TextCodecReplacement::registerEncodingNames(addToTextEncodingNameMap);
    TextCodecReplacement::registerCodecs(addToTextCodecMap);

    TextCodecICU::registerEncodingNames(addToTextEncodingNameMap);
    TextCodecICU::registerCodecs(addToTextCodecMap);

#if USE(JAVA_UNICODE)
    TextCodecJava::registerEncodingNames(addToTextEncodingNameMap);
    TextCodecJava::registerCodecs(addToTextCodecMap);
#endif

    pruneBlacklistedCodecs();
    buildQuirksSets();
}

std::unique_ptr<TextCodec> newTextCodec(const TextEncoding& encoding)
{
    std::lock_guard<Lock> lock(encodingRegistryMutex);

    ASSERT(textCodecMap);
    auto result = textCodecMap->find(encoding.name());
    ASSERT(result != textCodecMap->end());
    return result->value();
}

const char* atomicCanonicalTextEncodingName(const char* name)
{
    if (!name || !name[0])
        return nullptr;

    std::lock_guard<Lock> lock(encodingRegistryMutex);

    if (!textEncodingNameMap)
        buildBaseTextCodecMaps(lock);

    if (const char* atomicName = textEncodingNameMap->get(name))
        return atomicName;
    if (didExtendTextCodecMaps)
        return nullptr;

    extendTextCodecMaps();
    didExtendTextCodecMaps = true;
    return textEncodingNameMap->get(name);
}

template<typename CharacterType> static const char* atomicCanonicalTextEncodingName(const CharacterType* characters, size_t length)
{
    char buffer[maxEncodingNameLength + 1];
    size_t j = 0;
    for (size_t i = 0; i < length; ++i) {
        if (j == maxEncodingNameLength)
            return nullptr;
        buffer[j++] = characters[i];
    }
    buffer[j] = 0;
    return atomicCanonicalTextEncodingName(buffer);
}

const char* atomicCanonicalTextEncodingName(const String& alias)
{
    if (alias.isEmpty() || !alias.isAllASCII())
        return nullptr;

    if (alias.is8Bit())
        return atomicCanonicalTextEncodingName(alias.characters8(), alias.length());

    return atomicCanonicalTextEncodingName(alias.characters16(), alias.length());
}

bool noExtendedTextEncodingNameUsed()
{
    // If the calling thread did not use extended encoding names, it is fine for it to use a stale false value.
    return !didExtendTextCodecMaps;
}

String defaultTextEncodingNameForSystemLanguage()
{
#if PLATFORM(COCOA)
    String systemEncodingName = CFStringConvertEncodingToIANACharSetName(webDefaultCFStringEncoding());

    // ICU uses this name for a different encoding, so we need to change the name to a value that actually gives us windows-949.
    // In addition, this value must match what is used in Safari, see <rdar://problem/5579292>.
    // On some OS versions, the result is CP949 (uppercase).
    if (equalLettersIgnoringASCIICase(systemEncodingName, "cp949"))
        systemEncodingName = "ks_c_5601-1987"_s;

    // Since "cp874" alias is not standard (http://example.com), map to
    if (equalLettersIgnoringASCIICase(systemEncodingName, "cp874"))
        systemEncodingName = "dos-874"_s;

    return systemEncodingName;
#else
    return "ISO-8859-1"_s;
#endif
}

} // namespace WebCore


/*
Bullet Continuous Collision Detection and Physics Library

*/

#include "btCollisionDispatcher.h"

#include "BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h"

#include "BulletCollision/CollisionShapes/btCollisionShape.h"
#include "BulletCollision/CollisionDispatch/btCollisionObject.h"
#include "BulletCollision/BroadphaseCollision/btOverlappingPairCache.h"
#include "LinearMath/btPoolAllocator.h"
#include "BulletCollision/CollisionDispatch/btCollisionConfiguration.h"

int gNumManifold = 0;

#ifdef BT_DEBUG
#include <stdio.h>
#endif

btCollisionDispatcher::btCollisionDispatcher (btCollisionConfiguration* collisionConfiguration):
m_dispatcherFlags(btCollisionDispatcher::CD_USE_RELATIVE_CONTACT_BREAKING_THRESHOLD),
	m_collisionConfiguration(collisionConfiguration)
{
	int i;

	setNearCallback(defaultNearCallback);

	m_collisionAlgorithmPoolAllocator = collisionConfiguration->getCollisionAlgorithmPool();

	m_persistentManifoldPoolAllocator = collisionConfiguration->getPersistentManifoldPool();

	for (i=0;i<MAX_BROADPHASE_COLLISION_TYPES;i++)
	{
		for (int j=0;j<MAX_BROADPHASE_COLLISION_TYPES;j++)
		{
			m_doubleDispatch[i][j] = m_collisionConfiguration->getCollisionAlgorithmCreateFunc(i,j);
			btAssert(m_doubleDispatch[i][j]);
		}
	}

}

void btCollisionDispatcher::registerCollisionCreateFunc(int proxyType0, int proxyType1, btCollisionAlgorithmCreateFunc *createFunc)
{
	m_doubleDispatch[proxyType0][proxyType1] = createFunc;
}

btCollisionDispatcher::~btCollisionDispatcher()
{
}

btPersistentManifold*	btCollisionDispatcher::getNewManifold(void* b0,void* b1)
{
	gNumManifold++;

	btCollisionObject* body0 = (btCollisionObject*)b0;
	btCollisionObject* body1 = (btCollisionObject*)b1;

	//optional relative contact breaking threshold, turned on by default (use setDispatcherFlags to switch off feature for improved performance)

	btScalar contactBreakingThreshold =  (m_dispatcherFlags & btCollisionDispatcher::CD_USE_RELATIVE_CONTACT_BREAKING_THRESHOLD) ?
		btMin(body0->getCollisionShape()->getContactBreakingThreshold(gContactBreakingThreshold) , body1->getCollisionShape()->getContactBreakingThreshold(gContactBreakingThreshold))
		: gContactBreakingThreshold ;

	btScalar contactProcessingThreshold = btMin(body0->getContactProcessingThreshold(),body1->getContactProcessingThreshold());

	void* mem = 0;

	if (m_persistentManifoldPoolAllocator->getFreeCount())
	{
		mem = m_persistentManifoldPoolAllocator->allocate(sizeof(btPersistentManifold));
	} else
	{
		mem = btAlignedAlloc(sizeof(btPersistentManifold),16);

	}
	btPersistentManifold* manifold = new(mem) btPersistentManifold (body0,body1,0,contactBreakingThreshold,contactProcessingThreshold);
	manifold->m_index1a = m_manifoldsPtr.size();
	m_manifoldsPtr.push_back(manifold);

	return manifold;
}

void btCollisionDispatcher::clearManifold(btPersistentManifold* manifold)
{
	manifold->clearManifold();
}

void btCollisionDispatcher::releaseManifold(btPersistentManifold* manifold)
{

	gNumManifold--;

	clearManifold(manifold);

	int findIndex = manifold->m_index1a;
	btAssert(findIndex < m_manifoldsPtr.size());
	m_manifoldsPtr.swap(findIndex,m_manifoldsPtr.size()-1);
	m_manifoldsPtr[findIndex]->m_index1a = findIndex;
	m_manifoldsPtr.pop_back();

	manifold->~btPersistentManifold();
	if (m_persistentManifoldPoolAllocator->validPtr(manifold))
	{
		m_persistentManifoldPoolAllocator->freeMemory(manifold);
	} else
	{
		btAlignedFree(manifold);
	}

}

btCollisionAlgorithm* btCollisionDispatcher::findAlgorithm(btCollisionObject* body0,btCollisionObject* body1,btPersistentManifold* sharedManifold)
{

	btCollisionAlgorithmConstructionInfo ci;

	ci.m_dispatcher1 = this;
	ci.m_manifold = sharedManifold;
	btCollisionAlgorithm* algo = m_doubleDispatch[body0->getCollisionShape()->getShapeType()][body1->getCollisionShape()->getShapeType()]->CreateCollisionAlgorithm(ci,body0,body1);

	return algo;
}

bool	btCollisionDispatcher::needsResponse(btCollisionObject* body0,btCollisionObject* body1)
{
	//here you can do filtering
	bool hasResponse =
		(body0->hasContactResponse() && body1->hasContactResponse());
	//no response between two static/kinematic bodies:
	hasResponse = hasResponse &&
		((!body0->isStaticOrKinematicObject()) ||(! body1->isStaticOrKinematicObject()));
	return hasResponse;
}

bool	btCollisionDispatcher::needsCollision(btCollisionObject* body0,btCollisionObject* body1)
{
	btAssert(body0);
	btAssert(body1);

	bool needsCollision = true;

#ifdef BT_DEBUG
	if (!(m_dispatcherFlags & btCollisionDispatcher::CD_STATIC_STATIC_REPORTED))
	{
		//broadphase filtering already deals with this
		if ((body0->isStaticObject() || body0->isKinematicObject()) &&
			(body1->isStaticObject() || body1->isKinematicObject()))
		{
			m_dispatcherFlags |= btCollisionDispatcher::CD_STATIC_STATIC_REPORTED;
			printf("warning btCollisionDispatcher::needsCollision: static-static collision!\n");
		}
	}
#endif //BT_DEBUG

	if ((!body0->isActive()) && (!body1->isActive()))
		needsCollision = false;
	else if (!body0->checkCollideWith(body1))
		needsCollision = false;

	return needsCollision ;

}

///interface for iterating all overlapping collision pairs, no matter how those pairs are stored (array, set, map etc)
///this is useful for the collision dispatcher.
class btCollisionPairCallback : public btOverlapCallback
{
	const btDispatcherInfo& m_dispatchInfo;
	btCollisionDispatcher*	m_dispatcher;

public:

	btCollisionPairCallback(const btDispatcherInfo& dispatchInfo,btCollisionDispatcher*	dispatcher)
	:m_dispatchInfo(dispatchInfo),
	m_dispatcher(dispatcher)
	{
	}

	/*btCollisionPairCallback& operator=(btCollisionPairCallback& other)
	{
		m_dispatchInfo = other.m_dispatchInfo;
		m_dispatcher = other.m_dispatcher;
		return *this;
	}
	*/

	virtual bool	processOverlap(btBroadphasePair& pair)
	{
		(*m_dispatcher->getNearCallback())(pair,*m_dispatcher,m_dispatchInfo);

		return false;
	}
};

void	btCollisionDispatcher::dispatchAllCollisionPairs(btOverlappingPairCache* pairCache,const btDispatcherInfo& dispatchInfo,btDispatcher* dispatcher)
{

	btCollisionPairCallback	collisionCallback(dispatchInfo,this);

	pairCache->processAllOverlappingPairs(&collisionCallback,dispatcher);

}

//by default, Bullet will use this near callback
void btCollisionDispatcher::defaultNearCallback(btBroadphasePair& collisionPair, btCollisionDispatcher& dispatcher, const btDispatcherInfo& dispatchInfo)
{
		btCollisionObject* colObj0 = (btCollisionObject*)collisionPair.m_pProxy0->m_clientObject;
		btCollisionObject* colObj1 = (btCollisionObject*)collisionPair.m_pProxy1->m_clientObject;

		if (dispatcher.needsCollision(colObj0,colObj1))
		{
			//dispatcher will keep algorithms persistent in the collision pair
			if (!collisionPair.m_algorithm)
			{
				collisionPair.m_algorithm = dispatcher.findAlgorithm(colObj0,colObj1);
			}

			if (collisionPair.m_algorithm)
			{
				btManifoldResult contactPointResult(colObj0,colObj1);

				if (dispatchInfo.m_dispatchFunc == 		btDispatcherInfo::DISPATCH_DISCRETE)
				{
					//discrete collision detection query
					collisionPair.m_algorithm->processCollision(colObj0,colObj1,dispatchInfo,&contactPointResult);
				} else
				{
					//continuous collision detection query, time of impact (toi)
					btScalar toi = collisionPair.m_algorithm->calculateTimeOfImpact(colObj0,colObj1,dispatchInfo,&contactPointResult);
					if (dispatchInfo.m_timeOfImpact > toi)
						dispatchInfo.m_timeOfImpact = toi;

				}
			}
		}

}

void* btCollisionDispatcher::allocateCollisionAlgorithm(int size)
{
	if (m_collisionAlgorithmPoolAllocator->getFreeCount())
	{
		return m_collisionAlgorithmPoolAllocator->allocate(size);
	}

	//warn user for overflow?
	return	btAlignedAlloc(static_cast<size_t>(size), 16);
}

void btCollisionDispatcher::freeCollisionAlgorithm(void* ptr)
{
	if (m_collisionAlgorithmPoolAllocator->validPtr(ptr))
	{
		m_collisionAlgorithmPoolAllocator->freeMemory(ptr);
	} else
	{
		btAlignedFree(ptr);
	}
}


OGRErr VFKDataBlockSQLite::CleanProperties()
{
    if ( m_hStmt ) {
        if ( sqlite3_finalize(m_hStmt) != SQLITE_OK ) {
            m_hStmt = nullptr;
            return OGRERR_FAILURE;
        }
        m_hStmt = nullptr;
    }

    return OGRERR_NONE;
}

/******************************************************************************
 *
 * Project:  VFK Reader - Data block definition (SQLite)
 * Purpose:  Implements VFKDataBlockSQLite
 * Author:   Martin Landa, landa.martin gmail.com
 *
 ******************************************************************************
 *
 ****************************************************************************/

#include <algorithm>

#include "vfkreader.h"
#include "vfkreaderp.h"

#include "cpl_conv.h"
#include "cpl_error.h"

CPL_CVSID("$Id: vfkdatablocksqlite.cpp d14708b4634f593eb74ad298a5f9604588a7a8d2 2019-03-11 11:55:09 +0100 Martin Landa $")

/*!
  \brief VFKDataBlockSQLite constructor
*/
VFKDataBlockSQLite::VFKDataBlockSQLite(const char *pszName, const IVFKReader *poReader) :
   IVFKDataBlock(pszName, poReader),
   m_hStmt(nullptr)
{
}

/*!
  \brief Load geometry (point layers)

  \return number of invalid features
*/
int VFKDataBlockSQLite::LoadGeometryPoint()
{
    if (LoadGeometryFromDB()) /* try to load geometry from DB */
        return 0;

    const bool bSkipInvalid =
        EQUAL(m_pszName, "OB") || EQUAL(m_pszName, "OP") ||
        EQUAL(m_pszName, "OBBP");

    CPLString osSQL;
    osSQL.Printf("SELECT SOURADNICE_Y,SOURADNICE_X,%s,rowid FROM %s",
                 FID_COLUMN, m_pszName);

    VFKReaderSQLite *poReader = (VFKReaderSQLite*) m_poReader;
    sqlite3_stmt *hStmt = poReader->PrepareStatement(osSQL.c_str());

    if (poReader->IsSpatial())
        poReader->ExecuteSQL("BEGIN");

    int nGeometries = 0;
    int nInvalid = 0;
    while(poReader->ExecuteSQL(hStmt) == OGRERR_NONE) {
        /* read values */
        const double x = -1.0 * sqlite3_column_double(hStmt, 0); /* S-JTSK coordinate system expected */
        const double y = -1.0 * sqlite3_column_double(hStmt, 1);
        const GIntBig iFID = sqlite3_column_int64(hStmt, 2);
        const int rowId = sqlite3_column_int(hStmt, 3);

        VFKFeatureSQLite *poFeature = dynamic_cast<VFKFeatureSQLite *>(
            GetFeatureByIndex(rowId - 1));
        if( poFeature == nullptr || poFeature->GetFID() != iFID )
        {
            continue;
        }

        /* create geometry */
        OGRPoint pt(x, y);
        if (!poFeature->SetGeometry(&pt)) {
            nInvalid++;
            continue;
        }

        /* store also geometry in DB */
        if (poReader->IsSpatial() &&
            SaveGeometryToDB(&pt, rowId) != OGRERR_FAILURE)
            nGeometries++;
    }

    /* update number of geometries in VFK_DB_TABLE table */
    UpdateVfkBlocks(nGeometries);

    if (poReader->IsSpatial())
        poReader->ExecuteSQL("COMMIT");

    return bSkipInvalid ? 0 : nInvalid;
}

/*!
  \brief Set geometry for linestrings

  \param poLine VFK feature
  \param oOGRLine line geometry
  \param[in,out] bValid true when feature's geometry is valid
  \param ftype geometry VFK type
  \param[in,out] rowIdFeat list of row ids which forms linestring
  \param[in,out] nGeometries number of features with valid geometry
*/
bool VFKDataBlockSQLite::SetGeometryLineString(VFKFeatureSQLite *poLine, OGRLineString *oOGRLine,
                                               bool& bValid, const char *ftype,
                                               std::vector<int>& rowIdFeat, int& nGeometries)
{
    VFKReaderSQLite *poReader = (VFKReaderSQLite*) m_poReader;

    oOGRLine->setCoordinateDimension(2); /* force 2D */

    /* check also VFK validity */
    if( bValid )
    {
        /* Feature types

           - '3'    - line       (2 points)
           - '4'    - linestring (at least 2 points)
           - '11'   - curve      (at least 2 points)
           - '15'   - circle     (3 points)
           - '15 r' - circle     (center point & radius)
           - '16'   - arc        (3 points)
        */

        const int npoints = oOGRLine->getNumPoints();
        if (EQUAL(ftype, "3") && npoints > 2) {
            /* be less pedantic, just inform user about data
             * inconsistency

               bValid = false;
            */
            CPLDebug("OGR-VFK",
                     "Line (fid=" CPL_FRMT_GIB ") defined by more than two vertices",
                     poLine->GetFID());
        }
        else if (EQUAL(ftype, "11") && npoints < 2) {
            bValid = false;
            CPLError(CE_Warning, CPLE_AppDefined,
                     "Curve (fid=" CPL_FRMT_GIB ") defined by less than two vertices",
                     poLine->GetFID());
        }
        else if (EQUAL(ftype, "15") && npoints != 3) {
            bValid = false;
            CPLError(CE_Warning, CPLE_AppDefined,
                     "Circle (fid=" CPL_FRMT_GIB ") defined by invalid number of vertices (%d)",
                     poLine->GetFID(), oOGRLine->getNumPoints());
        }
        else if (strlen(ftype) > 2 && STARTS_WITH_CI(ftype, "15") && npoints != 1) {
            bValid = false;
            CPLError(CE_Warning, CPLE_AppDefined,
                     "Circle (fid=" CPL_FRMT_GIB ") defined by invalid number of vertices (%d)",
                     poLine->GetFID(), oOGRLine->getNumPoints());
        }
        else if (EQUAL(ftype, "16") && npoints != 3) {
            bValid = false;
            CPLError(CE_Warning, CPLE_AppDefined,
                     "Arc (fid=" CPL_FRMT_GIB ") defined by invalid number of vertices (%d)",
                     poLine->GetFID(), oOGRLine->getNumPoints());
        }
    }

    /* set geometry (NULL for invalid features) */
    if( bValid )
    {
        if (!poLine->SetGeometry(oOGRLine, ftype)) {
            bValid = false;
        }
    }
    else
    {
        poLine->SetGeometry(nullptr);
    }

    /* update fid column */
    UpdateFID(poLine->GetFID(), rowIdFeat);

    /* store also geometry in DB */
    CPLAssert( !rowIdFeat.empty() );
    if( bValid && poReader->IsSpatial() &&
        SaveGeometryToDB(bValid ? poLine->GetGeometry() : nullptr,
                         rowIdFeat[0]) != OGRERR_FAILURE )
    {
        nGeometries++;
    }

    rowIdFeat.clear();
    oOGRLine->empty(); /* restore line */

    return bValid;
}

/*!
  \brief Load geometry (linestring SBP layer)

  \return number of invalid features
*/
int VFKDataBlockSQLite::LoadGeometryLineStringSBP()
{
    int nInvalid = 0;

    VFKDataBlockSQLite *poDataBlockPoints =
        (VFKDataBlockSQLite *) m_poReader->GetDataBlock("SOBR");
    if (nullptr == poDataBlockPoints) {
        CPLError(CE_Failure, CPLE_FileIO,
                 "Data block %s not found.\n", m_pszName);
        return nInvalid;
    }

    int nGeometries = 0;
    VFKReaderSQLite *poReader = (VFKReaderSQLite*) m_poReader;

    poDataBlockPoints->LoadGeometry();

    if (LoadGeometryFromDB()) /* try to load geometry from DB */
        return 0;

    CPLString osSQL;
    osSQL.Printf("UPDATE %s SET %s = -1", m_pszName, FID_COLUMN);
    poReader->ExecuteSQL(osSQL.c_str());
    bool bValid = true;
    int iIdx = 0;

    VFKFeatureSQLite *poLine = nullptr;

    for( int i = 0; i < 2; i++ )
    {
        /* first collect linestrings related to HP, OB, DPM and ZVB
           then collect rest of linestrings */
        if( i == 0 )
            osSQL.Printf("SELECT BP_ID,PORADOVE_CISLO_BODU,PARAMETRY_SPOJENI,_rowid_ FROM '%s' WHERE "
                         "HP_ID IS NOT NULL OR OB_ID IS NOT NULL OR DPM_ID IS NOT NULL OR ZVB_ID IS NOT NULL "
                         "ORDER BY HP_ID,OB_ID,DPM_ID,ZVB_ID,PORADOVE_CISLO_BODU", m_pszName);
        else
            osSQL.Printf("SELECT BP_ID,PORADOVE_CISLO_BODU,PARAMETRY_SPOJENI,_rowid_ FROM '%s' WHERE "
                         "OB_ID IS NULL AND HP_ID IS NULL AND DPM_ID IS NULL AND ZVB_ID IS NULL "
                         "ORDER BY ID,PORADOVE_CISLO_BODU", m_pszName);

        sqlite3_stmt *hStmt = poReader->PrepareStatement(osSQL.c_str());

        if (poReader->IsSpatial())
            poReader->ExecuteSQL("BEGIN");

        std::vector<int> rowIdFeat;
        CPLString osFType;
        OGRLineString oOGRLine;

        while(poReader->ExecuteSQL(hStmt) == OGRERR_NONE) {
            // read values
            const GUIntBig id = sqlite3_column_int64(hStmt, 0);
            const GUIntBig ipcb  = sqlite3_column_int64(hStmt, 1);
            const char* pszFType = reinterpret_cast<const char*>(
                sqlite3_column_text(hStmt, 2));
            int rowId = sqlite3_column_int(hStmt, 3);

            if (ipcb == 1) {
                VFKFeatureSQLite *poFeature =
                    (VFKFeatureSQLite *) GetFeatureByIndex(iIdx);
                if( poFeature == nullptr )
                {
                    CPLError(CE_Failure, CPLE_AppDefined,
                             "Cannot retrieve feature %d", iIdx);
                    sqlite3_finalize(hStmt);
                    break;
                }
                poFeature->SetRowId(rowId);

                /* set geometry & reset */
                if( poLine &&
                    !SetGeometryLineString(
                        poLine, &oOGRLine,
                        bValid, osFType.c_str(), rowIdFeat, nGeometries) )
                {
                    nInvalid++;
                }

                bValid = true;
                poLine = poFeature;
                osFType = pszFType ? pszFType : "";
                iIdx++;
            }

            VFKFeatureSQLite *poPoint =
                (VFKFeatureSQLite *) poDataBlockPoints->GetFeature("ID", id);
            if( poPoint )
            {
                OGRGeometry *pt = poPoint->GetGeometry();
                if (pt) {
                    oOGRLine.addPoint(pt->toPoint());
                }
                else
                {
                    CPLDebug("OGR-VFK",
                             "Geometry (point ID = " CPL_FRMT_GUIB ") not valid", id);
                    bValid = false;
                }
            }
            else
            {
                CPLDebug("OGR-VFK",
                         "Point ID = " CPL_FRMT_GUIB " not found (rowid = %d)",
                         id, rowId);
                bValid = false;
            }

            /* add vertex to the linestring */
            rowIdFeat.push_back(rowId);
        }

        /* add last line */
        if( poLine &&
            !SetGeometryLineString(
                poLine, &oOGRLine,
                bValid, osFType.c_str(), rowIdFeat, nGeometries) )
        {
            nInvalid++;
        }
        poLine = nullptr;

        if (poReader->IsSpatial())
            poReader->ExecuteSQL("COMMIT");
    }

    /* update number of geometries in VFK_DB_TABLE table */
    UpdateVfkBlocks(nGeometries);

    return nInvalid;
}

/*!
  \brief Load geometry (linestring HP/DPM/ZVB layer)

  \return number of invalid features
*/
int VFKDataBlockSQLite::LoadGeometryLineStringHP()
{
    int nInvalid = 0;
    VFKReaderSQLite *poReader = (VFKReaderSQLite*) m_poReader;

    VFKDataBlockSQLite *poDataBlockLines =
        (VFKDataBlockSQLite *) m_poReader->GetDataBlock("SBP");
    if (nullptr == poDataBlockLines) {
        CPLError(CE_Failure, CPLE_FileIO,
                 "Data block %s not found.", m_pszName);
        return nInvalid;
    }

    poDataBlockLines->LoadGeometry();

    if (LoadGeometryFromDB()) /* try to load geometry from DB */
        return 0;

    CPLString osColumn;
    osColumn.Printf("%s_ID", m_pszName);
    const char *vrColumn[2] = {
        osColumn.c_str(),
        "PORADOVE_CISLO_BODU"
    };

    GUIntBig vrValue[2] = { 0, 1 }; // Reduce to first segment.

    CPLString osSQL;
    osSQL.Printf("SELECT ID,%s,rowid FROM %s", FID_COLUMN, m_pszName);
    /* TODO: handle points in DPM */
    if (EQUAL(m_pszName, "DPM"))
        osSQL += " WHERE SOURADNICE_X IS NULL";
    sqlite3_stmt *hStmt = poReader->PrepareStatement(osSQL.c_str());

    if (poReader->IsSpatial())
        poReader->ExecuteSQL("BEGIN");

    int nGeometries = 0;

    while( poReader->ExecuteSQL(hStmt) == OGRERR_NONE )
    {
        /* read values */
        vrValue[0] = sqlite3_column_int64(hStmt, 0);
        const GIntBig iFID = sqlite3_column_int64(hStmt, 1);
        const int rowId = sqlite3_column_int(hStmt, 2);

        VFKFeatureSQLite *poFeature =
            (VFKFeatureSQLite *) GetFeatureByIndex(rowId - 1);
        if( poFeature == nullptr || poFeature->GetFID() != iFID )
        {
            continue;
        }

        VFKFeatureSQLite *poLine =
            poDataBlockLines->GetFeature(vrColumn, vrValue, 2, TRUE);

        OGRGeometry *poOgrGeometry = nullptr;
        if( !poLine )
        {
            poOgrGeometry = nullptr;
        }
        else
        {
            poOgrGeometry = poLine->GetGeometry();
        }
        if (!poOgrGeometry || !poFeature->SetGeometry(poOgrGeometry)) {
            CPLDebug("OGR-VFK", "VFKDataBlockSQLite::LoadGeometryLineStringHP(): name=%s fid=" CPL_FRMT_GIB " "
                     "id=" CPL_FRMT_GUIB " -> %s geometry", m_pszName, iFID, vrValue[0],
                     poOgrGeometry ? "invalid" : "empty");
            nInvalid++;
            continue;
        }

        /* store also geometry in DB */
        if (poReader->IsSpatial() &&
            SaveGeometryToDB(poOgrGeometry, rowId) != OGRERR_FAILURE &&
            poOgrGeometry)
            nGeometries++;
    }

    /* update number of geometries in VFK_DB_TABLE table */
    UpdateVfkBlocks(nGeometries);

    if (poReader->IsSpatial())
        poReader->ExecuteSQL("COMMIT");

    return nInvalid;
}

/*!
  \brief Load geometry (polygon BUD/PAR layers)

  \return number of invalid features
*/
int VFKDataBlockSQLite::LoadGeometryPolygon()
{
    VFKReaderSQLite *poReader = (VFKReaderSQLite*) m_poReader;

    VFKDataBlockSQLite *poDataBlockLines1 = nullptr;
    VFKDataBlockSQLite *poDataBlockLines2 = nullptr;
    bool bIsPar = false;
    if (EQUAL (m_pszName, "PAR")) {
        poDataBlockLines1 = (VFKDataBlockSQLite *) m_poReader->GetDataBlock("HP");
        poDataBlockLines2 = poDataBlockLines1;
        bIsPar = true;
    }
    else {
        poDataBlockLines1 = (VFKDataBlockSQLite *) m_poReader->GetDataBlock("OB");
        poDataBlockLines2 = (VFKDataBlockSQLite *) m_poReader->GetDataBlock("SBP");
        bIsPar = false;
    }
    if( nullptr == poDataBlockLines1 )
    {
        CPLError(CE_Warning, CPLE_FileIO,
                 "Data block %s not found. Unable to build geometry for %s.",
                 bIsPar ? "HP" : "OB", m_pszName);
        return -1;
    }
    if( nullptr == poDataBlockLines2 )
    {
        CPLError(CE_Warning, CPLE_FileIO,
                 "Data block %s not found. Unable to build geometry for %s.",
                 "SBP", m_pszName);
        return -1;
    }

    poDataBlockLines1->LoadGeometry();
    poDataBlockLines2->LoadGeometry();

    if( LoadGeometryFromDB() )  // Try to load geometry from DB.
        return 0;

    const char *vrColumn[2] = { nullptr, nullptr };
    GUIntBig vrValue[2] = { 0, 0 };
    if (bIsPar) {
        vrColumn[0] = "PAR_ID_1";
        vrColumn[1] = "PAR_ID_2";
    }
    else {
        vrColumn[0] = "OB_ID";
        vrColumn[1] = "PORADOVE_CISLO_BODU";
        vrValue[1]  = 1;
    }

    CPLString osSQL;
    osSQL.Printf("SELECT ID,%s,rowid FROM %s", FID_COLUMN, m_pszName);
    sqlite3_stmt *hStmt = poReader->PrepareStatement(osSQL.c_str());

    if (poReader->IsSpatial())
        poReader->ExecuteSQL("BEGIN");

    VFKFeatureSQLiteList poLineList;
    /* first is to be considered as exterior */
    PointListArray poRingList;
    std::vector<OGRLinearRing *> poLinearRingList;
    OGRPolygon ogrPolygon;
    int nInvalidNoLines = 0;
    int nInvalidNoRings = 0;
    int nGeometries = 0;

    while(poReader->ExecuteSQL(hStmt) == OGRERR_NONE) {
        /* read values */
        const GUIntBig id = sqlite3_column_int64(hStmt, 0);
        const long iFID = static_cast<long>(sqlite3_column_int64(hStmt, 1));
        const int rowId = sqlite3_column_int(hStmt, 2);

        VFKFeatureSQLite *poFeature =
            (VFKFeatureSQLite *) GetFeatureByIndex(rowId - 1);
        if( poFeature == nullptr || poFeature->GetFID() != iFID )
        {
            continue;
        }

        if( bIsPar )
        {
            vrValue[0] = vrValue[1] = id;
            poLineList = poDataBlockLines1->GetFeatures(vrColumn, vrValue, 2);
        }
        else
        {

            osSQL.Printf("SELECT ID FROM %s WHERE BUD_ID = " CPL_FRMT_GUIB,
                         poDataBlockLines1->GetName(), id);
            if (poReader->IsSpatial()) {
                CPLString osColumn;

                osColumn.Printf(" AND %s IS NULL", GEOM_COLUMN);
                osSQL += osColumn;
            }
            sqlite3_stmt *hStmtOb = poReader->PrepareStatement(osSQL.c_str());

            while(poReader->ExecuteSQL(hStmtOb) == OGRERR_NONE) {
                const GUIntBig idOb = sqlite3_column_int64(hStmtOb, 0);
                vrValue[0] = idOb;
                VFKFeatureSQLite *poLineSbp =
                    poDataBlockLines2->GetFeature(vrColumn, vrValue, 2);
                if (poLineSbp)
                    poLineList.push_back(poLineSbp);
            }
        }
        size_t nLines = poLineList.size();
        if (nLines < 1) {
            CPLDebug("OGR-VFK",
                     "%s: unable to collect rings for polygon fid = %ld (no lines)",
                     m_pszName, iFID);
            nInvalidNoLines++;
            continue;
        }

        /* clear */
        ogrPolygon.empty();

        /* free ring list */
        for (PointListArray::iterator iRing = poRingList.begin(), eRing = poRingList.end();
            iRing != eRing; ++iRing) {
            delete (*iRing);
            *iRing = nullptr;
        }
        poRingList.clear();

        /* collect rings from lines */
        bool bFound = false;
        int nCount = 0;
        const int nCountMax = static_cast<int>(nLines) * 2;
        while( !poLineList.empty() && nCount < nCountMax )
        {
            bool bNewRing = !bFound;
            bFound = false;
            int i = 1;
            for (VFKFeatureSQLiteList::iterator iHp = poLineList.begin(), eHp = poLineList.end();
                 iHp != eHp; ++iHp, ++i) {
                auto pGeom = (*iHp)->GetGeometry();
                if (pGeom && AppendLineToRing(&poRingList, pGeom->toLineString(), bNewRing)) {
                    bFound = true;
                    poLineList.erase(iHp);
                    break;
                }
            }
            nCount++;
        }
        CPLDebug("OGR-VFK", "%s: fid = %ld nlines = %d -> nrings = %d", m_pszName,
                 iFID, (int)nLines, (int)poRingList.size());

        if (!poLineList.empty()) {
            CPLDebug("OGR-VFK",
                     "%s: unable to collect rings for polygon fid = %ld",
                     m_pszName, iFID);
            nInvalidNoRings++;
            continue;
        }

        /* build rings */
        poLinearRingList.clear();
        OGRLinearRing *poOgrRing = nullptr;
        int i = 1;
        for( PointListArray::const_iterator iRing = poRingList.begin(),
                 eRing = poRingList.end();
             iRing != eRing;
             ++iRing)
        {
            PointList *poList = *iRing;

            poLinearRingList.push_back(new OGRLinearRing());
            poOgrRing = poLinearRingList.back();
            CPLAssert(nullptr != poOgrRing);

            for( PointList::iterator iPoint = poList->begin(),
                     ePoint = poList->end();
                 iPoint != ePoint;
                 ++iPoint)
            {
                OGRPoint *poPoint = &(*iPoint);
                poOgrRing->addPoint(poPoint);
            }
            i++;
        }

        /* find exterior ring */
        if( poLinearRingList.size() > 1 )
        {
            std::vector<OGRLinearRing *>::iterator exteriorRing;

            exteriorRing = poLinearRingList.begin();
            double dMaxArea = -1.0;
            for( std::vector<OGRLinearRing *>::iterator iRing =
                     poLinearRingList.begin(),
                     eRing = poLinearRingList.end();
                 iRing != eRing;
                 ++iRing )
            {
                poOgrRing = *iRing;
                if (!IsRingClosed(poOgrRing))
                    continue; /* skip unclosed rings */

                const double dArea = poOgrRing->get_Area();
                if (dArea > dMaxArea) {
                    dMaxArea = dArea;
                    exteriorRing = iRing;
                }
            }
            if (exteriorRing != poLinearRingList.begin()) {
                std::swap(*poLinearRingList.begin(), *exteriorRing);
            }
        }

        /* build polygon from rings */
        int nBridges = 0;
        for( std::vector<OGRLinearRing *>::iterator iRing =
                 poLinearRingList.begin(),
                 eRing = poLinearRingList.end();
             iRing != eRing;
             ++iRing )
        {
            poOgrRing = *iRing;

            /* check if ring is closed */
            if (IsRingClosed(poOgrRing)) {
                ogrPolygon.addRing(poOgrRing);
            }
            else {
                if (poOgrRing->getNumPoints() == 2) {
                    CPLDebug("OGR-VFK", "%s: Polygon (fid = %ld) bridge removed",
                             m_pszName, iFID);
                    nBridges++;
                }
                else {
                    CPLDebug("OGR-VFK",
                             "%s: Polygon (fid = %ld) unclosed ring skipped",
                             m_pszName, iFID);
                }
            }
            delete poOgrRing;
            *iRing = nullptr;
        }

        /* set polygon */
        ogrPolygon.setCoordinateDimension(2); /* force 2D */
        if (ogrPolygon.getNumInteriorRings() + nBridges != (int) poLinearRingList.size() - 1 ||
            !poFeature->SetGeometry(&ogrPolygon)) {
            nInvalidNoRings++;
            continue;
        }

        /* store also geometry in DB */
        if (poReader->IsSpatial() &&
            SaveGeometryToDB(&ogrPolygon, rowId) != OGRERR_FAILURE)
            nGeometries++;
    }

    /* free ring list */
    for (PointListArray::iterator iRing = poRingList.begin(), eRing = poRingList.end();
         iRing != eRing; ++iRing) {
        delete (*iRing);
        *iRing = nullptr;
    }

    CPLDebug("OGR-VFK", "%s: nolines = %d norings = %d",
             m_pszName, nInvalidNoLines, nInvalidNoRings);

    /* update number of geometries in VFK_DB_TABLE table */
    UpdateVfkBlocks(nGeometries);

    if (poReader->IsSpatial())
        poReader->ExecuteSQL("COMMIT");

    return nInvalidNoLines + nInvalidNoRings;
}

/*!
  \brief Get feature by FID

  Modifies next feature id.

  \param nFID feature id

  \return pointer to feature definition or NULL on failure (not found)
*/
IVFKFeature *VFKDataBlockSQLite::GetFeature(GIntBig nFID)
{
    if (m_nFeatureCount < 0) {
        m_poReader->ReadDataRecords(this);
    }

    if (nFID < 1 || nFID > m_nFeatureCount)
        return nullptr;

    if( m_bGeometryPerBlock && !m_bGeometry )
    {
        LoadGeometry();
    }

    VFKReaderSQLite *poReader = (VFKReaderSQLite*) m_poReader;

    CPLString osSQL;
    osSQL.Printf("SELECT rowid FROM %s WHERE %s = " CPL_FRMT_GIB,
                 m_pszName, FID_COLUMN, nFID);
    if ( EQUAL(m_pszName, "SBP") || EQUAL(m_pszName, "SBPG") ) {
        osSQL += " AND PORADOVE_CISLO_BODU = 1";
    }
    sqlite3_stmt *hStmt = poReader->PrepareStatement(osSQL.c_str());

    int rowId = -1;
    if (poReader->ExecuteSQL(hStmt) == OGRERR_NONE) {
        rowId = sqlite3_column_int(hStmt, 0);
    }
    sqlite3_finalize(hStmt);

    return GetFeatureByIndex(rowId - 1);
}

/*!
  \brief Get first found feature based on its property

  \param column property name
  \param value property value
  \param bGeom True to check also geometry != NULL

  \return pointer to feature definition or NULL on failure (not found)
*/
VFKFeatureSQLite *VFKDataBlockSQLite::GetFeature(const char *column, GUIntBig value,
                                                 bool bGeom)
{
    VFKReaderSQLite *poReader = (VFKReaderSQLite*) m_poReader;

    CPLString osSQL;
    osSQL.Printf("SELECT %s from %s WHERE %s = " CPL_FRMT_GUIB,
                 FID_COLUMN, m_pszName, column, value);
    if( bGeom )
    {
        CPLString osColumn;

        osColumn.Printf(" AND %s IS NOT NULL", GEOM_COLUMN);
        osSQL += osColumn;
    }

    sqlite3_stmt *hStmt = poReader->PrepareStatement(osSQL.c_str());
    if (poReader->ExecuteSQL(hStmt) != OGRERR_NONE)
        return nullptr;

    const int idx = sqlite3_column_int(hStmt, 0) - 1;
    sqlite3_finalize(hStmt);

    if (idx < 0 || idx >= m_nFeatureCount) // ? assert
        return nullptr;

    return (VFKFeatureSQLite *) GetFeatureByIndex(idx);
}

/*!
  \brief Get first found feature based on its properties (AND)

  \param column array of property names
  \param value array of property values
  \param num number of array items
  \param bGeom True to check also geometry != NULL

  \return pointer to feature definition or NULL on failure (not found)
*/
VFKFeatureSQLite *VFKDataBlockSQLite::GetFeature(const char **column, GUIntBig *value, int num,
                                                 bool bGeom)
{
    VFKReaderSQLite *poReader = (VFKReaderSQLite*) m_poReader;

    CPLString osSQL;
    osSQL.Printf("SELECT %s FROM %s WHERE ", FID_COLUMN, m_pszName);

    CPLString osItem;
    for( int i = 0; i < num; i++ )
    {
        if (i > 0)
            osItem.Printf(" AND %s = " CPL_FRMT_GUIB, column[i], value[i]);
        else
            osItem.Printf("%s = " CPL_FRMT_GUIB, column[i], value[i]);
        osSQL += osItem;
    }
    if( bGeom )
    {
        osItem.Printf(" AND %s IS NOT NULL", GEOM_COLUMN);
        osSQL += osItem;
    }

    sqlite3_stmt *hStmt = poReader->PrepareStatement(osSQL.c_str());
    if (poReader->ExecuteSQL(hStmt) != OGRERR_NONE)
        return nullptr;

    int idx = sqlite3_column_int(hStmt, 0) - 1; /* rowid starts at 1 */
    sqlite3_finalize(hStmt);

    if (idx < 0 || idx >= m_nFeatureCount) // ? assert
        return nullptr;

    return (VFKFeatureSQLite *) GetFeatureByIndex(idx);
}

/*!
  \brief Get features based on properties

  \param column array of property names
  \param value array of property values
  \param num number of array items

  \return list of features
*/
VFKFeatureSQLiteList VFKDataBlockSQLite::GetFeatures(const char **column, GUIntBig *value, int num)
{
    VFKReaderSQLite *poReader = (VFKReaderSQLite*) m_poReader;

    CPLString osItem;
    CPLString osSQL;
    osSQL.Printf("SELECT rowid from %s WHERE ", m_pszName);
    for (int i = 0; i < num; i++) {
        if (i > 0)
            osItem.Printf(" OR %s = " CPL_FRMT_GUIB, column[i], value[i]);
        else
            osItem.Printf("%s = " CPL_FRMT_GUIB, column[i], value[i]);
        osSQL += osItem;
    }
    osSQL += " ORDER BY ";
    osSQL += FID_COLUMN;

    VFKFeatureSQLiteList fList;

    sqlite3_stmt *hStmt = poReader->PrepareStatement(osSQL.c_str());
    while (poReader->ExecuteSQL(hStmt) == OGRERR_NONE) {
        const int iRowId = sqlite3_column_int(hStmt, 0);
        VFKFeatureSQLite* poFeature = dynamic_cast<VFKFeatureSQLite*>(
            GetFeatureByIndex(iRowId - 1));
        if( poFeature == nullptr )
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Cannot retrieve feature %d", iRowId);
            sqlite3_finalize(hStmt);
            return VFKFeatureSQLiteList();
        }
        fList.push_back(poFeature);
    }

    return fList;
}

/*!
  \brief Save geometry to DB (as WKB)

  \param poGeom pointer to OGRGeometry to be saved
  \param iRowId row id to update

  \return OGRERR_NONE on success otherwise OGRERR_FAILURE
*/
OGRErr VFKDataBlockSQLite::SaveGeometryToDB(const OGRGeometry *poGeom, int iRowId)
{
    int        rc, nWKBLen;
    CPLString  osSQL;

    sqlite3_stmt *hStmt = nullptr;

    VFKReaderSQLite *poReader = (VFKReaderSQLite*) m_poReader;

    /* check if geometry column exists (see SUPPRESS_GEOMETRY open
       option) */
    if ( AddGeometryColumn() != OGRERR_NONE )
        return OGRERR_FAILURE;

    if (poGeom) {
        nWKBLen = poGeom->WkbSize();
        GByte *pabyWKB = (GByte *) CPLMalloc(nWKBLen + 1);
        poGeom->exportToWkb(wkbNDR, pabyWKB);

        osSQL.Printf("UPDATE %s SET %s = ? WHERE rowid = %d",
                     m_pszName, GEOM_COLUMN, iRowId);
        hStmt = poReader->PrepareStatement(osSQL.c_str());

        rc = sqlite3_bind_blob(hStmt, 1, pabyWKB, nWKBLen, CPLFree);
        if (rc != SQLITE_OK) {
            sqlite3_finalize(hStmt);
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Storing geometry in DB failed");
            return OGRERR_FAILURE;
        }
    }
    else { /* invalid */
        osSQL.Printf("UPDATE %s SET %s = NULL WHERE rowid = %d",
                     m_pszName, GEOM_COLUMN, iRowId);
        hStmt = poReader->PrepareStatement(osSQL.c_str());
    }

    return poReader->ExecuteSQL(hStmt); /* calls sqlite3_finalize() */
}

/*!
  \brief Load geometry from DB

  \return true if geometry successfully loaded otherwise false
*/
bool VFKDataBlockSQLite::LoadGeometryFromDB()
{
    VFKReaderSQLite *poReader = (VFKReaderSQLite*) m_poReader;

    if (!poReader->IsSpatial())   /* check if DB is spatial */
        return false;

    CPLString osSQL;
    osSQL.Printf("SELECT num_geometries FROM %s WHERE table_name = '%s'",
                 VFK_DB_TABLE, m_pszName);
    sqlite3_stmt *hStmt = poReader->PrepareStatement(osSQL.c_str());
    if (poReader->ExecuteSQL(hStmt) != OGRERR_NONE)
        return false;
    const int nGeometries = sqlite3_column_int(hStmt, 0);
    sqlite3_finalize(hStmt);

    if( nGeometries < 1 )
        return false;

    const bool bSkipInvalid =
        EQUAL(m_pszName, "OB") ||
        EQUAL(m_pszName, "OP") ||
        EQUAL(m_pszName, "OBBP");

    /* load geometry from DB */
    osSQL.Printf("SELECT %s,rowid,%s FROM %s ",
                 GEOM_COLUMN, FID_COLUMN, m_pszName);
    if ( EQUAL(m_pszName, "SBP") || EQUAL(m_pszName, "SBPG") )
        osSQL += "WHERE PORADOVE_CISLO_BODU = 1 ";
    osSQL += "ORDER BY ";
    osSQL += FID_COLUMN;
    hStmt = poReader->PrepareStatement(osSQL.c_str());

    int rowId = 0;
    int nInvalid = 0;
    int nGeometriesCount = 0;

    while(poReader->ExecuteSQL(hStmt) == OGRERR_NONE) {
        rowId++; // =sqlite3_column_int(hStmt, 1);
        const GIntBig iFID = sqlite3_column_int64(hStmt, 2);
        VFKFeatureSQLite *poFeature = dynamic_cast<VFKFeatureSQLite *>(
            GetFeatureByIndex(rowId - 1));
        if( poFeature == nullptr || poFeature->GetFID() != iFID )
        {
            continue;
        }

        // read geometry from DB
        const int nBytes = sqlite3_column_bytes(hStmt, 0);
        OGRGeometry *poGeometry = nullptr;
        if (nBytes > 0 &&
            OGRGeometryFactory::createFromWkb(sqlite3_column_blob(hStmt, 0),
                                              nullptr, &poGeometry, nBytes) == OGRERR_NONE) {
            nGeometriesCount++;
            if (!poFeature->SetGeometry(poGeometry)) {
                nInvalid++;
            }
            delete poGeometry;
        }
        else {
            nInvalid++;
        }
    }

    CPLDebug("OGR-VFK", "%s: %d geometries loaded from DB",
             m_pszName, nGeometriesCount);

    if (nGeometriesCount != nGeometries) {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "%s: %d geometries loaded (should be %d)",
                 m_pszName, nGeometriesCount, nGeometries);
    }

    if (nInvalid > 0 && !bSkipInvalid) {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "%s: %d features with invalid or empty geometry",
                 m_pszName, nInvalid);
    }

    return true;
}

/*!
  \brief Update VFK_DB_TABLE table

  \param nGeometries number of geometries to update
*/
void VFKDataBlockSQLite::UpdateVfkBlocks(int nGeometries) {
    CPLString osSQL;

    VFKReaderSQLite *poReader = (VFKReaderSQLite*) m_poReader;

    /* update number of features in VFK_DB_TABLE table */
    const int nFeatCount = (int)GetFeatureCount();
    if (nFeatCount > 0) {
        osSQL.Printf("UPDATE %s SET num_features = %d WHERE table_name = '%s'",
                     VFK_DB_TABLE, nFeatCount, m_pszName);
        poReader->ExecuteSQL(osSQL.c_str());
    }

    /* update number of geometries in VFK_DB_TABLE table */
    if (nGeometries > 0) {
        CPLDebug("OGR-VFK",
                 "VFKDataBlockSQLite::UpdateVfkBlocks(): name=%s -> "
                 "%d geometries saved to internal DB", m_pszName, nGeometries);

        osSQL.Printf("UPDATE %s SET num_geometries = %d WHERE table_name = '%s'",
                     VFK_DB_TABLE, nGeometries, m_pszName);
        poReader->ExecuteSQL(osSQL.c_str());
    }
}

/*!
  \brief Update feature id (see SBP)

  \param iFID feature id to set up
  \param rowId list of rows to update
*/
void VFKDataBlockSQLite::UpdateFID(GIntBig iFID, std::vector<int> rowId)
{
    CPLString osSQL, osValue;
    VFKReaderSQLite *poReader = (VFKReaderSQLite*) m_poReader;

    /* update number of geometries in VFK_DB_TABLE table */
    osSQL.Printf("UPDATE %s SET %s = " CPL_FRMT_GIB " WHERE rowid IN (",
                 m_pszName, FID_COLUMN, iFID);
    for (size_t i = 0; i < rowId.size(); i++) {
        if (i > 0)
            osValue.Printf(",%d", rowId[i]);
        else
            osValue.Printf("%d", rowId[i]);
        osSQL += osValue;
    }
    osSQL += ")";

    poReader->ExecuteSQL(osSQL.c_str());
}

/*!
  \brief Check is ring is closed

  \param poRing pointer to OGRLinearRing to check

  \return true if closed otherwise false
*/
bool VFKDataBlockSQLite::IsRingClosed( const OGRLinearRing *poRing )
{
    const int nPoints = poRing->getNumPoints();
    if (nPoints < 3)
        return false;

    if (poRing->getX(0) == poRing->getX(nPoints-1) &&
        poRing->getY(0) == poRing->getY(nPoints-1))
        return true;

    return false;
}

/*!
  \brief Get primary key

  \return property name or NULL
*/
const char *VFKDataBlockSQLite::GetKey() const
{
    if( GetPropertyCount() > 1 )
    {
        const VFKPropertyDefn *poPropDefn = GetProperty(0);
        const char *pszKey = poPropDefn->GetName();
        if( EQUAL(pszKey, "ID") )
            return pszKey;
    }

    return nullptr;
}

/*!
  \brief Get geometry SQL type (for geometry_columns table)

  \return geometry_type as integer
*/
int VFKDataBlockSQLite::GetGeometrySQLType() const
{
    if (m_nGeometryType == wkbPolygon)
        return 3;
    else if (m_nGeometryType == wkbLineString)
        return 2;
    else if (m_nGeometryType == wkbPoint)
        return 1;

    return 0; /* unknown geometry type */
}

/*!
  \brief Add geometry column into table if not exists

  \return OGRERR_NONE on success otherwise OGRERR_FAILURE
 */
OGRErr VFKDataBlockSQLite::AddGeometryColumn() const
{
    CPLString osSQL;

    VFKReaderSQLite *poReader = (VFKReaderSQLite*) m_poReader;

    osSQL.Printf("SELECT %s FROM %s LIMIT 0",
                 GEOM_COLUMN, m_pszName);
    if ( poReader->ExecuteSQL(osSQL.c_str(), CE_None) == OGRERR_FAILURE ) {
        /* query failed, we assume that geometry column not exists */
        osSQL.Printf("ALTER TABLE %s ADD COLUMN %s blob",
                     m_pszName, GEOM_COLUMN);
        return poReader->ExecuteSQL(osSQL.c_str());
    }

    return OGRERR_NONE;
}

/*!
  \brief Load feature properties

  Used for sequential access, see OGRVFKLayer:GetNextFeature().

  \return OGRERR_NONE on success otherwise OGRERR_FAILURE
*/
OGRErr VFKDataBlockSQLite::LoadProperties()
{
    CPLString osSQL;

    if ( m_hStmt )
        sqlite3_finalize(m_hStmt);

    osSQL.Printf("SELECT * FROM %s", // TODO: where
                m_pszName);
    if ( EQUAL(m_pszName, "SBP") || EQUAL(m_pszName, "SBPG") )
        osSQL += " WHERE PORADOVE_CISLO_BODU = 1";

    m_hStmt = ((VFKReaderSQLite*) m_poReader)->PrepareStatement(osSQL.c_str());

    if ( m_hStmt == nullptr )
        return OGRERR_FAILURE;

    return OGRERR_NONE;
}

/*
  \brief Clean feature properties for a next run

  \return OGRERR_NONE on success otherwise OGRERR_FAILURE
*/