package com.kurukurupapa.tryandroidui.draglist_bk;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;

/**
 * ドラッグアンドドロップで項目を上下に移動移動可能なリストビュー
 */
public class DragListView extends ListView implements
		AdapterView.OnItemLongClickListener {
	private static final int SCROLL_SPEED_FAST = 25;
	private static final int SCROLL_SPEED_SLOW = 8;

	private DragListListener mDragListener;
	private DragListViewHelper mHelper;
	private boolean mDragging = false;
	private MotionEvent mActionDownEvent;
	private int mPositionFrom = -1;

	/**
	 * コンストラクタ
	 */
	public DragListView(Context context, AttributeSet attrs) {
		super(context, attrs);
		mHelper = new DragListViewHelper(this);
		setOnItemLongClickListener(this);
	}

	/**
	 * ドラッグイベントリスナの設定
	 */
	public void setDragListener(DragListListener listener) {
		mDragListener = listener;
	}

	/**
	 * 長押しイベント<br>
	 * ドラッグを開始する。当イベントの前に、タッチイベント（ACTION_DOWN）が呼ばれている前提。
	 */
	@Override
	public boolean onItemLongClick(AdapterView<?> parent, View view,
			int position, long id) {
		return startDrag();
	}

	/**
	 * タッチイベント<br>
	 * ドラッグしている項目の移動や、ドラッグ終了の制御を行う。
	 */
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// LogUtil.d("MotionEvent=" + event.getAction());
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			storeMotionEvent(event);
			break;
		case MotionEvent.ACTION_MOVE:
			if (duringDrag(event)) {
				return true;
			}
			break;
		case MotionEvent.ACTION_UP:
			if (stopDrag(event)) {
				return true;
			}
			break;
		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_OUTSIDE:
			if (stopDrag(event)) {
				return true;
			}
			break;
		}
		return super.onTouchEvent(event);
	}

	/**
	 * ACTION_DOWN 時の MotionEvent をプロパティに格納
	 */
	private void storeMotionEvent(MotionEvent event) {
		mActionDownEvent = event;
	}

	/**
	 * ドラッグ開始
	 */
	private boolean startDrag() {
		// イベントから position を取得
		mPositionFrom = eventToPosition(mActionDownEvent);
		// 取得した position が 0未満＝範囲外の場合はドラッグを開始しない
		if (mPositionFrom < 0) {
			return false;
		}
		mDragging = true;

		// ドラッグ中のリスト項目の描画を開始する
		mHelper.startDrag(mPositionFrom, getChildByIndex(mPositionFrom));

		// リスナーにドラッグ開始イベントを送る
		if (mDragListener != null) {
			mDragListener.onStartDrag(mPositionFrom);
		}

		// リストビューを再描画する
		invalidateViews();

		// ドラッグ処理を呼び出す
		return duringDrag(mActionDownEvent);
	}

	/**
	 * ドラッグ処理
	 */
	private boolean duringDrag(MotionEvent event) {
		if (!mDragging) {
			return false;
		}

		final int x = (int) event.getX();
		final int y = (int) event.getY();
		final int positionTo = pointToPosition(x, y);

		// ドラッグ中のリスト項目の描画を更新する
		mHelper.duringDrag(x, y);

		// ドラッグの移動先リスト項目が存在する場合
		if (positionTo != AdapterView.INVALID_POSITION) {

			// リスナーにドラッグイベントを送る
			if (mDragListener != null) {
				mDragListener.onDuringDrag(mPositionFrom, positionTo);
			}

			// ドラッグ位置を更新する
			mPositionFrom = positionTo;
		}

		// リストビューを再描画する
		invalidateViews();

		// 必要あればスクロールさせる
		// 注意：invalidateViews()後に処理しないとスクロールしなかった
		setScroll(event);

		return true;
	}

	/**
	 * 必要あればスクロールさせる
	 */
	private void setScroll(MotionEvent event) {
		final int y = (int) event.getY();
		final int height = getHeight();
		final int middle = height / 2;

		// スクロール速度の決定
		final int speed;
		final int fastBound = height / 9;
		final int slowBound = height / 4;
		if (event.getEventTime() - event.getDownTime() < 500) {
			// ドラッグの開始から500ミリ秒の間はスクロールしない
			speed = 0;
		} else if (y < slowBound) {
			speed = y < fastBound ? -SCROLL_SPEED_FAST : -SCROLL_SPEED_SLOW;
		} else if (y > height - slowBound) {
			speed = y > height - fastBound ? SCROLL_SPEED_FAST
					: SCROLL_SPEED_SLOW;
		} else {
			speed = 0;
		}

		if (speed != 0) {
			// 画面の中央にあるリスト項目位置を求める
			// 横方向はとりあえず考えない
			// 中央がちょうどリスト項目間の境界の場合は、位置が取得できないので、
			// 境界からずらして再取得する。
			int middlePosition = pointToPosition(0, middle);
			if (middlePosition == AdapterView.INVALID_POSITION) {
				// middlePosition = pointToPosition(0, middle +
				// getDividerHeight()
				// + 64);
				middlePosition = pointToPosition(0, middle + getDividerHeight());
			}

			// スクロール実施
			final View middleView = getChildByIndex(middlePosition);
			if (middleView != null) {
				setSelectionFromTop(middlePosition, middleView.getTop() - speed);
			}
		}
	}

	/**
	 * ドラッグ終了
	 */
	private boolean stopDrag(MotionEvent event) {
		if (!mDragging) {
			return false;
		}
		mDragging = false;

		// ドラッグ中のリスト項目の描画を終了する
		mHelper.stopDrag();

		// リスナーにドラッグ終了イベントを送る
		if (mDragListener != null) {
			mDragListener.onStopDrag();
		}

		// リストビューを再描画する
		invalidateViews();
		return true;
	}

	/**
	 * MotionEvent から position を取得する
	 */
	private int eventToPosition(MotionEvent event) {
		return pointToPosition((int) event.getX(), (int) event.getY());
	}

	/**
	 * 指定インデックスのView要素を取得する
	 */
	private View getChildByIndex(int index) {
		return getChildAt(index - getFirstVisiblePosition());
	}

}