package jp.juggler.subwaytooter.view

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.Paint
import android.os.SystemClock
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
import jp.juggler.util.LogCategory
import jp.juggler.util.runOnMainLooper
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.sqrt

class PinchBitmapView(context : Context, attrs : AttributeSet?, defStyle : Int) :
	View(context, attrs, defStyle) {
	
	companion object {
		
		internal val log = LogCategory("PinchImageView")
		
		// 数値を範囲内にクリップする
		private fun clip(min : Float, max : Float, v : Float) : Float {
			return if(v < min) min else if(v > max) max else v
		}
		
		// ビューの幅と画像の描画サイズを元に描画位置をクリップする
		private fun clipTranslate(
			view_w : Float // ビューの幅
			, bitmap_w : Float // 画像の幅
			, current_scale : Float // 画像の拡大率
			, trans_x : Float // タッチ操作による表示位置
		) : Float {
			
			// 余白(拡大率が小さい場合はプラス、拡大率が大きい場合はマイナス)
			val padding = view_w - bitmap_w * current_scale
			
			// 余白が>=0なら画像を中心に表示する。 <0なら操作された位置をクリップする。
			return if(padding >= 0f) padding / 2f else clip(padding, 0f, trans_x)
		}
	}
	
	private var callback : Callback? = null
	
	private var bitmap : Bitmap? = null
	private var bitmap_w : Float = 0.toFloat()
	private var bitmap_h : Float = 0.toFloat()
	private var bitmap_aspect : Float = 0.toFloat()
	
	// 画像を表示する位置と拡大率
	private var current_trans_x : Float = 0.toFloat()
	private var current_trans_y : Float = 0.toFloat()
	private var current_scale : Float = 0.toFloat()
	
	// 画像表示に使う構造体
	private val drawMatrix = Matrix()
	internal val paint = Paint()
	
	// タッチ操作中に指を動かした
	private var bDrag : Boolean = false
	
	// タッチ操作中に指の数を変えた
	private var bPointerCountChanged : Boolean = false
	
	// ページめくりに必要なスワイプ強度
	private var swipe_velocity = 0f
	private var swipe_velocity2 = 0f
	
	// 指を動かしたと判断する距離
	private var drag_length = 0f
	
	private var time_touch_start = 0L
	
	// フリック操作の検出に使う
	private var velocityTracker : VelocityTracker? = null
	
	private var click_time = 0L
	private var click_count = 0
	
	// 移動後の指の位置
	internal val pos = PointerAvg()
	
	// 移動開始時の指の位置
	private val start_pos = PointerAvg()
	
	// 移動開始時の画像の位置
	private var start_image_trans_x : Float = 0.toFloat()
	private var start_image_trans_y : Float = 0.toFloat()
	private var start_image_scale : Float = 0.toFloat()
	
	private var scale_min : Float = 0.toFloat()
	private var scale_max : Float = 0.toFloat()
	
	private var view_w : Float = 0.toFloat()
	private var view_h : Float = 0.toFloat()
	private var view_aspect : Float = 0.toFloat()
	
	private val tracking_matrix = Matrix()
	private val tracking_matrix_inv = Matrix()
	private val avg_on_image1 = FloatArray(2)
	private val avg_on_image2 = FloatArray(2)
	
	constructor(context : Context) : this(context, null) {
		init(context)
	}
	
	constructor(context : Context, attrs : AttributeSet?) : this(context, attrs, 0) {
		init(context)
	}
	
	init {
		init(context)
	}
	
	internal fun init(context : Context) {
		
		// 定数をdpからpxに変換
		val density = context.resources.displayMetrics.density
		swipe_velocity = 1000f * density
		swipe_velocity2 = 250f * density
		drag_length = 4f * density // 誤反応しがちなのでやや厳しめ
	}
	
	// ページめくり操作のコールバック
	interface Callback {
		
		fun onSwipe(deltaX : Int, deltaY : Int)
		
		fun onMove(bitmap_w : Float, bitmap_h : Float, tx : Float, ty : Float, scale : Float)
	}
	
	fun setCallback(callback : Callback?) {
		this.callback = callback
	}
	
	fun setBitmap(b : Bitmap?) {
		
		bitmap?.recycle()
		
		this.bitmap = b
		
		initializeScale()
	}
	
	override fun onDraw(canvas : Canvas) {
		super.onDraw(canvas)
		
		val bitmap = this.bitmap
		if(bitmap != null && ! bitmap.isRecycled) {
			
			drawMatrix.reset()
			drawMatrix.postScale(current_scale, current_scale)
			drawMatrix.postTranslate(current_trans_x, current_trans_y)
			
			paint.isFilterBitmap = current_scale < 4f
			canvas.drawBitmap(bitmap, drawMatrix, paint)
		}
	}
	
	override fun onSizeChanged(w : Int, h : Int, oldw : Int, oldh : Int) {
		super.onSizeChanged(w, h, oldw, oldh)
		
		view_w = max(1f, w.toFloat())
		view_h = max(1f, h.toFloat())
		view_aspect = view_w / view_h
		
		initializeScale()
	}
	
	override fun performClick() : Boolean {
		super.performClick()
		
		initializeScale()
		
		return true
	}
	
	private var defaultScale : Float = 1f
	
	// 表示位置の初期化
	// 呼ばれるのは、ビットマップを変更した時、ビューのサイズが変わった時、画像をクリックした時
	private fun initializeScale() {
		val bitmap = this.bitmap
		if(bitmap != null && ! bitmap.isRecycled && view_w >= 1f) {
			
			bitmap_w = max(1f, bitmap.width.toFloat())
			bitmap_h = max(1f, bitmap.height.toFloat())
			bitmap_aspect = bitmap_w / bitmap_h
			
			if(view_aspect > bitmap_aspect) {
				scale_min = view_h / bitmap_h / 2f
				scale_max = view_w / bitmap_w * 8f
			} else {
				scale_min = view_w / bitmap_w / 2f
				scale_max = view_h / bitmap_h * 8f
			}
			if(scale_max < scale_min) scale_max = scale_min * 16f
			
			defaultScale = if(view_aspect > bitmap_aspect) {
				view_h / bitmap_h
			} else {
				view_w / bitmap_w
			}
			
			val draw_w = bitmap_w * defaultScale
			val draw_h = bitmap_h * defaultScale
			
			current_scale = defaultScale
			current_trans_x = (view_w - draw_w) / 2f
			current_trans_y = (view_h - draw_h) / 2f
			
			callback?.onMove(bitmap_w, bitmap_h, current_trans_x, current_trans_y, current_scale)
		} else {
			defaultScale = 1f
			scale_min = 1f
			scale_max = 1f
			
			current_scale = defaultScale
			current_trans_y = 0f
			current_trans_x = 0f
			
			callback?.onMove(0f, 0f, current_trans_x, current_trans_y, current_scale)
		}
		
		// 画像がnullに変化した時も再描画が必要
		invalidate()
	}
	
	@SuppressLint("ClickableViewAccessibility")
	override fun onTouchEvent(ev : MotionEvent) : Boolean {
		
		val bitmap = this.bitmap
		if(bitmap == null
			|| bitmap.isRecycled
			|| view_w < 1f)
			return false
		
		val action = ev.action
		
		if(action == MotionEvent.ACTION_DOWN) {
			time_touch_start = SystemClock.elapsedRealtime()
			
			velocityTracker?.clear()
			velocityTracker = VelocityTracker.obtain()
			velocityTracker?.addMovement(ev)
			
			bPointerCountChanged = false
			bDrag = bPointerCountChanged
			trackStart(ev)
			return true
		}
		
		velocityTracker?.addMovement(ev)
		
		when(action) {
			MotionEvent.ACTION_POINTER_DOWN, MotionEvent.ACTION_POINTER_UP -> {
				// タッチ操作中に指の数を変えた
				bPointerCountChanged = true
				bDrag = bPointerCountChanged
				trackStart(ev)
			}
			
			MotionEvent.ACTION_MOVE -> trackNext(ev)
			
			MotionEvent.ACTION_UP -> {
				trackNext(ev)
				
				checkClickOrPaging()
				
				velocityTracker?.recycle()
				velocityTracker = null
			}
		}
		return true
	}
	
	private fun checkClickOrPaging() {
		
		if(! bDrag) {
			// 指を動かしていないなら
			
			val now = SystemClock.elapsedRealtime()
			
			if(now - time_touch_start >= 1000L) {
				// ロングタップはタップカウントをリセットする
				log.d("click count reset by long tap")
				click_count = 0
				return
			}
			
			val delta = now - click_time
			click_time = now
			
			if(delta > 334L) {
				// 前回のタップからの時刻が長いとタップカウントをリセットする
				log.d("click count reset by long interval")
				click_count = 0
			}
			
			++ click_count
			
			log.d("click %d %d", click_count, delta)
			
			if(click_count >= 2) {
				// ダブルタップでクリック操作
				click_count = 0
				performClick()
			}
			
			return
		}
		
		click_count = 0
		
		val velocityTracker = this.velocityTracker
		if(! bPointerCountChanged && velocityTracker != null) {
			
			// 指の数を変えていないならページめくり操作かもしれない
			
			// 「画像を動かした」かどうかのチェック
			val image_moved = max(
				abs(current_trans_x - start_image_trans_x),
				abs(current_trans_y - start_image_trans_y)
			)
			if(image_moved >= drag_length) {
				log.d("image moved. not flick action. $image_moved")
				return
			}
			
			velocityTracker.computeCurrentVelocity(1000)
			val vx = velocityTracker.xVelocity
			val vy = velocityTracker.yVelocity
			val avx = abs(vx)
			val avy = abs(vy)
			val velocity = sqrt(vx * vx + vy * vy)
			val aspect = try {
				avx / avy
			} catch(ex : Throwable) {
				Float.MAX_VALUE
			}
			
			when {
				aspect >= 0.9f -> {
					// 指を動かした方向が左右だった
					
					val vMin = when {
						current_scale * bitmap_w <= view_w -> swipe_velocity2
						else -> swipe_velocity
					}
					
					if(velocity < vMin) {
						log.d("velocity $velocity not enough to pagingX")
						return
					}
					
					log.d("pagingX! m=$image_moved a=$aspect v=$velocity")
					runOnMainLooper { callback?.onSwipe(if(vx >= 0f) - 1 else 1, 0) }
				}
				
				aspect <= 0.333f -> {
					// 指を動かした方向が上下だった
					
					val vMin = when {
						current_scale * bitmap_h <= view_h -> swipe_velocity2
						else -> swipe_velocity
					}
					
					if(velocity < vMin) {
						log.d("velocity $velocity not enough to pagingY")
						return
					}
					
					log.d("pagingY! m=$image_moved a=$aspect v=$velocity")
					runOnMainLooper { callback?.onSwipe(0, if(vy >= 0f) - 1 else 1) }
				}
				
				else -> log.d("flick is not horizontal/vertical. aspect=$aspect")
			}
		}
	}
	
	// マルチタッチの中心位置の計算
	internal class PointerAvg {
		
		// タッチ位置の数
		var count : Int = 0
		
		// タッチ位置の平均
		val avg = FloatArray(2)
		
		// 中心と、中心から最も離れたタッチ位置の間の距離
		var max_radius : Float = 0.toFloat()
		
		fun update(ev : MotionEvent) {
			
			count = ev.pointerCount
			if(count <= 1) {
				avg[0] = ev.x
				avg[1] = ev.y
				max_radius = 0f
				
			} else {
				avg[0] = 0f
				avg[1] = 0f
				for(i in 0 until count) {
					avg[0] += ev.getX(i)
					avg[1] += ev.getY(i)
				}
				avg[0] /= count.toFloat()
				avg[1] /= count.toFloat()
				max_radius = 0f
				for(i in 0 until count) {
					val dx = ev.getX(i) - avg[0]
					val dy = ev.getY(i) - avg[1]
					val radius = dx * dx + dy * dy
					if(radius > max_radius) max_radius = radius
				}
				max_radius = sqrt(max_radius.toDouble()).toFloat()
				if(max_radius < 1f) max_radius = 1f
			}
		}
	}
	
	private fun trackStart(ev : MotionEvent) {
		
		// 追跡開始時の指の位置
		start_pos.update(ev)
		
		// 追跡開始時の画像の位置
		start_image_trans_x = current_trans_x
		start_image_trans_y = current_trans_y
		start_image_scale = current_scale
		
	}
	
	// 画面上の指の位置から画像中の指の位置を調べる
	private fun getCoordinateOnImage(dst : FloatArray, src : FloatArray) {
		tracking_matrix.reset()
		tracking_matrix.postScale(current_scale, current_scale)
		tracking_matrix.postTranslate(current_trans_x, current_trans_y)
		tracking_matrix.invert(tracking_matrix_inv)
		tracking_matrix_inv.mapPoints(dst, src)
	}
	
	private fun trackNext(ev : MotionEvent) {
		pos.update(ev)
		
		if(pos.count != start_pos.count) {
			// タッチ操作中に指の数が変わった
			log.d("nextTracking: pointer count changed")
			bPointerCountChanged = true
			bDrag = bPointerCountChanged
			trackStart(ev)
			return
		}
		
		// ズーム操作
		if(pos.count > 1) {
			
			// タッチ位置にある絵柄の座標を調べる
			getCoordinateOnImage(avg_on_image1, pos.avg)
			
			// ズーム率を変更する
			current_scale = clip(
				scale_min,
				scale_max,
				start_image_scale * pos.max_radius / start_pos.max_radius
			)
			
			// 再び調べる
			getCoordinateOnImage(avg_on_image2, pos.avg)
			
			// ズーム変更の前後で位置がズレた分だけ移動させると、タッチ位置にある絵柄がズレない
			start_image_trans_x += current_scale * (avg_on_image2[0] - avg_on_image1[0])
			start_image_trans_y += current_scale * (avg_on_image2[1] - avg_on_image1[1])
			
		}
		
		// 平行移動
		run {
			// start時から指を動かした量
			val move_x = pos.avg[0] - start_pos.avg[0]
			val move_y = pos.avg[1] - start_pos.avg[1]
			
			// 「指を動かした」と判断したらフラグを立てる
			if(abs(move_x) >= drag_length || abs(move_y) >= drag_length) {
				bDrag = true
			}
			
			// 画像の表示位置を更新
			current_trans_x =
				clipTranslate(view_w, bitmap_w, current_scale, start_image_trans_x + move_x)
			current_trans_y =
				clipTranslate(view_h, bitmap_h, current_scale, start_image_trans_y + move_y)
		}
		
		callback?.onMove(bitmap_w, bitmap_h, current_trans_x, current_trans_y, current_scale)
		invalidate()
	}
	
}
