/***********************************************************************
*
* Copyright (c) 2012-2020 Barbara Geller
* Copyright (c) 2012-2020 Ansel Sermersheim
*
* Copyright (c) 2015 The Qt Company Ltd.
* Copyright (c) 2012-2016 Digia Plc and/or its subsidiary(-ies).
* Copyright (c) 2008-2012 Nokia Corporation and/or its subsidiary(-ies).
*
* This file is part of CopperSpice.
*
* CopperSpice is free software. You can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* CopperSpice 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.
*
* https://www.gnu.org/licenses/
*
***********************************************************************/

#include <qbackingstore.h>

#include <qwindow.h>
#include <qpixmap.h>
#include <qplatform_backingstore.h>
#include <qplatform_integration.h>
#include <qscreen.h>
#include <qdebug.h>
#include <qscopedpointer.h>

#include <qguiapplication_p.h>
#include <qwindow_p.h>
#include <qhighdpiscaling_p.h>

class QBackingStorePrivate
{
 public:
   QBackingStorePrivate(QWindow *w)
      : window(w) {
   }

   QWindow *window;
   QPlatformBackingStore *platformBackingStore;
   QScopedPointer<QImage> highDpiBackingstore;
   QRegion staticContents;
   QSize size;
};

void QBackingStore::flush(const QRegion &region, QWindow *win, const QPoint &offset)
{
   if (! win) {
      win = window();
   }

   if (! win->handle()) {
#if defined(CS_SHOW_DEBUG)
      qWarning() << "QBackingStore::flush(): Called for "
                 << win << " which does not have a handle.";
#endif
      return;
   }

   if (win && win->isTopLevel() && ! qt_window_private(win)->receivedExpose) {
#if defined(CS_SHOW_DEBUG)
      qWarning() << "QBackingStore::flush():  Called with non-exposed window "
                 << win << ", behavior is undefined";
#endif
   }

   d_ptr->platformBackingStore->flush(win, QHighDpi::toNativeLocalRegion(region, win),
      QHighDpi::toNativeLocalPosition(offset, win));
}

QPaintDevice *QBackingStore::paintDevice()
{
   QPaintDevice *device = d_ptr->platformBackingStore->paintDevice();

   if (QHighDpiScaling::isActive() && device->devType() == QInternal::Image) {
      return d_ptr->highDpiBackingstore.data();
   }

   return device;
}

QBackingStore::QBackingStore(QWindow *window)
   : d_ptr(new QBackingStorePrivate(window))
{
   d_ptr->platformBackingStore = QGuiApplicationPrivate::platformIntegration()->createPlatformBackingStore(window);
}

QBackingStore::~QBackingStore()
{
   delete d_ptr->platformBackingStore;
}

QWindow *QBackingStore::window() const
{
   return d_ptr->window;
}

void QBackingStore::beginPaint(const QRegion &region)
{
   if (d_ptr->highDpiBackingstore &&
      d_ptr->highDpiBackingstore->devicePixelRatio() != d_ptr->window->devicePixelRatio()) {
      resize(size());
   }

   d_ptr->platformBackingStore->beginPaint(QHighDpi::toNativeLocalRegion(region, d_ptr->window));

   // When QtGui is applying a high-dpi scale factor the backing store
   // creates a "large" backing store image. This image needs to be
   // painted on as a high-dpi image, which is done by setting
   // devicePixelRatio. Do this on a separate image instance that shares
   // the image data to avoid having the new devicePixelRatio be propagated
   // back to the platform plugin.

   QPaintDevice *device = d_ptr->platformBackingStore->paintDevice();
   if (QHighDpiScaling::isActive() && device->devType() == QInternal::Image) {
      QImage *source = static_cast<QImage *>(device);

      const bool needsNewImage = d_ptr->highDpiBackingstore.isNull()
         || source->data_ptr() != d_ptr->highDpiBackingstore->data_ptr()
         || source->size() != d_ptr->highDpiBackingstore->size()
         || source->devicePixelRatio() != d_ptr->highDpiBackingstore->devicePixelRatio();

      if (needsNewImage) {

#if defined(CS_SHOW_DEBUG)
         qDebug() << "QBackingStore::beginPaint(): " << d_ptr->window << "\n  "
                  << "Source size =" << source->size() << " DP ratio =" << source->devicePixelRatio();
#endif

         d_ptr->highDpiBackingstore.reset(
            new QImage(source->bits(), source->width(), source->height(), source->bytesPerLine(), source->format()));

         qreal targetDevicePixelRatio = d_ptr->window->devicePixelRatio();
         d_ptr->highDpiBackingstore->setDevicePixelRatio(targetDevicePixelRatio);

#if defined(CS_SHOW_DEBUG)
         qDebug() << "   Destination Size =" << d_ptr->highDpiBackingstore->size()
                  << "DP ratio = " << targetDevicePixelRatio;
#endif
      }
   }
}

