/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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.
 *
 */

#ifndef SCI_GRAPHICS_PLANE32_H
#define SCI_GRAPHICS_PLANE32_H

#include "common/array.h"
#include "common/rect.h"
#include "sci/engine/vm_types.h"
#include "sci/graphics/helpers.h"
#include "sci/graphics/lists32.h"
#include "sci/graphics/screen_item32.h"

namespace Sci {
enum PlaneType {
	kPlaneTypeColored            = 0,
	kPlaneTypePicture            = 1,
	kPlaneTypeTransparent        = 2,
	kPlaneTypeOpaque             = 3,
	kPlaneTypeTransparentPicture = 4
};

enum PlanePictureCodes {
	// NOTE: Any value at or below 65531 means the plane
	// is a kPlaneTypePicture.
	kPlanePic                   = 65531,
	kPlanePicTransparentPicture = 65532,
	kPlanePicOpaque             = 65533,
	kPlanePicTransparent        = 65534,
	kPlanePicColored            = 65535
};

#pragma mark -
#pragma mark RectList

typedef StablePointerArray<Common::Rect, 200> RectListBase;
class RectList : public RectListBase {
public:
	void add(const Common::Rect &rect) {
		RectListBase::add(new Common::Rect(rect));
	}
};

#pragma mark -
#pragma mark DrawList

struct DrawItem {
	/**
	 * The screen item to draw.
	 */
	ScreenItem *screenItem;

	/**
	 * The target rectangle of the draw operation.
	 */
	Common::Rect rect;

	inline bool operator<(const DrawItem &other) const {
		return *screenItem < *other.screenItem;
	}
};

typedef StablePointerArray<DrawItem, 250> DrawListBase;
class DrawList : public DrawListBase {
private:
	inline static bool sortHelper(const DrawItem *a, const DrawItem *b) {
		return *a < *b;
	}
public:
	void add(ScreenItem *screenItem, const Common::Rect &rect);
	inline void sort() {
		pack();
		Common::sort(begin(), end(), sortHelper);
	}
};

class PlaneList;

#pragma mark -
#pragma mark Plane

/**
 * A plane is a grouped layer of screen items.
 */
class Plane {
private:
	/**
	 * A serial used for planes that are generated inside
	 * the graphics engine, rather than the interpreter.
	 */
	static uint16 _nextObjectId;

	/**
	 * For planes that are used to render picture data, the
	 * resource ID of the picture to be displayed. This
	 * value may also be one of the special
	 * PlanePictureCodes, in which case the plane becomes a
	 * non-picture plane.
	 */
	GuiResourceId _pictureId;

	/**
	 * Whether or not the contents of picture planes should
	 * be drawn horizontally mirrored. Only applies to
	 * planes of type kPlaneTypePicture.
	 */
	bool _mirrored;

	/**
	 * Whether the picture ID for this plane has changed.
	 * This flag is set when the plane is created or updated
	 * from a VM object, and is cleared when the plane is
	 * synchronised to another plane (which calls
	 * changePic).
	 */
	bool _pictureChanged;

	/**
	 * Converts the dimensions of the game rect used by
	 * scripts to the dimensions of the plane rect used to
	 * render content to the screen. Coordinates with
	 * remainders are rounded up to the next whole pixel.
	 */
	void convertGameRectToPlaneRect();

	/**
	 * Sets the type of the plane according to its assigned
	 * picture resource ID.
	 */
	void setType();

public:
	/**
	 * The type of the plane.
	 */
	PlaneType _type;

	/**
	 * The color to use when erasing the plane. Only
	 * applies to planes of type kPlaneTypeColored.
	 */
	byte _back;

	/**
	 * Whether the priority of this plane has changed.
	 * This flag is set when the plane is updated from
	 * another plane and cleared when draw list calculation
	 * occurs.
	 */
	int _priorityChanged;

	/**
	 * A handle to the VM object corresponding to this
	 * plane. Some planes are generated purely within the
	 * graphics engine and have a numeric object value.
	 */
	reg_t _object;

	/**
	 * The rendering priority of the plane. Higher
	 * priorities are drawn above lower priorities.
	 */
	int16 _priority;

	/**
	 * Whether or not all screen items in this plane should
	 * be redrawn on the next frameout, instead of just
	 * the screen items marked as updated. This is set when
	 * visual changes to the plane itself are made that
	 * affect the rendering of the entire plane, and cleared
	 * once those changes are rendered by `redrawAll`.
	 */
	int _redrawAllCount;

	/**
	 * Flags indicating the state of the plane.
	 * - `created` is set when the plane is first created,
	 *   either from a VM object or from within the engine
	 *   itself
	 * - `updated` is set when the plane is updated from
	 *   another plane and the two planes' `planeRect`s do
	 *   not match
	 * - `deleted` is set when the plane is deleted by a
	 *   kernel call
	 * - `moved` is set when the plane has been moved or
	 *   resized
	 */
	int _created, _updated, _deleted, _moved;

