#pragma once

/*
 *   License:	LGPL - See file COPYING.LIB for details.
 *   Author:	Stefan Hundhammer <sh@suse.de>
 *              Joshua Hodosh <kdirstat@grumpypenguin.org>
 */

#include <QGraphicsRectItem>

namespace KDirStat {
class KFileInfo;
class KTreemapView;

enum KOrientation { KTreemapHorizontal, KTreemapVertical, KTreemapAuto };

/**
 * Helper class for cushioned treemaps: This class holds the polynome
 * parameters for the cushion surface. The height of each point of such a
 * surface is defined as:
 *
 *     z(x, y) = a*x^2 + b*y^2 + c*x + d*y
 * or
 *     z(x, y) = xx2*x^2 + yy2*y^2 + xx1*x + yy1*y
 *
 * to better keep track of which coefficient belongs where.
 **/
class KCushionSurface {
public:
  /**
   * Constructor. All polynome coefficients are set to 0.
   **/
  KCushionSurface();

  /**
   * Adds a ridge of the specified height in dimension 'dim' within
   * rectangle 'rect' to this surface. It's real voodo magic.
   *
   * Just kidding - read the paper about "cushion treemaps" by Jarke
   * J. van Wiik and Huub van de Wetering from the TU Eindhoven, NL for
   * more details.
   *
   * If you don't want to get all that involved: The coefficients are
   * changed in some way.
   **/
  void addRidge(KOrientation dim, double height, const QRectF &rect);

  /**
   * Set the cushion's height.
   **/
  void setHeight(double newHeight) { _height = newHeight; }

  /**
   * Returns the cushion's height.
   **/
  double height() const { return _height; }

  /**
   * Returns the polynomal coefficient of the second order for X direction.
   **/
  double xx2() const { return _xx2; }

  /**
   * Returns the polynomal coefficient of the first order for X direction.
   **/
  double xx1() const { return _xx1; }

  /**
   * Returns the polynomal coefficient of the second order for Y direction.
   **/
  double yy2() const { return _yy2; }

  /**
   * Returns the polynomal coefficient of the first order for Y direction.
   **/
  double yy1() const { return _yy1; }

protected:
  /**
   * Calculate a new square polynomal coefficient for adding a ridge of
   * specified height between x1 and x2.
   **/
  double squareRidge(double squareCoefficient, double height, int x1, int x2);

  /**
   * Calculate a new linear polynomal coefficient for adding a ridge of
   * specified height between x1 and x2.
   **/
  double linearRidge(double linearCoefficient, double height, int x1, int x2);

  // Data members

  double _xx2, _xx1;
  double _yy2, _yy1;
  double _height;

}; // class KCushionSurface

/**
 * This is the basic building block of a treemap view: One single tile of a
 * treemap. If it corresponds to a leaf in the tree, it will be visible as
 * one tile (one rectangle) of the treemap. If it has children, it will be
 * subdivided again.
 *
 * @short Basic building block of a treemap
 **/
class KTreemapTile : public QGraphicsRectItem {
public:
  /**
   * Constructor: Create a treemap tile from 'fileinfo' that fits into a
   * rectangle 'rect' inside 'parent'.
   *
   * 'orientation' is the direction for further subdivision. 'Auto'
   * selects the wider direction inside 'rect'.
   **/
  KTreemapTile(KTreemapView *parentView, KTreemapTile *parentTile,
               KFileInfo *orig, const QRectF &rect,
               KOrientation orientation = KTreemapAuto);

protected:
  /**
   * Alternate constructor: Like the above, but explicitly specify a
   * cushion surface rather than using the parent's.
   **/
  KTreemapTile(KTreemapView *parentView, KTreemapTile *parentTile,
               KFileInfo *orig, const QRect &rect,
               const KCushionSurface &cushionSurface,
               KOrientation orientation = KTreemapAuto);

public:
  /**
   * Destructor.
   **/
  virtual ~KTreemapTile();

  /**
   * Returns the original @ref KFileInfo item that corresponds to this
   * treemap tile.
   **/
  KFileInfo *orig() const { return _orig; }

  /**
   * Returns the parent @ref KTreemapView.
   **/
  KTreemapView *parentView() const { return _parentView; }

