package org.vostan.sokoban.play;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.v4.view.ViewCompat;
import android.view.View;
import static java.lang.Math.*;

import org.vostan.sokoban.Puzzle;
import static org.vostan.sokoban.Puzzle.*;
import org.vostan.sokoban.R;

/**
 */
public class PuzzleView extends View
{
	protected Puzzle puzzle;

	private RectF screen = new RectF(0,0,0,0);
	private RectF viewport = new RectF();
	private RectF board = new RectF();

	private Matrix col2scr = new Matrix();
	private Matrix scr2col = new Matrix();
    private Bitmap[] tile = new Bitmap[Puzzle.getSymCount()];
    private Point tile_size = new Point(0,0);
	private int rotated;
	
    private final Paint mPaint = new Paint();
    private Paint mSelectPaint = new Paint();
    private Rect tile_src = new Rect();
    private RectF tile_dest = new RectF();

    public PuzzleView(Context context)
	{
        super(context);
        setFocusable(true);
		
//
//        Resources r = this.getContext().getResources();

//        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TileView);
//
//        mTileSize = a.getInt(R.styleable.TileView_tileSize, 12);
//        
//        a.recycle();
    }
	
	public void setPuzzle( Puzzle p )
	{
		puzzle = p;
		board.set(0,0,puzzle.getColumnCount(),puzzle.getRowCount());
		viewport.set(board);
		tile_size.set(0,0);
		calcTransforms();
		initTiles();
	}

	public Puzzle getPuzzle()
	{
		return puzzle;
	}

	@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
	{
		screen.set(0, 0, w, h);
		tile_size.set(0,0);
		calcTransforms();
		initTiles();
    }


	public void scaleViewport( float focusX, float focusY, float scale )
	{
		float left = scale*(focusX - screen.left);
		float right = scale*(screen.right - focusX);
		float top = scale*(focusY - screen.top);
		float bottom = scale*(screen.bottom - focusY);
		viewport.set( focusX-left, focusY-top, focusX+right, focusY+bottom );
		scr2col.mapRect(viewport);
		constrainViewport();
		calcTransforms();
		ViewCompat.postInvalidateOnAnimation(this);
	}

	public void scaleViewportDone()
	{
		//initTiles();
		ViewCompat.postInvalidateOnAnimation(this);
	}

	public void scrollViewport( float distanceX, float distanceY )
	{
		viewport.set( screen.left+distanceX,
						screen.top+distanceY,
						screen.right+distanceX,
						screen.bottom+distanceY );
		scr2col.mapRect(viewport);
		constrainViewport();
		calcTransforms();
		ViewCompat.postInvalidateOnAnimation(this);
	}

	private void constrainViewport()
	{
		if ( !board.contains(viewport) )
		{
			if ( board.width() < viewport.width() )
			{
				viewport.left = board.left;
				viewport.right = board.right;
			}
			if ( viewport.left < board.left )
			{
				viewport.right += board.left - viewport.left;
				viewport.left = board.left;
			}
			if ( board.right < viewport.right )
			{
				viewport.left -= viewport.right - board.right;
				viewport.right = board.right;
			}
			
			if ( board.height() < viewport.height() )
			{
				viewport.top = board.top;
				viewport.bottom = board.bottom;
			}
			if ( viewport.top < board.top )
			{
				viewport.bottom += board.top - viewport.top;
				viewport.top = board.top;
			}
			if ( viewport.bottom > board.bottom )
			{
				viewport.top -= viewport.bottom - board.bottom;
				viewport.bottom = board.bottom;
			}
		}
		if ( viewport.width() < 4 )
		{
			float center = (viewport.left + viewport.right)/2;
			viewport.left = center-2;
			viewport.right = center+2;
		}
		if ( viewport.height() < 4 )
		{
			float center = (viewport.top + viewport.bottom)/2;
			viewport.top = center-2;
			viewport.bottom = center+2;
		}
	}

	/**
	 * Enlarge viewport to meet aspect ratio and calculate transforms.
	 */
    private void calcTransforms()
	{
		//
		//	Get the sizes of screen. If it is zero then simplly do nothing.
		//	Otherwise, make sure that the width is bigger the height.
		//
		double sw = (double)screen.width();
		double sh = (double)screen.height();
		if ( sw <= 0 || sh <= 0 || puzzle == null )
			return;
		else if ( sw < sh )
		{
			double t = sw;
			sw = sh;
			sh = t;
			rotated = 90;
		}
		//
		//	Enlarge the view to have the same aspect ratio as the screen.
		//
		double wscale = sw/(double)viewport.width();
		double hscale = sh/(double)viewport.height();
//		double scale = floor(min(wscale,hscale));
		double scale = min(wscale,hscale);
		float enlarge_vert = (float)(sh / scale - viewport.height())/2;
		float enlarge_horiz = (float)(sw / scale - viewport.width())/2;
		viewport.top -= enlarge_vert;
		viewport.bottom += enlarge_vert;
		viewport.left -= enlarge_horiz;
		viewport.right += enlarge_horiz;
		//
		//	Calculate the tile sizes which is the scale.
		//
		tile_size.set((int)scale,(int)scale);
		//
		//	Set transformations from and to screen.
		//
		col2scr.reset();
		col2scr.postTranslate(-viewport.left, -viewport.top);
		col2scr.postScale((float)scale,(float)scale);
		col2scr.postTranslate(screen.left, screen.top);
		if ( screen.width() < screen.height() )
		{
			float around = screen.width()/2;
			col2scr.postRotate(90, around, around);
		}
		col2scr.invert(scr2col);
    }
	