void QBackingStore::endPaint()
{
   d_ptr->platformBackingStore->endPaint();
}

void QBackingStore::resize(const QSize &size)
{
   d_ptr->size = size;
   d_ptr->platformBackingStore->resize(QHighDpi::toNativePixels(size, d_ptr->window), d_ptr->staticContents);
}

QSize QBackingStore::size() const
{
   return d_ptr->size;
}

bool QBackingStore::scroll(const QRegion &area, int dx, int dy)
{
   // Disable scrolling for non-integer scroll deltas. For this case
   // the the existing rendered pixels can't be re-used, and we return
   // false to signal that a repaint is needed.
   const qreal nativeDx = QHighDpi::toNativePixels(qreal(dx), d_ptr->window);
   const qreal nativeDy = QHighDpi::toNativePixels(qreal(dy), d_ptr->window);
   if (qFloor(nativeDx) != nativeDx || qFloor(nativeDy) != nativeDy) {
      return false;
   }

   return d_ptr->platformBackingStore->scroll(QHighDpi::toNativeLocalRegion(area, d_ptr->window),
         nativeDx, nativeDy);
}

void QBackingStore::setStaticContents(const QRegion &region)
{
   d_ptr->staticContents = region;
}

QRegion QBackingStore::staticContents() const
{
   return d_ptr->staticContents;
}

bool QBackingStore::hasStaticContents() const
{
   return !d_ptr->staticContents.isEmpty();
}

void Q_GUI_EXPORT qt_scrollRectInImage(QImage &img, const QRect &rect, const QPoint &offset)
{
   // make sure we do not detach
   uchar *mem = const_cast<uchar *>(const_cast<const QImage &>(img).bits());

   int lineskip = img.bytesPerLine();
   int depth = img.depth() >> 3;

   const QRect imageRect(0, 0, img.width(), img.height());
   const QRect r = rect & imageRect & imageRect.translated(-offset);
   const QPoint p = rect.topLeft() + offset;

   if (r.isEmpty()) {
      return;
   }

   const uchar *src;
   uchar *dest;

   if (r.top() < p.y()) {
      src = mem + r.bottom() * lineskip + r.left() * depth;
      dest = mem + (p.y() + r.height() - 1) * lineskip + p.x() * depth;
      lineskip = -lineskip;
   } else {
      src = mem + r.top() * lineskip + r.left() * depth;
      dest = mem + p.y() * lineskip + p.x() * depth;
   }

   const int w = r.width();
   int h = r.height();
   const int bytes = w * depth;

   // overlapping segments?
   if (offset.y() == 0 && qAbs(offset.x()) < w) {
      do {
         ::memmove(dest, src, bytes);
         dest += lineskip;
         src += lineskip;
      } while (--h);
   } else {
      do {
         ::memcpy(dest, src, bytes);
         dest += lineskip;
         src += lineskip;
      } while (--h);
   }
}

QPlatformBackingStore *QBackingStore::handle() const
{
   return d_ptr->platformBackingStore;
}

