/*
 *  Copyright (C) 2003 by Unai Garro <ugarro@users.sourceforge.net>
 *  Copyright (C) 2004 by Enrico Ros <rosenric@dei.unipd.it>
 *  Copyright (C) 2004 by Stephan Kulow <coolo@kde.org>
 *  Copyright (C) 2004 by Oswald Buddenhagen <ossi@kde.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "kdmpixmap.h"
#include "kdmthemer.h"

//#include <kstandarddirs.h>

#include <QApplication>
#include <QScreen>
#include <QDirIterator>
#include <QPainter>
#include <QSignalMapper>
#include <QSvgRenderer>
#include <QX11Info>

#include <math.h>

#include <strings.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>


KdmPixmap::KdmPixmap(QObject *parent, const QDomNode &node)
    : KdmItem(parent, node)
{
    itemType = "pixmap";
    if (!isVisible())
        return;

    // Set default values for pixmap (note: strings are already Null)
    pixmap.normal.tint.setRgb(0xFFFFFF);
    pixmap.normal.present = true;

    // Read PIXMAP TAGS
    QDomNodeList childList = node.childNodes();
    for (int nod = 0; nod < childList.count(); nod++) {
        QDomNode child = childList.item(nod);
        QDomElement el = child.toElement();
        QString tagName = el.tagName();

        if (tagName == "normal") {
            definePixmap(el, pixmap.normal);
            parseColor(el.attribute("tint", "#ffffff"), el.attribute("alpha", "1.0"), pixmap.normal.tint);
        } else if (tagName == "active") {
            pixmap.active.present = true;
            definePixmap(el, pixmap.active);
            parseColor(el.attribute("tint", "#ffffff"), el.attribute("alpha", "1.0"), pixmap.active.tint);
        } else if (tagName == "prelight") {
            pixmap.prelight.present = true;
            definePixmap(el, pixmap.prelight);
            parseColor(el.attribute("tint", "#ffffff"), el.attribute("alpha", "1.0"), pixmap.prelight.tint);
        }
    }
}

void
KdmPixmap::definePixmap(const QDomElement &el, PixmapStruct::PixmapClass &pClass)
{
    QString fileName = el.attribute("file");
    if (!fileName.isEmpty()) {
        if (fileName.at(0) != '/')
            fileName.prepend(themer()->baseDir() + '/');
        pClass.fullpath = fileName;
    } else {
        fileName = el.attribute("wallpaper");
        if (fileName.isEmpty())
            return;
        QDir di; //alex: try to keep kde4 compatibility
        QString xf = QString("/usr/share/wallpapers/")+fileName+"/contents/images";// = KStandardDirs::locate("wallpaper", fileName + "/contents/images/");
        di = QDir(xf);
        if (di.exists()/*!xf.isEmpty()*/) {
            pClass.package = true;
        } else {
            if (fileName.at(0) != '/') //alex
                 xf = QString("/usr/share/wallpapers/")+fileName; //KStandardDirs::locate("wallpaper", fileName);
            else xf = fileName;
            QFileInfo fi(xf);
            if (!fi.exists()/*xf.isEmpty()*/) {
                fprintf(stderr,"Cannot find wallpaper '%s'\n",fileName.toLocal8Bit().data()); //kWarning() << "Cannot find wallpaper" << fileName;
                return;
            }
        }
        pClass.fullpath = xf;
    }

    QString aspect = el.attribute("scalemode", "free");
    pClass.aspectMode =
            (aspect == "fit") ? Qt::KeepAspectRatio :
            (aspect == "crop") ? Qt::KeepAspectRatioByExpanding :
            Qt::IgnoreAspectRatio;

    pClass.svgImage = fileName.endsWith(".svg") || fileName.endsWith(".svgz");
    if (pClass.svgImage)
        pClass.svgElement = el.attribute("element");
}