	/**
	 * Set up tiles and matrices based on puzzle and view sizes.
	 */
    private void initTiles()
	{
		//
		//	Prevent this from being called if tile_size is not set.
		//
		if ( tile_size.x <= 0 && tile_size.y <= 0 || puzzle == null )
			return;
		//
		//	Selection rect.
		//
		mSelectPaint.setARGB(0xff,0x40,0x40,0xf0);
		mSelectPaint.setStrokeWidth((float)3.0);
		//
		//	Create tile bitmaps for given width and height.
		//
		tile[FLOOR] = createFloorTile();
		tile[GOAL] = createGoalTile();
		tile[WALL] = createWallTile();
		tile[BOX] = createBoxTile();
		tile[BOX|SELECTED] = createBoxSelTile();
		tile[BINGO] = createBingoTile();
		tile[BINGO|SELECTED] = createBingoSelTile();
		tile[WORKER] = createWorkerTile();
		tile[WORKER|SELECTED] = createWorkerSelTile();
    }
	
	/**
	 * Apply screen to board mapping and if the points are in the
	 * column,row range then return it. Otherwise return null.
	 */
	public void getTile( float scr_x, float scr_y, Point /*out*/ tile )
	{
		float [] scr_p = { scr_x, scr_y };
		scr2col.mapPoints( scr_p );

		if ( 0 <= scr_p[0] && scr_p[0] < puzzle.getColumnCount()
			&& 0 <= scr_p[1] && scr_p[1] < puzzle.getRowCount())
			tile.set((int)scr_p[0], (int)scr_p[1] );
		else
			tile.set(-1, -1);
	}

