/*
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * <kool.name at gmail.com> wrote this file. As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a beer in return.  Justin "kool.name" Lee
 * ----------------------------------------------------------------------------
 */

package net.toload.main;

import android.R;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.Toast;

public class MultiListPreference extends DialogPreference {
	/**	Tag for logging! */
	public static final String TAG = "MultiListPreference";

	/**	How the "choices" will be delimeted. */
	public static final String CHOICE_DELIMITER = ";";
	/**	Truth regex thing. */
	public static final String TRUTH_REGEX = "^(?i:t(?:rue)?)";
	/**	"Using default." message incase of dialog error. */
	public static final String USING_DEFAULT = "Using default.";

	/**	Contains the boolean state of each entry. */
	private boolean[] state = null;
	/**	Contains textual representations of the entries. */
	private CharSequence[] entry = null;
	/**	Contains the default value, if any. */
	private String defaultValue = null;


	/**	Parse character sequence array into boolean array using regex.
		@param state Character sequence array to parse.
		@return Boolean array or null. */
	private static boolean[] cs2b(CharSequence[] state) {
		Log.d(TAG, "cs2b()");

		boolean[] out = null;

		if (state != null) {
			out = new boolean[state.length];

			for (int e = 0; e < state.length; e++) {
				out[e] = state[e].toString().matches(TRUTH_REGEX);
			}
		}

		return (out);
	}

	/**	Parse delimited numbers into a boolean array.
		@param state The states given here will be true.
		@param size The array size.
		@return Boolean array of given size or null. */
	private static boolean[] ds2b(CharSequence state, int size) {
		Log.d(TAG, "ds2b(): " + state + " (" + size + ")");

		boolean[] out = null;

		if (state != null) {
			out = new boolean[size];

			String[] s = state.toString().split(CHOICE_DELIMITER);

			for (int e = 0; e < s.length; e++) {
				int index = Integer.parseInt(s[e]);

				if (index < out.length) {
					out[index] = true;
				} else {
					out = null;
					break;
				}
			}
		}

		return (out);
	}

	/**	Turn boolean array into delimited numbers.
		@param state State array.
		@return Delimited number thing or null. */
	private static CharSequence b2ds(boolean[] state) {
		Log.d(TAG, "b2ds()");

		String out = null;

		if (state != null) {
			for (int e = 0; e < state.length; e++) {
				if (state[e]) {
					if (out == null) {
						out = "" + e;
					} else {
						out += CHOICE_DELIMITER + e;
					}
				}
			}

			if (out == null) {
				out = "";
			}
		}

		return (out);
	}


	/**	Set the entries to something new.
		@param entry New entry array.
		@return Success or failure flag. */
	public boolean setEntries(CharSequence[] entry) {
		Log.d(TAG, "setEntries()");

		boolean updated = false;

		if (this.entry == null) {
			if (this.state == null) {
				updated = true;
			} else if (entry.length == this.state.length) {
				updated = true;
			}
		} else if (entry.length == this.entry.length) {
			updated = true;
		}

		if (updated) {
			this.entry = entry;
		}

		return (updated);
	}

	/**	Fetch a copy of the current entry array.
		@return Current entry array clone. */
	public CharSequence[] getEntries() {
		Log.d(TAG, "getEntries()");

		return (this.entry.clone());
	}

	/**	Set the state to something new.
		@param state New state array.
		@return Success or failure flag. */
	public boolean setValue(boolean[] state) {
		Log.d(TAG, "setValue()");

		boolean updated = false;

		if (this.state == null) {
			if (this.entry == null) {
				updated = true;
			} else if (state.length == this.entry.length) {
				updated = true;
			}
		} else if (state.length == this.state.length) {
			updated = true;
		}

		if (updated) {
			this.state = state;
		}

		return (updated);
	}