QString
KdmPixmap::findBestPixmap(const QString &dir, const QString &pat,
                          const QSize &size, Qt::AspectRatioMode aspectMode)
{
    int tw = size.width(), th = size.height();
    float tar = 1.0 * tw / th;
    float tpn = tw * th;
    QString best;
    float bestPenalty = 0;
    QRegExp rx(QRegExp::escape(dir) + pat);
    QDirIterator dit(dir);
    while (dit.hasNext()) {
        QString fn = dit.next();
        if (rx.exactMatch(fn)) {
            int w = rx.cap(1).toInt(), h = rx.cap(2).toInt();
            // This algorithm considers need for zooming and distortion of
            // aspect ratio / discarded pixels / pixels left free. The weighting
            // gives good results in the tested cases, but is pretty arbitrary
            // by all practical means.
            float ar = 1.0 * w / h;
            float pn = w * h;
            float rawAspect = ((tar > ar) ? tar / ar : ar / tar);
            float penalty = rawAspect - 1;
            if (aspectMode != Qt::IgnoreAspectRatio) {
                bool exp = (aspectMode == Qt::KeepAspectRatioByExpanding);
                // Give an advantage to non-zooming cases.
                if ((w == tw && (h > th) == exp) || (h == th && (w > tw) == exp))
                    goto skipSize;
                else
                    penalty *= 5;
                // Dropped pixels don't contribute to the input area.
                if (exp)
                    pn /= rawAspect;
            } else {
                // This mode does not preserve aspect ratio, so give an
                // advantage to pics with a better ratio to start with.
                penalty *= 10;
            }
            // Too small is worse than too big - within limits.
            penalty += sqrt((tpn > pn) ? (tpn / pn - 1) * 2 : pn / tpn - 1);
            skipSize:
            if (best.isEmpty() || penalty < bestPenalty) {
                best = fn;
                bestPenalty = penalty;
            }
        }
    }
    return best;
}

bool
KdmPixmap::loadPixmap(PixmapStruct::PixmapClass &pClass)
{
    if (!pClass.image.isNull())
        return true;
    if (pClass.fullpath.isEmpty())
        return false;
    QString fn;
    QSize size = area.size() * qApp->devicePixelRatio();
    if (pClass.package) {
        // Always find best fit from package.
        fn = findBestPixmap(pClass.fullpath, "(\\d+)x(\\d+)\\.[^.]+",
                            area.isValid() ? size : QSize(1600, 1200),
                            pClass.aspectMode);
    } else {
        if (area.isValid()) {
            if (QFile::exists(pClass.fullpath)) {
                // If base file exists, use only a perfect match.
                int dot = pClass.fullpath.lastIndexOf('.');
                fn = pClass.fullpath.left(dot);
                fn += QString("-%1x%2").arg(area.width()).arg(area.height());
                fn += pClass.fullpath.mid(dot);
                if (!QFile::exists(fn))
                    fn = pClass.fullpath;
            } else {
                // Otherwise find best match.
                int sep = pClass.fullpath.lastIndexOf('/');
                int dot = pClass.fullpath.lastIndexOf('.');
                if (dot < sep)
                    dot = pClass.fullpath.length();
                QString f = QRegExp::escape(pClass.fullpath.mid(sep + 1, dot - sep - 1));
                f += "-(\\d+)x(\\d+)";
                f += QRegExp::escape(pClass.fullpath.mid(dot));
                fn = findBestPixmap(pClass.fullpath.left(sep + 1), f, size,
                                    pClass.aspectMode);
            }
        } else {
            fn = pClass.fullpath;
        }
    }
    if (!pClass.image.load(fn)) {
        fprintf(stderr,"failed to load image from '%s'\n",fn.toLocal8Bit().data()); //kWarning() << "failed to load" << fn;
        pClass.fullpath.clear();
        return false;
    }
    if (pClass.image.format() != QImage::Format_ARGB32)
        pClass.image = pClass.image.convertToFormat(QImage::Format_ARGB32);
    applyTint(pClass, pClass.image);
    return true;
}