	private Bitmap createFloorTile()
	{
		Bitmap tmp = BitmapFactory.decodeResource( getResources(), R.drawable.floor );
        Bitmap bitmap = Bitmap.createBitmap(tile_size.x, tile_size.y, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
		//canvas.drawARGB(0xff,0x40,0x40,0x40);
		canvas.drawBitmap(tmp, 
				new Rect(0,0,tmp.getWidth()-1,tmp.getHeight()-1), 
				new Rect(0,0,tile_size.x, tile_size.y),
				mPaint);
		return bitmap;
    }
	private Bitmap createGoalTile()
	{
  		Bitmap tmp = BitmapFactory.decodeResource( getResources(), R.drawable.goal );
        Bitmap bitmap = Bitmap.createBitmap(tile_size.x, tile_size.y, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
//		canvas.drawARGB(0xff,0x40,0x40,0xF0);
		canvas.drawBitmap(tmp, 
				new Rect(0,0,tmp.getWidth()-1,tmp.getHeight()-1), 
				new Rect(0,0,tile_size.x, tile_size.y),
				mPaint);
		return bitmap;
    }
	private Bitmap createWallTile()
	{
		Bitmap tmp = BitmapFactory.decodeResource( getResources(), R.drawable.wall );
        Bitmap bitmap = Bitmap.createBitmap(tile_size.x, tile_size.y, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
		//canvas.drawARGB(0xff,0xF0,0x40,0x40);
		canvas.drawBitmap(tmp, 
				new Rect(1,1,tmp.getWidth()-2,tmp.getHeight()-2), 
				new Rect(0,0,tile_size.x, tile_size.y),
				mPaint);
		return bitmap;
    }
	private Bitmap createBoxTile()
	{
  		Bitmap tmp = BitmapFactory.decodeResource( getResources(), R.drawable.box );
        Bitmap bitmap = Bitmap.createBitmap(tile_size.x, tile_size.y, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
//		canvas.drawARGB(0xff,0x40,0xF0,0x40);
		canvas.drawBitmap(tmp, 
				new Rect(0,0,tmp.getWidth()-1,tmp.getHeight()-1), 
				new Rect(0,0,tile_size.x, tile_size.y),
				mPaint);
		return bitmap;
    }
	private Bitmap createBoxSelTile()
	{
        Bitmap bitmap = createBoxTile();
        Canvas canvas = new Canvas(bitmap);
		canvas.drawLine(0,0,tile_size.x-1, 0, mSelectPaint);
		canvas.drawLine(tile_size.x-1, 0,tile_size.x-1, tile_size.y-1, mSelectPaint);
		canvas.drawLine(tile_size.x-1, tile_size.y-1, 0, tile_size.y-1, mSelectPaint);
		canvas.drawLine(0, tile_size.y-1, 0, 0, mSelectPaint);
		return bitmap;
    }
	private Bitmap createBingoTile()
	{
  		Bitmap tmp = BitmapFactory.decodeResource( getResources(), R.drawable.bingo );
        Bitmap bitmap = Bitmap.createBitmap(tile_size.x, tile_size.y, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
//		canvas.drawARGB(0xff,0x40,0xF0,0xF0);
		canvas.drawBitmap(tmp, 
				new Rect(0,0,tmp.getWidth()-1,tmp.getHeight()-1), 
				new Rect(0,0,tile_size.x, tile_size.y),
				mPaint);
		return bitmap;
    }
	private Bitmap createBingoSelTile()
	{
        Bitmap bitmap = createBingoTile();
        Canvas canvas = new Canvas(bitmap);
		canvas.drawLine(0,0,tile_size.x-1, 0, mSelectPaint);
		canvas.drawLine(tile_size.x-1, 0,tile_size.x-1, tile_size.y-1, mSelectPaint);
		canvas.drawLine(tile_size.x-1, tile_size.y-1, 0, tile_size.y-1, mSelectPaint);
		canvas.drawLine(0, tile_size.y-1, 0, 0, mSelectPaint);
		return bitmap;
    }
	private Bitmap createWorkerTile()
	{
		Bitmap tmp = BitmapFactory.decodeResource( getResources(), R.drawable.worker );
        Bitmap bitmap = Bitmap.createBitmap(tile_size.x, tile_size.y, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
		canvas.drawBitmap(tmp, 
				new Rect(0,0,tmp.getWidth()-1,tmp.getHeight()-1), 
				new Rect(0,0,tile_size.x, tile_size.y),
				mPaint);
		
		return bitmap;
    }
	private Bitmap createWorkerSelTile()
	{
		Bitmap tmp = BitmapFactory.decodeResource( getResources(), R.drawable.worker_select );
        Bitmap bitmap = Bitmap.createBitmap(tile_size.x, tile_size.y, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
		canvas.drawBitmap(tmp, 
				new Rect(0,0,tmp.getWidth()-1,tmp.getHeight()-1), 
				new Rect(0,0,tile_size.x, tile_size.y),
				mPaint);
		
		return bitmap;
    }

    @Override
    public void onDraw(Canvas canvas)
	{
        super.onDraw(canvas);
		if ( puzzle != null )
		{
			//int cols = puzzle.getColumnCount();
			//int rows = puzzle.getRowCount();
//			canvas.concat(col2scr);
			int xbgn = Math.max((int)viewport.left,(int)board.left);
			int xend = Math.min((int)viewport.right,(int)board.right-1);
			int ybgn = Math.max((int)viewport.top,(int)board.top);
			int yend = Math.min((int)viewport.bottom,(int)board.bottom-1);
			tile_src.set(0,0,tile_size.x,tile_size.y);
			for (int y = ybgn; y <= yend; ++y)
			{
				for (int x = xbgn; x <= xend; ++x)
				{
					tile_dest.set(x,y,x+1,y+1);
					col2scr.mapRect(tile_dest);
					//tile_hlpr.roundOut(tile_hlpr);
					//tile_hlpr.right -=1;
					//tile_hlpr.bottom -=1;
					int sym = puzzle.getSym(x,y);
					if ( !Puzzle.hasWorker(sym) )
					{
						canvas.drawBitmap(tile[sym], 
								tile_src,tile_dest,
								mPaint);
					}
					else
					{
						//
						//	Draw floor.
						//
						canvas.drawBitmap(tile[sym&~(WORKER_MASK|SELECTED)],
								tile_src,tile_dest,
								mPaint);
						//
						//	Draw worker.
						//
						canvas.save();
						Bitmap normal_worker = ((sym&SELECTED)!=0) ?
								tile[WORKER|SELECTED] : tile[WORKER];
						int direction = sym & WORKER_MASK;
						if ( direction == WORKER_NORTH )
						{
							canvas.translate(tile_dest.centerX(), tile_dest.centerY());
							canvas.rotate(180+rotated);
							canvas.translate(-tile_dest.centerX(), -tile_dest.centerY());
						}
						else if ( direction == WORKER_SOUTH )
						{
							canvas.translate(tile_dest.centerX(), tile_dest.centerY());
							canvas.rotate(0+rotated);
							canvas.translate(-tile_dest.centerX(), -tile_dest.centerY());
						}
						else if ( direction == WORKER_WEST )
						{
							canvas.translate(tile_dest.centerX(), tile_dest.centerY());
							canvas.rotate(90+rotated);
							canvas.translate(-tile_dest.centerX(), -tile_dest.centerY());
						}
						else if ( direction == WORKER_EAST )
						{
							canvas.translate(tile_dest.centerX(), tile_dest.centerY());
							canvas.rotate(-90+rotated);
							canvas.translate(-tile_dest.centerX(), -tile_dest.centerY());
						}
						canvas.drawBitmap(normal_worker,
								tile_src,tile_dest,
								mPaint);
						canvas.restore();
					}						
				}
			}
		}
    }
}
