package com.derpfish.pinkielive;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import com.derpfish.pinkielive.animation.PinkieAnimation;
import com.derpfish.pinkielive.animation.PonyAnimation;
import com.derpfish.pinkielive.download.PonyAnimationContainer;
import com.derpfish.pinkielive.download.PonyDownloader;

import android.app.WallpaperManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Handler;
import android.os.SystemClock;
import android.service.wallpaper.WallpaperService;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;

public class PinkiePieLiveWallpaper extends WallpaperService
{
	public static final String SHARED_PREFS_NAME = "livewallpapersettings";
	// Round-trip time for a jump to complete in ms.
	public static final double TIME_FOR_JUMP = 1500.0;

	private Bitmap defaultBg = null;

	// Settings
	private Bitmap selectedBg = null;
	private boolean useDefaultBg = true;
	private long targetFramerate = 60L;
	private boolean enableParallax = true;
	private PonyAnimation selectedPony;

	@Override
	public void onCreate()
	{
		super.onCreate();
	}

	@Override
	public void onDestroy()
	{
		if (defaultBg != null)
		{
			defaultBg.recycle();
			defaultBg = null;
		}
		if (selectedBg != null)
		{
			selectedBg.recycle();
			selectedBg = null;
		}
		if (selectedPony != null)
		{
			selectedPony.onDestroy();
			selectedPony = null;
		}

		super.onDestroy();
	}

	@Override
	public Engine onCreateEngine()
	{
		return new PonyEngine();
	}

	class PonyEngine extends Engine implements SharedPreferences.OnSharedPreferenceChangeListener
	{
		private long lastUpdate;

		private int surfaceWidth;
		private int surfaceHeight;
		private float offsetX;
		private float offsetY;
		private final Handler		mHandler		= new Handler();
		private final Paint			mPaint			= new Paint();
		private final Runnable		mDrawPattern	= new Runnable()
													{
														public void run()
														{
															drawFrame();
														}
													};
		
		private boolean				mVisible;
		private final SharedPreferences mPreferences;
		private final BroadcastReceiver broadcastReceiver;

		private PonyEngine()
		{
			final Paint paint = mPaint;
			paint.setColor(0xffffffff);
			paint.setAntiAlias(true);
			paint.setStrokeWidth(2);
			paint.setStrokeCap(Paint.Cap.ROUND);
			paint.setStyle(Paint.Style.STROKE);

			mPreferences = PinkiePieLiveWallpaper.this.getSharedPreferences(SHARED_PREFS_NAME, 0);
			mPreferences.registerOnSharedPreferenceChangeListener(this);

			/*
			 * If the media scanner finishes a scan, reload the preferences
			 * since this means a previously unavailable background is now
			 * available.
			 */
			final IntentFilter iFilter = new IntentFilter();
			iFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
			iFilter.addDataScheme("file");

			broadcastReceiver = new BroadcastReceiver()
			{
				@Override
				public void onReceive(Context context, Intent intent)
				{
					final String action = intent.getAction();
					if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED))
					{
						onSharedPreferenceChanged(mPreferences, null);
					}
				}
			};
			registerReceiver(broadcastReceiver, iFilter);