bool
KdmPixmap::loadSvg(PixmapStruct::PixmapClass &pClass)
{
    if (pClass.svgRenderer)
        return true;
    if (pClass.fullpath.isEmpty())
        return false;
    pClass.svgRenderer = new QSvgRenderer(pClass.fullpath, this);
    if (!pClass.svgRenderer->isValid()) {
        delete pClass.svgRenderer;
        pClass.svgRenderer = nullptr;
        /*kWarning() <<*/fprintf(stderr, "failed to load %s\n", pClass.fullpath.toLocal8Bit().data()) ;
        pClass.fullpath.clear();
        return false;
    }
    if (pClass.svgRenderer->animated()) {
        connect(pClass.svgRenderer, &QSvgRenderer::repaintNeeded, this, [this, currentState = state]() {
            slotAnimate(currentState);
        });
    }
    pClass.svgSizeHint = pClass.svgElement.isEmpty() ?
        pClass.svgRenderer->defaultSize() :
        pClass.svgRenderer->boundsOnElement(pClass.svgElement).size().toSize();
    return true;
}

QSize
KdmPixmap::sizeHint()
{
    // use the pixmap size as the size hint
    if (!pixmap.normal.svgImage) {
        if (loadPixmap(pixmap.normal))
            return pixmap.normal.image.size() / pixmap.normal.image.devicePixelRatio();
    } else {
        if (loadSvg(pixmap.normal))
            return pixmap.normal.svgSizeHint;
    }
    return KdmItem::sizeHint();
}

void
KdmPixmap::setGeometry(QStack<QSize> &parentSizes, const QRect &newGeometry, bool force)
{
    KdmItem::setGeometry(parentSizes, newGeometry, force);
    pixmap.active.targetArea = QRect();
    pixmap.prelight.targetArea = QRect();
    pixmap.normal.targetArea = QRect();
}

bool
KdmPixmap::calcTargetArea(PixmapStruct::PixmapClass &pClass, const QSize &sh)
{
    QSize sz = sh;
    sz.scale(area.size(), pClass.aspectMode);
    pClass.targetArea.setSize(sz);
    pClass.targetArea.moveCenter(area.center());
    return pClass.targetArea.size() * pClass.readyPixmap.devicePixelRatio() != pClass.readyPixmap.size();
}


