using System;
using System.Collections.Generic;
using System.Drawing;

namespace SpriteWave
{
	public class Collage : IPalette
	{
		private const int defCols = 16;

		private bool _readOnly;
		public bool ReadOnly { get { return _readOnly; } }

		private List<Tile> _tiles;
		public int nTiles { get { return _tiles.Count; } }

		private FileFormat _fmt;
		public FileFormat Format { get { return _fmt; } }

		private Tile _templ;
		public int TileW { get { return _templ.Width; } }
		public int TileH { get { return _templ.Height; } }

		private int _nCols;
		public int Columns { get { return _nCols; } }
		public int Rows
		{
			get
			{
				int rows = _tiles.Count / _nCols;
				if (_tiles.Count % _nCols != 0)
					rows++;

				return rows;
			}
		}

		public int Width { get { return Columns * TileW; } }
		public int Height { get { return Rows * TileH; } }

		private Bitmap _canvas;
		public Bitmap Bitmap { get { return _canvas; } }

		private Pen _gridPen;
		public Pen GridPen { get { return _gridPen; } }

		private readonly ColorTable _tbl;
		//public uint HighestColor { get { return _tbl.LastColor; } }

		private uint[] _nativeClrs;
		public uint[] NativeColors { get { return _nativeClrs; } }
		public int ColorCount { get { return _nativeClrs.Length; } }

		public uint this[int idx]
		{
			get { return Utils.RedBlueSwap(_tbl.NativeToRGBA(_nativeClrs[idx])); }
			set {
				_nativeClrs[idx] = _tbl.RGBAToNative(Utils.RedBlueSwap(value));
				UpdateGridPen();
			}
		}

		public uint[] GetList()
		{
			int len = _nativeClrs.Length;
			var clrs = new uint[len];
			for (int i = 0; i < len; i++)
				clrs[i] = this[i];

			return clrs;
		}

		public Collage(FileFormat fmt, int nCols = defCols, bool readOnly = true)
		{
			_fmt = fmt;
			_templ = _fmt.NewTile();

			_tiles = new List<Tile>();
			_nCols = nCols;
			_readOnly = readOnly;

			_tbl = _fmt.ColorTable;
			uint[] defs = _tbl.Defaults;
			_nativeClrs = new uint[defs.Length];
			Buffer.BlockCopy(defs, 0, _nativeClrs, 0, defs.Length * Utils.cLen);

			UpdateGridPen();
		}

		public byte[] BGRAPalette()
		{
			var clrs = new byte[_nativeClrs.Length * Utils.cLen];
			int idx = 0;
			for (int i = 0; i < _nativeClrs.Length; i++)
			{
				uint c = this[i];
				clrs[idx++] = (byte)(c >> 24);
				clrs[idx++] = (byte)((c >> 16) & 0xff);
				clrs[idx++] = (byte)((c >> 8) & 0xff);
				clrs[idx++] = (byte)(c & 0xff);
			}

			return clrs;
		}

		public void UpdateGridPen()
		{
			double red = 0, green = 0, blue = 0, alpha = 0;
			for (int i = 0; i < _nativeClrs.Length; i++)
			{
				uint clr = this[i];
				blue += (double)((clr >> 24) & 0xff);
				green += (double)((clr >> 16) & 0xff);
				red += (double)((clr >> 8) & 0xff);
				alpha += (double)(clr & 0xff);
			}

			double n = (double)_nativeClrs.Length;
			uint rgba =
				((uint)(red / n) & 0xff) << 24 |
				((uint)(green / n) & 0xff) << 16 |
				((uint)(blue / n) & 0xff) << 8 |
				(uint)(alpha / n) & 0xff
			;

			_gridPen = new Pen(Utils.FromRGB(rgba ^ 0xFFFFFF00));
		}

		public void AddTile(Tile t)
		{
			_tiles.Add(t);
		}

		public Tile TileAt(int idx)
		{
			return _tiles[idx];
		}
		public Tile TileAt(Position p)
		{
			return TileAt(p.row * _nCols + p.col);
		}

		public void SetTile(int idx, Tile t)
		{
			if (t == null)
				throw new ArgumentNullException();

			_tiles[idx] = t;
		}
		public void SetTile(Position p, Tile t)
		{
			SetTile(p.row * _nCols + p.col, t);
		}

		public void AddBlankTiles(int count)
		{
			for (int i = 0; i < count; i++)
				_tiles.Add(_fmt.NewTile());
		}

		// Each pixel is stored as four bytes, ordered B, G, R, then A
		public Bitmap RenderTile(Tile t)
		{
			return t.ToBitmap(BGRAPalette());
		}

		public bool LoadTiles(byte[] file, int offset)
		{
			if (file == null || offset < 0)
				return false;

			int idx = 0;
			while (offset < file.Length)
			{
				if (idx == _tiles.Count)
					_tiles.Add(_fmt.NewTile());

				int off = _tiles[idx].Import(file, offset);

				idx++;
				if (off <= offset)
					break;

				offset = off;
			}

			// 'idx' now represents the last tile index + 1, aka the true number of tiles
			if (_tiles.Count > idx)
				_tiles.RemoveRange(idx, _tiles.Count - idx);

			return true;
		}

		public void Render()
		{
			const int cLen = 4;

			int rows = Rows;
			int height = rows * TileH;
			int width = _nCols * TileW;
			byte[] collage = new byte[height * width * cLen];

			byte[] palBGRA = BGRAPalette();
			int idx = 0;
			foreach (Tile t in _tiles)
			{
				int tCol = idx % _nCols;
				int tRow = idx / _nCols;

				int imgCol = tCol * TileW;
				int imgRow = tRow * TileH;

				int offset = (imgRow * _nCols * TileW) + imgCol;
				offset *= cLen;

				// Each pixel is stored as four bytes, ordered B, G, R, then A
				t.ApplyTo(collage, offset, width, palBGRA);
				idx++;
			}

			_canvas = Utils.BitmapFrom(collage, width, height);
		}

		public int InsertColumn(int idx)
		{
			if (idx < 0 || idx > _nCols)
				return 0;

			_nCols++;
			for (int i = 0; i < Rows; i++)
				_tiles.Insert(i * _nCols + idx, _fmt.NewTile());

			return 1;
		}

		public int InsertRow(int idx)
		{
			int rows = Rows;
			if (idx < 0 || idx > rows)
				return 0;

			for (int i = 0; i < _nCols; i++)
				_tiles.Insert(idx * _nCols + i, _fmt.NewTile());

			return 1;
		}

		public int DeleteColumn(int idx)
		{
			if (idx < 0 || idx >= _nCols || _nCols <= 1)
				return 0;

			for (int i = Rows - 1; i >= 0; i--)
				_tiles.RemoveAt(i * _nCols + idx);

			_nCols--;
			return -1;
		}

		public int DeleteRow(int idx)
		{
			int rows = Rows;
			if (idx < 0 || idx >= rows || rows <= 1)
				return 0;

			for (int i = _nCols - 1; i >= 0; i--)
				_tiles.RemoveAt(idx * _nCols + i);

			return -1;
		}
	}
}