			// Load saved preferences
			onSharedPreferenceChanged(mPreferences, null);
		}

		@Override
		public void onSharedPreferenceChanged(SharedPreferences prefs, String key)
		{
			useDefaultBg = prefs.getBoolean("livewallpaper_defaultbg", true);
			if (useDefaultBg)
			{
				if (selectedBg != null)
				{
					selectedBg.recycle();
					selectedBg = null;
				}
				try
				{
					final WallpaperManager wmMan = WallpaperManager.getInstance(getApplicationContext());
					final AssetManager assetManager = getAssets();
					
					InputStream istr = assetManager.open("defaultbg.jpg");
					final int sampleSize = BitmapLoader.getSampleSizeFromInputStream(istr,
							wmMan.getDesiredMinimumWidth(), wmMan.getDesiredMinimumHeight());
					istr.close();
					istr = assetManager.open("defaultbg.jpg");
					defaultBg = BitmapLoader.decodeSampledBitmapFromInputStream(istr, sampleSize);
					istr.close();
				}
				catch (IOException e)
				{
					throw new IllegalStateException("Could not find background image");
				}
			}
			else
			{
				if (defaultBg != null)
				{
					defaultBg.recycle();
					defaultBg = null;
				}
				
				final String imageUriStr = prefs.getString("livewallpaper_image", null);
				if (imageUriStr != null)
				{
					if (selectedBg != null)
					{
						selectedBg.recycle();
					}

					final Uri bgImage = Uri.parse(imageUriStr);
					try
					{
						final ContentResolver contentResolver = getContentResolver();
						final WallpaperManager wmMan = WallpaperManager.getInstance(getApplicationContext());
						
						InputStream istr = contentResolver.openInputStream(bgImage);
						final int sampleSize = BitmapLoader.getSampleSizeFromInputStream(istr,
								wmMan.getDesiredMinimumWidth(), wmMan.getDesiredMinimumHeight());
						istr.close();
						
						istr = contentResolver.openInputStream(bgImage);
						selectedBg = BitmapLoader.decodeSampledBitmapFromInputStream(istr, sampleSize);
						istr.close();
					}
					catch (IOException e)
					{
						selectedBg = null;
						Log.w("PinkieLive", e);
					}
				}
			}

			// Framerate
			final String frameratePref = prefs.getString("livewallpaper_framerate", "60");
			try
			{
				targetFramerate = Long.parseLong(frameratePref);
			}
			catch (NumberFormatException e)
			{
				Log.e("PinkieLive", e.getMessage());
			}

			// Parallax
			enableParallax = prefs.getBoolean("livewallpaper_enableparallax", true);
			if (!enableParallax)
			{
				offsetX = offsetY = 0.0f;
			}

			// Change selected pony
			if (selectedPony != null)
			{
				selectedPony.onDestroy();
			}
			// Refresh pony animations; preference change could have updated them
			final Map<String, PonyAnimation> ponyAnimations = new HashMap<String, PonyAnimation>();
			ponyAnimations.put("pinkie", new PinkieAnimation(getAssets()));
			try
			{
				for (final PonyAnimationContainer container : PonyDownloader.getPonyAnimations(
						getFilesDir(), getCacheDir(), true))
				{
					ponyAnimations.put(container.getId(), container.getPonyAnimation());
				}
			}
			catch (Exception e)
			{
				throw new RuntimeException(e);
			}
			// Set new pony animation
			final String selectedPonyId = prefs.getString("livewallpaper_pony", "pinkie");
			selectedPony = ponyAnimations.containsKey(selectedPonyId) ? ponyAnimations.get(selectedPonyId) : ponyAnimations.get("pinkie");
			selectedPony.onCreate();

			drawFrame();
		}

		@Override
		public void onCreate(SurfaceHolder surfaceHolder)
		{
			super.onCreate(surfaceHolder);
			setTouchEventsEnabled(true);
		}

		@Override
		public void onDestroy()
		{
			super.onDestroy();

			mHandler.removeCallbacks(mDrawPattern);
			unregisterReceiver(broadcastReceiver);
		}

		@Override
		public void onVisibilityChanged(boolean visible)
		{
			mVisible = visible;
			if (visible)
			{
				drawFrame();
			}
			else
			{
				mHandler.removeCallbacks(mDrawPattern);
			}
		}

		@Override
		public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height)
		{
			super.onSurfaceChanged(holder, format, width, height);
			surfaceWidth = width;
			surfaceHeight = height;
			drawFrame();
		}

		@Override
		public void onSurfaceCreated(SurfaceHolder holder)
		{
			super.onSurfaceCreated(holder);
		}

		@Override
		public void onSurfaceDestroyed(SurfaceHolder holder)
		{
			super.onSurfaceDestroyed(holder);
			mVisible = false;
			mHandler.removeCallbacks(mDrawPattern);
		}

		@Override
		public void onOffsetsChanged(float xOffset, float yOffset, float xStep, float yStep, int xPixels, int yPixels)
		{
			if (enableParallax)
			{
				offsetX = xPixels;
				offsetY = yPixels;
				drawFrame();
			}
		}

		/*
		 * Store the position of the touch event so we can use it for drawing
		 * later
		 */
		@Override
		public void onTouchEvent(MotionEvent event)
		{
			if (event.getAction() == MotionEvent.ACTION_UP)
			{
				// If the length of time pressed was less than 0.5 seconds,
				// trigger a new drawing
				if (event.getEventTime() - event.getDownTime() < 500)
				{
					if (selectedPony.isComplete())
					{
						selectedPony.initialize(surfaceWidth, surfaceHeight, event.getX(), event.getY());
						lastUpdate = SystemClock.elapsedRealtime();
						drawFrame();
					}
				}
			}

			super.onTouchEvent(event);
		}

		/*
		 * Draw one frame of the animation. This method gets called repeatedly
		 * by posting a delayed Runnable. You can do any drawing you want in
		 * here. This example draws a wireframe cube.
		 */
		private void drawFrame()
		{
			final SurfaceHolder holder = getSurfaceHolder();

			Canvas c = null;
			try
			{
				c = holder.lockCanvas();
				if (c != null)
				{
					// Blank canvas
					final Paint paintBlack = new Paint();
					paintBlack.setColor(0xff000000);
					c.drawRect(0.0f, 0.0f, surfaceWidth, surfaceHeight, paintBlack);

					// draw something
					if (useDefaultBg || selectedBg != null)
					{
						final Bitmap actualBg = selectedBg != null ? selectedBg : defaultBg;

						if (enableParallax)
						{
							final WallpaperManager wmMan = WallpaperManager.getInstance(getApplicationContext());
							final int minWidth = wmMan.getDesiredMinimumWidth();
							final int minHeight = wmMan.getDesiredMinimumHeight();
							final float bgScale = Math.min(((float)actualBg.getWidth()) / ((float)minWidth), ((float)actualBg.getHeight()) / ((float)minHeight));
							final int centeringOffsetX = (int)((float)actualBg.getWidth() - bgScale*minWidth)/2;
							final int centeringOffsetY = (int)((float)actualBg.getHeight() - bgScale*minHeight)/2;
							
							c.drawBitmap(actualBg,
									new Rect(centeringOffsetX - (int)(offsetX*bgScale),
											centeringOffsetY - (int)(offsetY*bgScale),
											centeringOffsetX + (int)((-offsetX + surfaceWidth)*bgScale),
											centeringOffsetY + (int)((-offsetY + surfaceHeight)*bgScale)),
									new Rect(0, 0, surfaceWidth, surfaceHeight), mPaint);
						}
						else
						{
							final float bgScale = Math.min(((float)actualBg.getWidth()) / ((float)surfaceWidth), ((float)actualBg.getHeight()) / ((float)surfaceHeight));
							final int centeringOffsetX = (int)((float)actualBg.getWidth() - bgScale*surfaceWidth)/2;
							final int centeringOffsetY = (int)((float)actualBg.getHeight() - bgScale*surfaceHeight)/2;
							c.drawBitmap(actualBg,
									new Rect(centeringOffsetX, centeringOffsetY,
											centeringOffsetX + (int)(surfaceWidth*bgScale),
											centeringOffsetY + (int)(surfaceHeight*bgScale)),
									new Rect(0, 0, surfaceWidth, surfaceHeight), mPaint);
						}

					}

					// Decide new position and velocity.
					if (!selectedPony.isComplete())
					{
						final long now = SystemClock.elapsedRealtime();
						long elapsedTime = now - lastUpdate;
						lastUpdate = now;
						selectedPony.drawAnimation(c, elapsedTime);
					}
				}
			}
			finally
			{
				if (c != null)
					holder.unlockCanvasAndPost(c);
			}

			mHandler.removeCallbacks(mDrawPattern);
			// Only queue another frame if we're still animating pinkie
			if (mVisible && !selectedPony.isComplete())
			{
				mHandler.postDelayed(mDrawPattern, 1000 / targetFramerate);
			}
		}
	}
}