	/**
	 * The vanishing point for the plane. Used when
	 * automatically calculating the correct scaling of the
	 * plane's screen items according to their position.
	 */
	Common::Point _vanishingPoint;

	/**
	 * The position & dimensions of the plane in screen
	 * coordinates. This rect is not clipped to the screen,
	 * so may include coordinates that are offscreen.
	 */
	Common::Rect _planeRect;

	/**
	 * The position & dimensions of the plane in game script
	 * coordinates.
	 */
	Common::Rect _gameRect;

	/**
	 * The position & dimensions of the plane in screen
	 * coordinates. This rect is clipped to the screen.
	 */
	Common::Rect _screenRect;

	/**
	 * The list of screen items grouped within this plane.
	 */
	ScreenItemList _screenItemList;

public:
	/**
	 * Initialises static Plane members.
	 */
	static void init();

	// NOTE: This constructor signature originally did not accept a
	// picture ID, but some calls to construct planes with this signature
	// immediately set the picture ID and then called setType again, so
	// it made more sense to just make the picture ID a parameter instead.
	Plane(const Common::Rect &gameRect, PlanePictureCodes pictureId = kPlanePicColored);

	Plane(const reg_t object);

	Plane(const Plane &other);

	void operator=(const Plane &other);

	inline bool operator<(const Plane &other) const {
		if (_priority < other._priority) {
			return true;
		}

		if (_priority == other._priority) {
			return _object < other._object;
		}

		return false;
	}

	/**
	 * Clips the screen rect of this plane to fit within the
	 * given screen rect.
	 */
	inline void clipScreenRect(const Common::Rect &screenRect) {
		// LSL6 hires creates planes with invalid rects; SSCI does not
		// care about this, but `Common::Rect::clip` does, so we need to
		// check whether or not the rect is actually valid before clipping
		// and only clip valid rects
		if (_screenRect.isValidRect() && _screenRect.intersects(screenRect)) {
			_screenRect.clip(screenRect);
		} else {
			_screenRect.left = 0;
			_screenRect.top = 0;
			_screenRect.right = 0;
			_screenRect.bottom = 0;
		}
	}

	void printDebugInfo(Console *con) const;

	/**
	 * Compares the properties of the current plane against
	 * the properties of the `other` plane (which is the
	 * corresponding plane from the visible plane list) to
	 * discover which properties have been changed on this
	 * plane by a call to `update(reg_t)`.
	 *
	 * @note This method was originally called UpdatePlane
	 * in SCI engine.
	 */
	void sync(const Plane *other, const Common::Rect &screenRect);

	/**
	 * Updates the plane to match the state of the plane
	 * object from the virtual machine.
	 *
	 * @note This method was originally called UpdatePlane
	 * in SCI engine.
	 */
	void update(const reg_t object);

	/**
	 * Modifies the position of all non-pic screen items
	 * by the given delta. If `scrollPics` is true, pic
	 * items are also repositioned.
	 */
	void scrollScreenItems(const int16 deltaX, const int16 deltaY, const bool scrollPics);

#pragma mark -
#pragma mark Plane - Pic
private:
	/**
	 * Adds all cels from the specified picture resource to
	 * the plane as screen items. If a position is provided,
	 * the screen items will be given that position;
	 * otherwise, the default relative positions for each
	 * cel will be taken from the picture resource data.
	 */
	inline void addPicInternal(const GuiResourceId pictureId, const Common::Point *position, const bool mirrorX);

	/**
	 * Marks all screen items to be deleted that are within
	 * this plane and match the given picture ID.
	 */
	void deletePic(const GuiResourceId pictureId);

	/**
	 * Marks all screen items to be deleted that are within
	 * this plane and are picture cels.
	 */
	void deleteAllPics();

public:
	/**
	 * Marks all existing screen items matching the current
	 * picture to be deleted, then adds all cels from the
	 * new picture resource to the plane at the given
	 * position.
	 */
	GuiResourceId addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX, const bool deleteDuplicate = true);

	/**
	 * If the plane is a picture plane, re-adds all cels
	 * from its picture resource to the plane. Otherwise,
	 * just clears the _pictureChanged flag.
	 */
	void changePic();

	/**
	 * Marks all screen items to be deleted that are within
	 * this plane and match the given picture ID, then sets
	 * the picture ID of the plane to the new picture ID
	 * without adding any screen items.
	 */
	void deletePic(const GuiResourceId oldPictureId, const GuiResourceId newPictureId);

#pragma mark -
#pragma mark Plane - Rendering
private:
	/**
	 * Splits all rects in the given draw list at the edges
	 * of all higher-priority, non-transparent, intersecting
	 * planes.
	 */
	void breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const;