static XImage * ximageFromQImage(Display *dpy, int scr, int depth, const QImage &imgsrc)
{
  enum ByteOrders {
    bo32_ARGB, bo32_BGRA, bo24_RGB, bo24_BGR,
    bo16_RGB_565, bo16_BGR_565, bo16_RGB_555,
    bo16_BGR_555, bo8
  };
  typedef short     int16;
  typedef int       int32;

  XImage * ximage = XCreateImage(dpy, DefaultVisual(dpy, scr), depth, ZPixmap, 0, nullptr,
                                 imgsrc.size().width(), imgsrc.size().height(), 32, 0);
  if (!ximage) return nullptr;

  char* d = (char *)malloc(imgsrc.bytesPerLine() * imgsrc.size().height() * sizeof(char));
  if (!d)
  {
      XDestroyImage(ximage);
      return nullptr;
  }
  ximage->data = d;


  int bpp = ximage->bits_per_pixel;
  if (ximage->byte_order == LSBFirst) bpp++;

  int red_shift   = ffsl(ximage->red_mask)-1;
  int green_shift = ffsl(ximage->green_mask)-1;
  int blue_shift  = ffsl(ximage->blue_mask)-1;

  int byteorder;
       if ((bpp == 32) && (red_shift == 16) && (green_shift == 8) && (blue_shift == 0)) byteorder = bo32_ARGB;
  else if ((bpp == 33) && (red_shift == 16) && (green_shift == 8) && (blue_shift == 0)) byteorder = bo32_BGRA;
  else if ((bpp == 24) && (red_shift == 16) && (green_shift == 8) && (blue_shift == 0)) byteorder = bo24_RGB;
  else if ((bpp == 25) && (red_shift == 16) && (green_shift == 8) && (blue_shift == 0)) byteorder = bo24_BGR;
  else if ((bpp == 16) && (red_shift == 11) && (green_shift == 5) && (blue_shift == 0)) byteorder = bo16_RGB_565;
  else if ((bpp == 16) && (red_shift == 10) && (green_shift == 5) && (blue_shift == 0)) byteorder = bo16_RGB_555;
  else if ((bpp == 17) && (red_shift == 11) && (green_shift == 5) && (blue_shift == 0)) byteorder = bo16_BGR_565;
  else if ((bpp == 16) && (red_shift == 10) && (green_shift == 5) && (blue_shift == 0)) byteorder = bo16_BGR_555;
  else if ((bpp == 8) || (bpp == 9)) byteorder = bo8;
  else
  {
      XDestroyImage(ximage);
      return nullptr;
  }

  int x, y;
  int width  = ximage->width, 
      height = ximage->height;
  int bpl    = ximage->bytes_per_line;
  char *data = ximage->data;

  QImage img;
  switch (byteorder)
  {
    case bo16_RGB_555:
    case bo16_BGR_555:
    {
        if (img.format() != QImage::Format_RGB32 &&
            img.format() != QImage::Format_ARGB32_Premultiplied &&
            img.format() != QImage::Format_ARGB32)
            img = imgsrc.convertToFormat(QImage::Format_RGB32);
        else
            img = imgsrc;

        QRgb *src, pixel;
        int32 *dst, val;
        for (y=0; y<height; y++)
        {
           src = (QRgb *) img.scanLine(y);
           dst = (int32 *) (data + y*bpl);
           for (x=0; x<width/2; x++) {
               pixel = *src++;
               val = ((pixel & 0xf80000) >> 9) | ((pixel & 0xf800) >> 5) | ((pixel & 0xff) >> 3);
               pixel = *src++;
               val |= (((pixel & 0xf80000) >> 9) | ((pixel & 0xf800) >> 5) | ((pixel & 0xff) >> 3)) << 16;
               *dst++ = val;
           }
           if (width%2)
           {
               pixel = *src++;
               *((int16 *)dst) = ((pixel & 0xf80000) >> 9) | ((pixel & 0xf800) >> 5) | ((pixel & 0xff) >> 3);
           }
        }
    }
    break;

    case bo16_RGB_565:
    case bo16_BGR_565:
    {
          if (img.format() != QImage::Format_RGB32 &&
              img.format() != QImage::Format_ARGB32_Premultiplied &&
              img.format() != QImage::Format_ARGB32)
              img = imgsrc.convertToFormat(QImage::Format_RGB32);
          else
              img = imgsrc;

          QRgb *src, pixel;
          int32 *dst, val;
          for (y=0; y<height; y++)
          {
              src = (QRgb *) img.scanLine(y);
              dst = (int32 *) (data + y*bpl);
              for (x=0; x<width/2; x++)
              {
                  pixel = *src++;
                  val = ((pixel & 0xf80000) >> 8) | ((pixel & 0xfc00) >> 5) | ((pixel & 0xff) >> 3);
                  pixel = *src++;
                  val |= (((pixel & 0xf80000) >> 8) | ((pixel & 0xfc00) >> 5) | ((pixel & 0xff) >> 3)) << 16;
                  *dst++ = val;
              }
              if (width%2)
              {
                  pixel = *src++;
                  *((int16 *)dst) = ((pixel & 0xf80000) >> 8) | ((pixel & 0xfc00) >> 5) | ((pixel & 0xff) >> 3);
              }
          }
    }
    break;

    case bo24_RGB:
      {
          if (img.format() != QImage::Format_RGB32 &&
              img.format() != QImage::Format_ARGB32_Premultiplied &&
              img.format() != QImage::Format_ARGB32)
              img = imgsrc.convertToFormat(QImage::Format_RGB32);
          else
              img = imgsrc;

          char *dst;
          int w1 = width/4;
          QRgb *src, d1, d2, d3, d4;
          for (y=0; y<height; y++)
          {
              src = (QRgb *) img.scanLine(y);
              dst = data + y*bpl;
              for (x=0; x<w1; x++)
              {
                  d1 = (*src++ & 0xffffff);
                  d2 = (*src++ & 0xffffff);
                  d3 = (*src++ & 0xffffff);
                  d4 = (*src++ & 0xffffff);
                  *((int32 *)dst) = d1 | (d2 << 24);
                  *((int32 *)dst+1) = (d2 >> 8) | (d3 << 16);
                  *((int32 *)dst+2) = (d4 << 8) | (d3 >> 16);
                  dst += 12;
              }
              for (x=w1*4; x<width; x++)
              {
                  d1 = *src++;
                  *dst++ = qRed(d1);
                  *dst++ = qGreen(d1);
                  *dst++ = qBlue(d1);
              }
          }
      }
      break;

    case bo24_BGR:
    {
          if (img.format() != QImage::Format_RGB32 &&
              img.format() != QImage::Format_ARGB32_Premultiplied &&
              img.format() != QImage::Format_ARGB32)
              img = imgsrc.convertToFormat(QImage::Format_RGB32);
          else
              img = imgsrc;

          char *dst;
          QRgb *src, d1, d2, d3, d4;
          int w1 = width/4;
          for (y=0; y<height; y++)
          {
              src = (QRgb *) img.scanLine(y);
              dst = data + y*bpl;
              for (x=0; x<w1; x++)
              {
                  d1 = (*src++ & 0xffffff);
                  d2 = (*src++ & 0xffffff);
                  d3 = (*src++ & 0xffffff);
                  d4 = (*src++ & 0xffffff);
                  *((int32 *)dst) = d1 | (d2 << 24);
                  *((int32 *)dst+1) = (d2 >> 8) | (d3 << 16);
                  *((int32 *)dst+2) = (d4 << 8) | (d3 >> 16);
                  dst += 12;
              }
              for (x=w1*4; x<width; x++)
              {
                  d1 = *src++;
                  *dst++ = qBlue(d1);
                  *dst++ = qGreen(d1);
                  *dst++ = qRed(d1);
              }
          }
    }
    break;

    case bo32_ARGB:
    case bo32_BGRA:
    {
        if (img.format() != QImage::Format_RGB32 &&
            img.format() != QImage::Format_ARGB32_Premultiplied &&
            img.format() != QImage::Format_ARGB32)
            img = imgsrc.convertToFormat(QImage::Format_RGB32);
        else
            img = imgsrc;

        for (y=0; y<height; y++) memcpy(data + y*bpl, img.scanLine(y), width*4);
    }
    break;
  }

  return ximage;
}


