/*******************************************************************************
 * Copyright (c) 2015 - 2017
 * <p>
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * <p>
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 * <p>
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *******************************************************************************/
package jsettlers.common.map.shapes;

import jsettlers.common.movable.EDirection;
import jsettlers.common.position.ShortPoint2D;
import jsettlers.common.utils.coordinates.CoordinateStream;
import jsettlers.common.utils.coordinates.IBooleanCoordinateFunction;

import java.io.Serializable;
import java.util.Iterator;

/**
 * Represents a hexagon on the grid.
 *
 * @author Andreas Eberle
 */
public final class HexGridArea implements IMapArea {

	private static final int MAX_DIRECTIONS_IDX = EDirection.NUMBER_OF_DIRECTIONS - 1;
	private static final byte[] DIRECTION_INCREASE_X = { EDirection.SOUTH_EAST.gridDeltaX, EDirection.SOUTH_WEST.gridDeltaX,
			EDirection.WEST.gridDeltaX, EDirection.NORTH_WEST.gridDeltaX, EDirection.NORTH_EAST.gridDeltaX, EDirection.EAST.gridDeltaX };
	private static final byte[] DIRECTION_INCREASE_Y = { EDirection.SOUTH_EAST.gridDeltaY, EDirection.SOUTH_WEST.gridDeltaY,
			EDirection.WEST.gridDeltaY, EDirection.NORTH_WEST.gridDeltaY, EDirection.NORTH_EAST.gridDeltaY, EDirection.EAST.gridDeltaY };

	private static final long serialVersionUID = -2218632675269689379L;
	final short cX;
	final short cY;
	final short startRadius;
	final short maxRadius;

	/**
	 * Hexagon area from including {@link #startRadius} to including {@link #maxRadius}
	 *
	 * @param centerX
	 *            center x
	 * @param centerY
	 *            center y
	 * @param startRadius
	 *            inclusive inner radius
	 * @param maxRadius
	 *            inclusive outer radius
	 */
	public HexGridArea(int centerX, int centerY, int startRadius, int maxRadius) {
		this.cX = (short) centerX;
		this.cY = (short) centerY;
		this.startRadius = (short) startRadius;
		this.maxRadius = (short) maxRadius;
	}

	public HexGridArea(int centerX, int centerY, int radius) {
		this(centerX, centerY, 0, radius);
	}

	public HexGridArea(ShortPoint2D center, int radius) {
		this(center.x, center.y, radius);
	}

	@Override
	public boolean contains(ShortPoint2D position) {
		throw new UnsupportedOperationException("not implemented yet");
	}

	@Override
	public boolean contains(int x, int y) {
		throw new UnsupportedOperationException("not implemented yet");
	}

	@Override
	public HexGridAreaIterator iterator() {
		return new HexGridAreaIterator(this);
	}

	public static final class HexGridAreaIterator implements Iterator<ShortPoint2D>, Serializable {
		private static final long serialVersionUID = -8760653162789299782L;

		private final HexGridArea hexGridArea;
		private short radius;
		private short x;
		private short y;
		private int direction;
		private short length = 1;

		public HexGridAreaIterator(HexGridArea hexGridArea) {
			this.hexGridArea = hexGridArea;
			radius = hexGridArea.startRadius;

			x = hexGridArea.cX;
			y = (short) (hexGridArea.cY - radius); // radius * NORTH_EAST

			if (hexGridArea.startRadius == 0) {
				direction = EDirection.NUMBER_OF_DIRECTIONS;
			} else {
				direction = 0;
				x += EDirection.SOUTH_EAST.gridDeltaX;
				y += EDirection.SOUTH_EAST.gridDeltaY;
			}
		}

		@Override
		public boolean hasNext() {
			return radius <= hexGridArea.maxRadius;
		}

		@Override
		public ShortPoint2D next() {
			ShortPoint2D result = new ShortPoint2D(x, y);

			if (length >= radius) {
				length = 0;
				direction++;

				if (direction >= EDirection.NUMBER_OF_DIRECTIONS) {
					x += DIRECTION_INCREASE_X[MAX_DIRECTIONS_IDX];
					y += DIRECTION_INCREASE_Y[MAX_DIRECTIONS_IDX];

					direction = 0;
					length = 1;
					radius++;

					return result;
				}
			}
			length++;

			x += DIRECTION_INCREASE_X[direction];
			y += DIRECTION_INCREASE_Y[direction];

			return result;
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException("not implemented!");
		}
	}

	@Override
	public CoordinateStream stream() {
		return stream(cX, cY, startRadius, maxRadius);
	}

	public static CoordinateStream stream(int cX, int cY, int startRadius, int maxRadius) {
		return new CoordinateStream() {
			@Override
			public boolean iterate(IBooleanCoordinateFunction function) {
				if (startRadius == 0 && !function.apply(cX, cY)) {
					return false;
				}

				int x = cX;
				int y = cY - startRadius; // radius * NORTH_EAST

				for (int radius = startRadius; radius <= maxRadius; radius++) {
					for (int direction = 0; direction < EDirection.NUMBER_OF_DIRECTIONS; direction++) {
						for (int step = 0; step < radius; step++) {
							x += DIRECTION_INCREASE_X[direction];
							y += DIRECTION_INCREASE_Y[direction];

							if (!function.apply(x, y)) {
								return false;
							}
						}
					}
					y--; // go to next radius / go one NORTH_EAST
				}

				return true;
			}
		};

	}

	public CoordinateStream streamBorder() {
		return streamBorder(cX, cY, maxRadius);
	}

	public static CoordinateStream streamBorder(short centerX, short centerY, int radius) {
		return stream(centerX, centerY, radius, radius);
	}

	public static CoordinateStream streamBorder(ShortPoint2D center, int radius) {
		return streamBorder(center.x, center.y, radius);
	}
}