	/**	Set the state to something new, state must be previously initialized.
		@param state New state, given by delimeted index.
		@return Success or failure flag. */
	public boolean setValue(CharSequence state) {
		Log.d(TAG, "setValue(): " + state);

		boolean updated = false;

		if (this.state != null) {
			boolean[] newState = ds2b(state, this.state.length);

			if (newState != null) {
				this.state = newState;
				updated = true;
			}
		}

		return (updated);
	}

	/**	Fetch a copy of the current state array.
		@return Current state array clone. */
	public boolean[] getValue() {
		Log.d(TAG, "getValue()");

		return (this.state.clone());
	}


	public MultiListPreference(Context context, AttributeSet attrs) {
		super(context, attrs); Log.d(TAG, "MultiListPreference()");

		TypedArray in = context.obtainStyledAttributes(
			attrs,
			new int[]{
				// "android:entries", just like ListPreference
				R.attr.entries,
				// "android:entryValues", these are defaults ONLY
				R.attr.entryValues,
				// "android:defaultValue"
				R.attr.defaultValue
			}
		);

		this.entry = in.getTextArray(0);
		this.defaultValue = in.getString(2);

		if (this.entry == null) {
			Log.d(TAG, "MultiListPreference(): Could not restore entry.");
		} else {
			Log.d(TAG, "MultiListPreference(): Entry from defaults.");

			if (this.defaultValue != null) {
				this.state = ds2b(this.defaultValue, this.entry.length);
			}

			if (this.state == null) {
				this.state = cs2b(in.getTextArray(1));

				if (this.state == null) {
					Log.d(TAG, "MultiListPreference(): Could not restore state.");
				} else {
					Log.d(TAG, "MultiListPreference(): Restored state from entryValues.");
				}
			} else {
				Log.d(TAG, "MultiListPreference(): Restored state from defaultValue.");
			}
		}

		in.recycle();
	}


	@Override // here we set the multichoiceitem content
	public void onPrepareDialogBuilder(AlertDialog.Builder builder) {
		Log.d(TAG, "onPrepareDialogBuilder()");

		// try to restore state from persisted value
		if (this.entry != null || this.state != null) {
			int size = (
				(this.entry == null)
					? this.state.length
					: this.entry.length
			);

			boolean[] persistedState = ds2b(this.getPersistedString(null), size);

			if (persistedState != null) {
				this.state = persistedState;

				Log.d(TAG, "onPrepareDialogBuilder(): Persisted state restored.");
			}
		}


		// build the dialog
		builder
			.setCancelable(false)

			.setMultiChoiceItems(
				this.entry, // entries pulled from XML
				this.state,

				new DialogInterface.OnMultiChoiceClickListener() {
					// make a "convenience" pointer, cause I'm lazy
					private final MultiListPreference that = MultiListPreference.this;

					public void onClick(DialogInterface dialog, int which, boolean isChecked) {
						Log.d(TAG, "listItem_" + which + ".onClick(): " + isChecked);

						that.state[which] = isChecked;
					}
				}
			)
		;
	}


	@Override // called when OK (true) or Cancel (false) are pushed
	public void onDialogClosed(boolean positiveResult) {
		Log.d(TAG, "onDialogClosed(): " + positiveResult);

		if (positiveResult) {
			// only commit if my change listener says so
			if (this.callChangeListener(this.state)) {
				String out = (String) b2ds(this.state);

				// check if the value is OK
				if (out == null || out == "") {
					Toast.makeText(
						this.getContext(),
						USING_DEFAULT,
						Toast.LENGTH_SHORT
					).show();

					out = (
						(this.defaultValue == null)
							? "0" // XXX: lol, hax
							: this.defaultValue
					);
				}

				Log.d(TAG, "onDialogClosed(): Saving: " + out);

				if (this.persistString(out)) {
					this.notifyChanged();
				} else {
					Log.d(TAG, "onDialogClosed(): Saving failed.");
				}
			}
		}
	}
}