void
KdmPixmap::drawContents(QPainter *p, const QRect &r)
{
    PixmapStruct::PixmapClass &pClass = getCurClass();

    if (pClass.targetArea.isEmpty()) {
        const auto dpr = p->device()->devicePixelRatioF();
        QImage scaledImage;

        if (pClass.svgImage) {
            if (loadSvg(pClass)) {
                if (!calcTargetArea(pClass, pClass.svgSizeHint))
                    goto noop;
                scaledImage = QImage(pClass.targetArea.size() * dpr, QImage::Format_ARGB32);
                scaledImage.setDevicePixelRatio(dpr);
                scaledImage.fill(0);
                QPainter pa(&scaledImage);
                if (pClass.svgElement.isEmpty())
                    pClass.svgRenderer->render(&pa);
                else
                    pClass.svgRenderer->render(&pa, pClass.svgElement);
                applyTint(pClass, scaledImage);
            }
        } else {
            if (loadPixmap(pClass)) {
                if (!calcTargetArea(pClass, pClass.image.size()))
                    goto noop;
                scaledImage =
                    (area.size() * dpr != pClass.image.size()) ?
                        pClass.image.scaled(pClass.targetArea.size() * dpr,
                                            Qt::IgnoreAspectRatio, Qt::SmoothTransformation) :
                        pClass.image;
                scaledImage.setDevicePixelRatio(dpr);
            }
        }

        if (scaledImage.isNull()) {
            p->fillRect(r, Qt::black);
            return;
        }

        //alex: set root window background
        static int done=0;
        if (done==0)
        if (isBackground &&  QX11Info::isPlatformX11())
        {
            done=1;

            Display *dpy = QX11Info::display();
            int scr      = QX11Info::appScreen();
            int depth    = DefaultDepth (dpy, scr);
            GC gc        = DefaultGC (dpy, scr);

            Window rootWin = QX11Info::appRootWindow();

            int w = scaledImage.size().width(), 
                h = scaledImage.size().height(),
                scrW, scrH;

            XImage *rootImg;

/*
            XWindowAttributes attr;
            if (XGetWindowAttributes(dpy, rootWin, &attr) && (w!=attr.width || h!=attr.height))
            {
               scrW = attr.width; scrH = attr.height;
            }
*/
            QScreen *qscr = qApp->primaryScreen();
            scrW = qscr->size().width()  * qscr->devicePixelRatio();
            scrH = qscr->size().height() * qscr->devicePixelRatio();

            if (w!=scrW || h!=scrH)
            {
                 w = scrW; h = scrH;
                 rootImg = ximageFromQImage(dpy,scr,depth, scaledImage.scaled(w,h, Qt::KeepAspectRatio, Qt::SmoothTransformation));
            }
            else rootImg = ximageFromQImage(dpy,scr,depth, scaledImage);

            if (rootImg)
            {
                Pixmap rootPix = XCreatePixmap(dpy,rootWin, w,h, depth);
                XPutImage(dpy, rootPix, gc, rootImg, 0,0,0,0, w,h);

                XSetWindowBackgroundPixmap(dpy, rootWin, rootPix);
                XClearWindow(dpy, rootWin);

                XDestroyImage(rootImg);
                XFreePixmap(dpy,rootPix);
            }
        }

        pClass.readyPixmap = QPixmap::fromImage(scaledImage);
    }
  noop:
    QRect tr = r.intersected(pClass.targetArea);
    const auto srcDpr = pClass.readyPixmap.devicePixelRatio();
    QRect sourceRect = QRect((tr.topLeft() - pClass.targetArea.topLeft()) * srcDpr,
                             tr.size() * srcDpr);
    p->drawPixmap(tr.topLeft(), pClass.readyPixmap, sourceRect);
}

