package com.vaguehope.onosendai.ui;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;

import local.apache.ByteArrayOutputStream;
import android.app.ProgressDialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Rect;
import android.net.Uri;
import android.os.AsyncTask;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView;

import com.vaguehope.onosendai.R;
import com.vaguehope.onosendai.storage.AttachmentStorage;
import com.vaguehope.onosendai.util.DialogHelper.Listener;
import com.vaguehope.onosendai.util.ImageMetadata;
import com.vaguehope.onosendai.util.IoHelper;
import com.vaguehope.onosendai.util.LogWrapper;
import com.vaguehope.onosendai.util.Result;
import com.vaguehope.onosendai.util.Titleable;

public class PictureResizeDialog implements Titleable {

	private static final List<Scale> SCALES = Scale.setOf(100, 50, 25);
	private static final int DEFAULT_SCALES_POS = 1;

	private static final List<Scale> QUALITIES = Scale.setOf(100, 95, 90, 80, 70, 50, 30, 10);
	private static final int DEFAULT_QUALITY_POS = 2;

	private static final LogWrapper LOG = new LogWrapper("PRD");

	private final Context context;
	private final ImageMetadata srcMetadata;

	private final View llParent;
	private final ImageView imgPreview;
	private final Spinner spnScale;
	private final Spinner spnQuality;
	private final ProgressBar prgRedrawing;
	private final TextView txtSummary;

	public PictureResizeDialog (final Context context, final Uri pictureUri) {
		this.context = context;
		this.srcMetadata = new ImageMetadata(this.context, pictureUri);

		final LayoutInflater inflater = LayoutInflater.from(context);
		this.llParent = inflater.inflate(R.layout.pictureresizedialog, null);

		this.imgPreview = (ImageView) this.llParent.findViewById(R.id.imgPreview);
		this.spnScale = (Spinner) this.llParent.findViewById(R.id.spnScale);
		this.spnQuality = (Spinner) this.llParent.findViewById(R.id.spnQuality);
		this.prgRedrawing = (ProgressBar) this.llParent.findViewById(R.id.prgRedrawing);
		this.txtSummary = (TextView) this.llParent.findViewById(R.id.txtSummary);

		final ArrayAdapter<Scale> scaleAdapter = new ArrayAdapter<Scale>(context, R.layout.numberspinneritem);
		scaleAdapter.addAll(SCALES);
		this.spnScale.setAdapter(scaleAdapter);
		this.spnScale.setSelection(DEFAULT_SCALES_POS);
		this.spnScale.setOnItemSelectedListener(this.spnChangeListener);

		final ArrayAdapter<Scale> qualityAdapter = new ArrayAdapter<Scale>(context, R.layout.numberspinneritem);
		qualityAdapter.addAll(QUALITIES);
		this.spnQuality.setAdapter(qualityAdapter);
		this.spnQuality.setSelection(DEFAULT_QUALITY_POS);
		this.spnQuality.setOnItemSelectedListener(this.spnChangeListener);
	}

	public View getRootView () {
		return this.llParent;
	}

	@Override
	public String getUiTitle () {
		return "Shrink " + this.srcMetadata.getName(); //ES
	}

	public Scale getScale () {
		return (Scale) this.spnScale.getSelectedItem();
	}

	public Scale getQuality () {
		return (Scale) this.spnQuality.getSelectedItem();
	}

	public Uri resizeToTempFile () throws IOException {
		final Bitmap scaled = this.srcMetadata.getBitmap(getScale().getPercentage());
		final File tgt = AttachmentStorage.getTempFile(this.context, "shrunk_" + this.srcMetadata.getName());
		final OutputStream tgtOut = new FileOutputStream(tgt);
		try {
			scaled.compress(Bitmap.CompressFormat.JPEG, getQuality().getPercentage(), tgtOut);
			return Uri.fromFile(tgt);
		}
		finally {
			IoHelper.closeQuietly(tgtOut);
		}
	}

	public void recycle () {
		this.srcMetadata.recycle();
	}

	protected Context getContext () {
		return this.context;
	}

	private final OnItemSelectedListener spnChangeListener = new OnItemSelectedListener() {
		@Override
		public void onItemSelected (final AdapterView<?> parent, final View view, final int position, final long id) {
			updateSummary();
		}

		@Override
		public void onNothingSelected (final AdapterView<?> arg0) {/**/}
	};

	private Scale lastScale = null;
	private Scale lastQuality = null;

	protected void updateSummary () {
		if (this.lastScale == getScale() && this.lastQuality == getQuality()) return;
		this.lastScale = getScale();
		this.lastQuality = getQuality();
		new PreviewResizeTask(this.txtSummary, this.imgPreview, this.prgRedrawing, this.srcMetadata, this).execute();
	}

	protected int scaleDimension (final int from) {
		final Scale s = getScale();
		if (s.getPercentage() == 100) return from; // NOSONAR 100 not magic number, its how percentage works.
		return (int) (from * s.getPercentage() / 100d); // NOSONAR 100 not magic number, its how percentage works.
	}

	public static class Scale {

		private final int percentage;
		private final String title;

		public Scale (final int percentage, final String title) {
			this.percentage = percentage;
			this.title = title;
		}