  /**
   * Returns the parent @ref KTreemapTile or 0 if there is none.
   **/
  KTreemapTile *parentTile() const { return _parentTile; }

  /**
   * Returns this tile's cushion surface parameters.
   **/
  KCushionSurface &cushionSurface() { return _cushionSurface; }

protected:
  /**
   * Create children (sub-tiles) of this tile.
   **/
  void createChildren(const QRectF &rect, KOrientation orientation);

  /**
   * Create children (sub-tiles) using the simple treemap algorithm:
   * Alternate between horizontal and vertical subdivision in each
   * level. Each child will get the entire height or width, respectively,
   * of the specified rectangle. This algorithm is very fast, but often
   * results in very thin, elongated tiles.
   **/
  void createChildrenSimple(const QRectF &rect, KOrientation orientation);

  /**
   * Create children using the "squarified treemaps" algorithm as
   * described by Mark Bruls, Kees Huizing, and Jarke J. van Wijk of the
   * TU Eindhoven, NL.
   *
   * This algorithm is not quite so simple and involves more expensive
   * operations, e.g., sorting the children of each node by size first,
   * try some variations of the layout and maybe backtrack to the
   * previous attempt. But it results in tiles that are much more
   * square-like, i.e. have more reasonable width-to-height ratios. It is
   * very much less likely to get thin, elongated tiles that are hard to
   * point at and even harder to compare visually against each other.
   *
   * This implementation includes some improvements to that basic
   * algorithm. For example, children below a certain size are
   * disregarded completely since they will not get an adequate visual
   * representation anyway (it would be way too small). They are
   * summarized in some kind of 'misc stuff' area in the parent treemap
   * tile - in fact, part of the parent directory's tile can be "seen
   * through".
   *
   * In short, a lot of small children that don't have any useful effect
   * for the user in finding wasted disk space are omitted from handling
   * and, most important, don't need to be sorted by size (which has a
   * cost of O(n*ln(n)) in the best case, so reducing n helps a lot).
   **/
  void createSquarifiedChildren(const QRectF &rect);

  /**
   * Squarify as many children as possible: Try to squeeze members
   * referred to by 'it' into 'rect' until the aspect ratio doesn't get
   * better any more. Returns a list of children that should be laid out
   * in 'rect'. Moves 'it' until there is no more improvement or 'it'
   * runs out of items.
   *
   * 'scale' is the scaling factor between file sizes and pixels.
   **/
  void squarify(const QRectF &rect, double scale,
                std::vector<KFileInfo*>::iterator &,
                std::vector<KFileInfo*>::iterator end,
                std::vector<KFileInfo*> & output);

  /**
   * Lay out all members of 'row' within 'rect' along its longer side.
   * Returns the new rectangle with the layouted area subtracted.
   **/
  QRectF layoutRow(const QRectF &rect, double scale, std::vector<KFileInfo*> &row);

  /**
   * Draw the tile.
   *
   * Reimplemented from QCanvasRectangle.
   **/
  void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
                     QWidget *widget = 0) override;

  /**
   * Render a cushion as described in "cushioned treemaps" by Jarke
   * J. van Wijk and Huub van de Wetering  of the TU Eindhoven, NL.
   **/
  QPixmap renderCushion();

  /**
   * Check if the contrast of the specified image is sufficient to
   * visually distinguish an outline at the right and bottom borders
   * and add a grey line there, if necessary.
   **/
  void ensureContrast(QImage &image);

  /**
   * Returns a color that gives a reasonable contrast to 'col': Lighter
   * if 'col' is dark, darker if 'col' is light.
   **/
  QRgb contrastingColor(QRgb col);

private:
  /**
   * Initialization common to all constructors.
   **/
  void init();

protected:
  // Data members

  KTreemapView *_parentView;
  KTreemapTile *_parentTile;
  KFileInfo *_orig;
  KCushionSurface _cushionSurface;
  QPixmap _cushion;

}; // class KTreemapTile

} // namespace KDirStat

inline QDebug &operator<<(QDebug &stream, const QRect &rect) {
  stream << "(" << rect.width() << "x" << rect.height() << "+" << rect.x()
         << "+" << rect.y() << ")";

  return stream;
}

