// SPDX-FileCopyrightText: Adam Evyčędo
//
// SPDX-License-Identifier: GPL-3.0-or-later

package xyz.apiote.bimba.czwek.search

import android.content.Context
import android.content.Intent
import android.location.Location
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import xyz.apiote.bimba.czwek.R
import xyz.apiote.bimba.czwek.departures.DeparturesActivity
import xyz.apiote.bimba.czwek.repo.FeedInfo
import xyz.apiote.bimba.czwek.repo.Line
import xyz.apiote.bimba.czwek.repo.Queryable
import xyz.apiote.bimba.czwek.repo.Stop
import xyz.apiote.bimba.czwek.repo.StopStub
import xyz.apiote.bimba.czwek.settings.feeds.FeedsSettings
import xyz.apiote.bimba.czwek.units.Metre
import xyz.apiote.bimba.czwek.units.UnitSystem
import kotlin.math.abs

class BimbaViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
	val root: View = itemView.findViewById(R.id.suggestion)
	val icon: ImageView = itemView.findViewById(R.id.suggestion_image)
	val title: TextView = itemView.findViewById(R.id.suggestion_title)
	val description: TextView = itemView.findViewById(R.id.suggestion_description)
	val feedName: TextView = itemView.findViewById(R.id.feed_name)
	val distance: TextView = itemView.findViewById(R.id.distance)
	val arrow: ImageView = itemView.findViewById(R.id.arrow)

	companion object {
		fun bind(
			queryable: Queryable,
			holder: BimbaViewHolder?,
			context: Context?,
			feeds: Map<String, FeedInfo>?,
			feedsSettings: FeedsSettings?,
			onClickListener: (Queryable) -> Unit,
			position: Location?,
			heading: Float?,
			showArrow: Boolean
		) {
			when (queryable) {
				is Stop -> bindStop(
					queryable,
					holder,
					context,
					feeds,
					feedsSettings,
					position,
					heading,
					showArrow
				)

				is Line -> bindLine(queryable, holder, context, feeds, feedsSettings)
			}
			holder?.root?.setOnClickListener {
				onClickListener(queryable)
			}
		}

		fun bind(
			stopStub: StopStub,
			holder: BimbaViewHolder?,
			context: Context?,
			onClickListener: (StopStub) -> Unit
		) {
			holder?.title?.text = stopStub.name
			holder?.icon?.apply {
				setImageDrawable(stopStub.icon(context!!))
				contentDescription = context.getString(R.string.stop_content_description)
			}
			holder?.description?.text = when {
				stopStub.zone != "" && stopStub.onDemand -> context?.getString(
					R.string.stop_stub_on_demand_in_zone,
					stopStub.zone
				)

				stopStub.zone == "" && stopStub.onDemand -> context?.getString(R.string.stop_stub_on_demand)
				stopStub.zone != "" && !stopStub.onDemand -> context?.getString(
					R.string.stop_stub_in_zone,
					stopStub.zone
				)

				else -> ""
			}
			holder?.root?.setOnClickListener {
				onClickListener(stopStub)
			}
		}

		private fun bindStop(
			stop: Stop,
			holder: BimbaViewHolder?,
			context: Context?,
			feeds: Map<String, FeedInfo>?,
			feedsSettings: FeedsSettings?,
			position: Location?,
			heading: Float?,
			showArrow: Boolean
		) {

			if (showArrow && position != null && heading != null) {
				Location(null).apply {
					latitude = stop.position.latitude
					longitude = stop.position.longitude
				}.let {
					val angle =
						(360 + ((position.bearingTo(it) + 360).mod(360f)) - heading).mod(360f)
					val distance = position.distanceTo(it)
					holder?.arrow?.apply {
						setImageResource(R.drawable.arrow)
						rotation = angle
						visibility = View.VISIBLE
						contentDescription = "Arrow" // TODO
					}
					holder?.distance?.apply {
						val us = UnitSystem.getSelected(context!!)
						text = us.toString(context, us.distanceUnit(Metre(distance.toDouble())))
						contentDescription =
							us.distanceUnit(Metre(distance.toDouble())).contentDescription(context, us.base)
						visibility = View.VISIBLE
					}
				}
			} else {
				holder?.arrow?.visibility = View.GONE
				holder?.distance?.visibility = View.GONE
			}

			holder?.icon?.apply {
				setImageDrawable(stop.icon(context!!))
				contentDescription = context.getString(R.string.stop_content_description)
			}
			holder?.title?.text = stop.name
			if ((feedsSettings?.activeFeedsCount() ?: 0) > 1 || (stop.feedID ?: "") == "transitous") {
				holder?.feedName?.visibility = View.VISIBLE
				holder?.feedName?.text = feeds?.get(stop.feedID)?.name ?: ""
			} else {
				holder?.feedName?.visibility = View.GONE
			}
			context?.let {
				stop.changeOptions(it, Stop.LineDecoration.fromPreferences(context)).let { changeOptions ->
					holder?.description?.apply {
						text = changeOptions.first
						contentDescription = changeOptions.second
					}
				}
			}
		}

		private fun bindLine(
			line: Line,
			holder: BimbaViewHolder?,
			context: Context?,
			feeds: Map<String, FeedInfo>?,
			feedsSettings: FeedsSettings?
		) {
			holder?.icon?.apply {
				setImageDrawable(line.icon(context!!))
				contentDescription = line.type.name
				colorFilter = null
			}
			if ((feedsSettings?.activeFeedsCount() ?: 0) > 1) {
				holder?.feedName?.visibility = View.VISIBLE
				holder?.feedName?.text = feeds?.get(line.feedID)?.name ?: ""
			}
			holder?.title?.text = line.name
			holder?.description?.text = if (line.headsigns.size == 1) {
				context?.getString(
					R.string.line_headsign,
					line.headsigns[0].joinToString { it })
			} else {
				context?.getString(
					R.string.line_headsigns,
					line.headsigns[0].joinToString { it },
					line.headsigns[1].joinToString { it })
			}
			holder?.description?.contentDescription = if (line.headsigns.size == 1) {
				context?.getString(
					R.string.line_headsign_content_description,
					line.headsigns[0].joinToString { it })
			} else {
				context?.getString(
					R.string.line_headsigns_content_description,
					line.headsigns[0].joinToString { it },
					line.headsigns[1].joinToString { it })
			}
		}
	}
}