	/**
	 * Splits all rects in the given erase list at the
	 * edges of higher-priority, non-transparent,
	 * intersecting planes.
	 */
	void breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const;

	/**
	 * Adds the screen item at `index` into `drawList`,
	 * ensuring it is only drawn within the bounds of
	 * `rect`. If an existing draw list entry exists
	 * for this screen item, it will be modified.
	 * Otherwise, a new entry will be added.
	 */
	void mergeToDrawList(const DrawList::size_type index, const Common::Rect &rect, DrawList &drawList) const;

	/**
	 * Merges `rect` with an existing rect in `eraseList`,
	 * if possible. Otherwise, adds the rect as a new entry
	 * to `eraseList`.
	 */
	void mergeToRectList(const Common::Rect &rect, RectList &eraseList) const;

public:
	/**
	 * Calculates the location and dimensions of dirty rects
	 * of the screen items in this plane and adds them to
	 * the given draw and erase lists, and synchronises this
	 * plane's list of screen items to the given visible
	 * plane.
	 */
	void calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList);

	/**
	 * Synchronises changes to screen items from the current
	 * plane to the visible plane and deletes screen items
	 * from the current plane that have been marked as
	 * deleted. If `forceUpdate` is true, all screen items
	 * on the visible plane will be updated, even if they
	 * are not marked as having changed.
	 */
	void decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate);

	/**
	 * This method is called from the highest priority plane
	 * to the lowest priority plane.
	 *
	 * Adds screen items from this plane to the draw list
	 * that must be redrawn because they intersect entries
	 * in the `higherEraseList`.
	 *
	 * If this plane is opaque, all intersecting erase rects
	 * in `lowerEraseList` are removed, as they would be
	 * completely overwritten by the contents of this plane.
	 *
	 * If this plane is transparent, erase rects from the
	 * `lowerEraseList` are added to the erase list for this
	 * plane, so that lower planes.
	 *
	 * @param drawList The draw list for this plane.
	 * @param eraseList The erase list for this plane.
	 * @param higherEraseList The erase list for a plane
	 * above this plane.
	 */
	void filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &higherEraseList) const;

	/**
	 * This method is called from the lowest priority plane
	 * to the highest priority plane.
	 *
	 * Adds screen items from this plane to the draw list
	 * that must be drawn because the lower plane is being
	 * redrawn and potentially transparent screen items
	 * from this plane would draw over the lower priority
	 * plane's screen items.
	 *
	 * This method applies only to transparent planes.
	 *
	 * @param drawList The draw list for this plane.
	 * @param eraseList The erase list for a plane below
	 * this plane.
	 */
	void filterUpEraseRects(DrawList &drawList, const RectList &lowerEraseList) const;

	/**
	 * This method is called from the lowest priority plane
	 * to the highest priority plane.
	 *
	 * Adds screen items from this plane to the draw list
	 * that must be drawn because the lower plane is being
	 * redrawn and potentially transparent screen items
	 * from this plane would draw over the lower priority
	 * plane's screen items.
	 *
	 * This method applies only to transparent planes.
	 *
	 * @param drawList The draw list for this plane.
	 * @param lowerDrawList The draw list for a plane below
	 * this plane.
	 */
	void filterUpDrawRects(DrawList &drawList, const DrawList &lowerDrawList) const;

	/**
	 * Updates all of the plane's non-deleted screen items
	 * and adds them to the given draw and erase lists.
	 */
	void redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList);

	void remapMarkRedraw();
};

#pragma mark -
#pragma mark PlaneList

typedef Common::Array<Plane *> PlaneListBase;
class PlaneList : public PlaneListBase {
private:
	inline static bool sortHelper(const Plane *a, const Plane *b) {
		return *a < *b;
	}

	using PlaneListBase::push_back;

public:
	// A method for finding the index of a plane inside a
	// PlaneList is used because entries in the main plane
	// list and visible plane list of GfxFrameout are
	// synchronised by index
	int findIndexByObject(const reg_t object) const;
	Plane *findByObject(const reg_t object) const;

	/**
	 * Gets the priority of the top plane in the plane list.
	 */
	int16 getTopPlanePriority() const;

	/**
	 * Gets the priority of the top plane in the plane list
	 * created by a game script.
	 */
	int16 getTopSciPlanePriority() const;

	void add(Plane *plane);
	void clear();
	iterator erase(iterator it);
	void erase(Plane *plane);
	inline void sort() {
		Common::sort(begin(), end(), sortHelper);
	}
	void remove_at(size_type index);
};

} // End of namespace Sci

#endif