		public int getPercentage () {
			return this.percentage;
		}

		@Override
		public String toString () {
			return this.title;
		}

		public static List<Scale> setOf (final int... a) {
			final List<Scale> ret = new ArrayList<Scale>();
			for (final int i : a) {
				ret.add(new Scale(i, i + "%"));
			}
			return Collections.unmodifiableList(ret);
		}

	}

	private static class PreviewResizeTask extends AsyncTask<Void, Void, Bitmap> {

		private final TextView txtSummary;
		private final ImageView imgPreview;
		private final ProgressBar prgRedrawing;
		private final StringBuilder summary;
		private final ImageMetadata srcMetadata;
		private final PictureResizeDialog dlg;

		public PreviewResizeTask (final TextView txtSummary, final ImageView imgPreview, final ProgressBar prgRedrawing, final ImageMetadata srcMetadata, final PictureResizeDialog dlg) {
			this.txtSummary = txtSummary;
			this.imgPreview = imgPreview;
			this.prgRedrawing = prgRedrawing;
			this.srcMetadata = srcMetadata;
			this.dlg = dlg;
			this.summary = new StringBuilder();
		}

		@Override
		protected void onPreExecute () {
			this.prgRedrawing.setVisibility(View.VISIBLE);
		}

		@Override
		protected Bitmap doInBackground (final Void... params) {
			LOG.i("Generating preview: s=%s q=%s.", this.dlg.getScale(), this.dlg.getQuality());
			try {
				final Bitmap scaled = this.srcMetadata.getBitmap(this.dlg.getScale().getPercentage());
				final ByteArrayOutputStream compOut = new ByteArrayOutputStream(512 * 1024);
				if (scaled.compress(Bitmap.CompressFormat.JPEG, this.dlg.getQuality().getPercentage(), compOut)) {
					this.summary
							.append(this.srcMetadata.getWidth()).append(" x ").append(this.srcMetadata.getHeight())
							.append(" (").append(IoHelper.readableFileSize(this.srcMetadata.getSize())).append(")")
							.append(" --> ").append(scaled.getWidth()).append(" x ").append(scaled.getHeight())
							.append(" (").append(IoHelper.readableFileSize(compOut.size())).append(")");
					final BitmapRegionDecoder dec = BitmapRegionDecoder.newInstance(compOut.toBufferedInputStream(), true);
					try {
						final int srcW = dec.getWidth();
						final int srcH = dec.getHeight();
						final int tgtW = this.dlg.getRootView().getWidth(); // FIXME Workaround for ImageView width issue.  Fix properly with something like FixedWidthImageView.
						final int tgtH = this.imgPreview.getHeight();
						final int left = srcW > tgtW ? (srcW - tgtW) / 2 : 0;
						final int top = srcH > tgtH ? (srcH - tgtH) / 2 : 0;
						final BitmapFactory.Options options = new BitmapFactory.Options();
						options.inPurgeable = true;
						options.inInputShareable = true;
						return dec.decodeRegion(new Rect(left, top, left + tgtW, top + tgtH), options);
					}
					finally {
						dec.recycle();
					}
				}
				this.summary.append("Failed to compress image."); //ES
				return null;
			}
			// XXX Many cases of OutOfMemoryError have been reported, particularly on low end hardware.
			// Try not to upset the user too much by not dying completely if possible.
			catch (final Throwable e) {
				LOG.e("Failed to generate preview image.", e);
				this.summary.append(e.toString());
				return null;
			}
		}

		@Override
		protected void onPostExecute (final Bitmap result) {
			this.txtSummary.setText(this.summary.toString());
			if (result != null) {
				this.imgPreview.setImageBitmap(result);
			}
			else {
				this.imgPreview.setImageResource(R.drawable.exclamation_red);
			}
			this.prgRedrawing.setVisibility(View.INVISIBLE);
		}

	}

	public static class ResizeToTempFileTask extends AsyncTask<Void, Void, Result<Uri>> {

		private final PictureResizeDialog prd;
		private final Listener<Result<Uri>> resultListener;
		private ProgressDialog dialog;

		public ResizeToTempFileTask (final PictureResizeDialog prd, final Listener<Result<Uri>> resultListener) {
			this.prd = prd;
			this.resultListener = resultListener;
		}

		@Override
		protected void onPreExecute () {
			this.dialog = ProgressDialog.show(this.prd.getContext(), "Shrink image", "Resizing image...", true); //ES
		}

		@Override
		protected Result<Uri> doInBackground (final Void... params) {
			try {
				return new Result<Uri>(this.prd.resizeToTempFile());
			}
			catch (final Exception e) { // NOSONAR show user all errors.
				return new Result<Uri>(e);
			}
			// XXX Cases of OutOfMemoryError have been reported, particularly on low end hardware.
			// Try not to upset the user too much by not dying completely if possible.
			catch (final Throwable e) {
				return new Result<Uri>(new ExecutionException("Failed to resize image.", e));
			}
		}

		@Override
		protected void onPostExecute (final Result<Uri> result) {
			this.dialog.dismiss();
			this.resultListener.onAnswer(result);
		}

	}

}