void
KdmPixmap::applyTint(PixmapStruct::PixmapClass &pClass, QImage &img)
{
    if (pClass.tint.rgba() == 0xFFFFFFFF)
        return;

    int w = img.width();
    int h = img.height();
    int tint_red = pClass.tint.red();
    int tint_green = pClass.tint.green();
    int tint_blue = pClass.tint.blue();
    int tint_alpha = pClass.tint.alpha();

    for (int y = 0; y < h; ++y) {
        QRgb *ls = (QRgb *)img.scanLine(y);
        for (int x = 0; x < w; ++x) {
            QRgb l = ls[x];
            int r = qRed(l) * tint_red / 255;
            int g = qGreen(l) * tint_green / 255;
            int b = qBlue(l) * tint_blue / 255;
            int a = qAlpha(l) * tint_alpha / 255;
            ls[x] = qRgba(r, g, b, a);
        }
    }
}

KdmPixmap::PixmapStruct::PixmapClass &
KdmPixmap::getClass(ItemState sts)
{
    return
        (sts == Sprelight && pixmap.prelight.present) ?
            pixmap.prelight :
            (sts == Sactive && pixmap.active.present) ?
                pixmap.active :
                pixmap.normal;
}

void
KdmPixmap::slotAnimate(int sts)
{
    PixmapStruct::PixmapClass &pClass = getClass(ItemState(sts));
    pClass.readyPixmap = QPixmap();
    if (&pClass == &getCurClass())
        needUpdate();
}

void
KdmPixmap::statusChanged(bool descend)
{
    KdmItem::statusChanged(descend);
    if (!pixmap.active.present && !pixmap.prelight.present)
        return;
    if ((state == Sprelight && !pixmap.prelight.present) ||
        (state == Sactive && !pixmap.active.present))
        return;
    needUpdate();
}

//#include "kdmpixmap.moc"