class BimbaResultsAdapter(
	private val inflater: LayoutInflater,
	private val context: Context?,
	private var queryables: List<Queryable>,
	private var position: Location?,
	private var heading: Float?,
	private var showArrow: Boolean
) :
	RecyclerView.Adapter<BimbaViewHolder>() {
	class DiffUtilCallback(
		private val oldQueryables: List<Queryable>,
		private val newQueryables: List<Queryable>,
		private val oldPosition: Location?,
		private val newPosition: Location?,
		private val oldHeading: Float?,
		private val newHeading: Float?,
		private val oldShowArrow: Boolean,
		private val newShowArrow: Boolean
	) : DiffUtil.Callback() {
		override fun getOldListSize() = oldQueryables.size

		override fun getNewListSize() = newQueryables.size

		override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
			val oldQueryable = oldQueryables[oldItemPosition]
			val newQueryable = newQueryables[newItemPosition]
			return if (oldQueryable::class.java != newQueryable::class.java) {
				false
			} else {
				when (oldQueryable) {
					is Line -> {
						assert(newQueryable is Line)
						oldQueryable.name == (newQueryable as Line).name // TODO compare line.ID when struct is updated
					}

					is Stop -> {
						assert(newQueryable is Stop)
						oldQueryable.code == (newQueryable as Stop).code
					}

					else -> false // XXX unreachable
				}
			}
		}

		override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
			val oldQueryable = oldQueryables[oldItemPosition]
			val newQueryable = newQueryables[newItemPosition]
			return when (oldQueryable) {
				is Line -> {
					assert(newQueryable is Line)
					val oldHeadsigns =
						oldQueryable.headsigns.joinToString { hsList -> hsList.joinToString { it } }
					val newHeadsigns =
						(newQueryable as Line).headsigns.joinToString { hsList -> hsList.joinToString { it } }

					oldQueryable.name == newQueryable.name && oldQueryable.type == newQueryable.type &&
						oldQueryable.colour == newQueryable.colour && oldHeadsigns == newHeadsigns
				}

				is Stop -> {
					assert(newQueryable is Stop)
					val oldChangeOptions = oldQueryable.changeOptionsString()
					val newChangeOptions = (newQueryable as Stop).changeOptionsString()
					oldQueryable.name == newQueryable.name && oldChangeOptions == newChangeOptions &&
						oldPosition?.latitude == newPosition?.latitude &&
						oldPosition?.longitude == newPosition?.longitude &&
						oldHeading == newHeading &&
						oldShowArrow == newShowArrow
				}

				else -> false // XXX unreachable
			}
		}

	}

	var feeds: Map<String, FeedInfo>? = null
	var feedsSettings: FeedsSettings? = null
	private val onClickListener: ((Queryable) -> Unit) = {
		when (it) {
			is Stop -> {
				val intent = Intent(context, DeparturesActivity::class.java).apply {
					putExtra("code", it.code)
					putExtra("name", it.name)
					putExtra("feedID", it.feedID)
				}
				context!!.startActivity(intent)
			}

			is Line -> {
				val intent = Intent(context, LineGraphActivity::class.java).apply {
					putExtra("lineName", it.name)
					putExtra("lineID", it.id)
					putExtra("feedID", it.feedID)
				}
				context!!.startActivity(intent)
			}
		}
	}

	override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BimbaViewHolder {
		val rowView = inflater.inflate(R.layout.result, parent, false)
		return BimbaViewHolder(rowView)
	}

	override fun onBindViewHolder(holder: BimbaViewHolder, position: Int) {
		BimbaViewHolder.bind(
			queryables[position],
			holder,
			context,
			feeds,
			feedsSettings,
			onClickListener,
			this.position,
			heading,
			showArrow
		)
	}

	override fun getItemCount(): Int = queryables.size

	fun update(
		queryables: List<Queryable>?,
		position: Location?,
		heading: Float?,
		showArrow: Boolean
	) {
		val diff = DiffUtil.calculateDiff(
			DiffUtilCallback(
				this.queryables,
				queryables ?: emptyList(),
				this.position,
				position,
				this.heading,
				heading,
				this.showArrow,
				showArrow
			)
		)
		this.position = position
		this.heading = heading
		this.showArrow = showArrow
		this.queryables = queryables ?: emptyList()
		diff.dispatchUpdatesTo(this)
	}

	fun update(
		heading: Float?,
	) {
		if (abs((heading ?: 0f) - (this.heading ?: 0f)) < 15) {
			return
		}
		val diff = DiffUtil.calculateDiff(
			DiffUtilCallback(
				queryables,
				queryables,
				position,
				position,
				this.heading,
				heading,
				showArrow,
				showArrow
			)
		)
		this.heading = heading
		diff.dispatchUpdatesTo(this)
	}

	fun update(
		queryables: List<Queryable>?,
		position: Location?,
		showArrow: Boolean
	) {
		val diff = DiffUtil.calculateDiff(
			DiffUtilCallback(
				this.queryables,
				queryables ?: emptyList(),
				this.position,
				position,
				heading,
				heading,
				this.showArrow,
				showArrow
			)
		)
		this.position = position
		this.showArrow = showArrow
		this.queryables = queryables ?: emptyList()
		diff.dispatchUpdatesTo(this)
	}

	fun click(position: Int) {
		onClickListener(queryables[position])
	}
}