// {LICENSE}
/*
 * Copyright 2013-2015 HeroesGrave and other Spade developers.
 * 
 * This file is part of Spade
 * 
 * Spade 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 3 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, see <http://www.gnu.org/licenses/>
 */

package heroesgrave.spade.image.change.edit;

import heroesgrave.spade.image.RawImage;
import heroesgrave.spade.image.change.IEditChange;
import heroesgrave.spade.image.change.SingleChange;
import heroesgrave.spade.io.HistoryIO;
import heroesgrave.spade.io.Serialised;

import java.awt.Point;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;

public class PathChange implements IEditChange
{
	public static abstract class IPathChange extends SingleChange
	{
		public abstract void point(RawImage image, int x, int y, int colour);
		
		public abstract void line(RawImage image, int x1, int y1, int x2, int y2, int colour);
		
		public static void lineToPoints(IPathChange path, RawImage image, int x1, int y1, int x2, int y2, int colour)
		{
			final int dx = Math.abs(x2 - x1);
			final int dy = Math.abs(y2 - y1);
			final int sx = (x1 < x2) ? 1 : -1;
			final int sy = (y1 < y2) ? 1 : -1;
			int err = dx - dy;
			do
			{
				path.point(image, x1, y1, colour);
				final int e2 = 2 * err;
				if(e2 > -dy)
				{
					err = err - dy;
					x1 = x1 + sx;
				}
				if(e2 < dx)
				{
					err = err + dx;
					y1 = y1 + sy;
				}
			}
			while(!(x1 == x2 && y1 == y2));
			path.point(image, x2, y2, colour);
		}
	}
	
	protected IPathChange change;
	protected ArrayList<Point> points = new ArrayList<Point>();
	protected int colour;
	
	public PathChange(short x, short y, int colour, IPathChange change)
	{
		this(new Point(x, y), colour, change);
	}
	
	public PathChange(Point p, int colour, IPathChange change)
	{
		this.colour = colour;
		points.add(p);
		this.change = change;
	}
	
	public boolean moveTo(short x, short y)
	{
		return this.moveTo(new Point(x, y));
	}
	
	public boolean moveTo(Point p)
	{
		if(points.get(points.size() - 1).equals(p))
			return false;
		points.add(p);
		return true;
	}
	
	@Override
	public Serial encode()
	{
		short[] data = new short[points.size() * 2];
		int i = 0;
		for(Point p : points)
		{
			data[i++] = (short) p.x;
			data[i++] = (short) p.y;
		}
		return new Serial(data, colour, change.encode());
	}
	
	@Override
	public void apply(RawImage image)
	{
		if(points.size() == 1)
		{
			Point p = points.get(0);
			change.point(image, p.x, p.y, colour);
		}
		else
		{
			// Fill in lines first
			for(int i = 1; i < points.size(); i++)
			{
				Point p1 = points.get(i - 1);
				Point p2 = points.get(i);
				
				change.line(image, p1.x, p1.y, p2.x, p2.y, colour);
			}
			
			// Points after
			for(Point p : points)
			{
				change.point(image, p.x, p.y, colour);
			}
		}
	}
	
	public static class Serial implements Serialised
	{
		private short[] points;
		private int colour;
		private Serialised change;
		
		public Serial()
		{
			
		}
		
		public Serial(short[] data, int colour, Serialised change)
		{
			this.colour = colour;
			this.points = data;
			this.change = change;
		}
		
		@Override
		public PathChange decode()
		{
			PathChange change = new PathChange(points[0], points[1], colour, (IPathChange) this.change.decode());
			for(int i = 2; i < points.length; i += 2)
				change.moveTo(points[i], points[i + 1]);
			return change;
		}
		
		@Override
		public void write(DataOutputStream out) throws IOException
		{
			out.writeByte(HistoryIO.getChangeID(change.getClass()));
			change.write(out);
			out.writeInt(colour);
			out.writeInt(points.length);
			for(short s : points)
				out.writeShort(s);
		}
		
		@Override
		public void read(DataInputStream in) throws IOException
		{
			change = HistoryIO.create(in.readByte());
			change.read(in);
			colour = in.readInt();
			points = new short[in.readInt()];
			for(int i = 0; i < points.length; i++)
				points[i] = in.readShort();
		}
	}
}