# RecyclerAdapterView.kt

    /*
     * The MIT License
     *
     * Original work sponsored and donated by The Danish Health Data Authority (http://www.sundhedsdatastyrelsen.dk)
     *
     * Copyright (C) 2021 The Danish Health Data Authority (http://www.sundhedsdatastyrelsen.dk)
     *
     *
     * Permission is hereby granted, free of charge, to any person obtaining a copy of
     * this software and associated documentation files (the "Software"), to deal in
     * the Software without restriction, including without limitation the rights to
     * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
     * of the Software, and to permit persons to whom the Software is furnished to do
     * so, subject to the following conditions:
     *
     *
     * The above copyright notice and this permission notice shall be included in all
     * copies or substantial portions of the Software.
     *
     *
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     * SOFTWARE.
     */

    package dk.nsp.mingraviditet.util.android.recycler

    import android.view.LayoutInflater
    import android.view.ViewGroup
    import androidx.recyclerview.widget.DiffUtil
    import androidx.recyclerview.widget.ListAdapter
    import androidx.recyclerview.widget.RecyclerView
    import androidx.viewbinding.ViewBinding
    import com.commonsense.android.kotlin.base.extensions.use
    import com.commonsense.android.kotlin.base.extensions.weakReference
    import java.lang.ref.WeakReference

    typealias DelegationListAdapterInflater<UI> = (inflater: LayoutInflater, parent: ViewGroup?, attachToParent: Boolean) -> UI

    /**
     *
     * Implementers should make their classes data classes to have the equals method generated that is used to compare content (thus the data should be equatable as well)
     * @param UI : [ViewBinding] the viewBinding type we are using.
     */
    interface RecyclerAdapterView<UI : ViewBinding> {
        /**
         * bind your data to the viewHolders.binding
         * @param viewHolder [BindingViewHolder]
         */
        fun bindData(viewHolder: BindingViewHolder<UI>)

        /**
         * For example, just take a viewBinding's "inflate" function, so "MyViewBinding::inflate" is the result
         */
        val createHolderFunction: DelegationListAdapterInflater<UI>

        fun bindDataInternal(viewHolder: BindingViewHolder<*>) {
            @Suppress("UNCHECKED_CAST") ///we assume we are called correctly. otherwise we will throw ClassCastException.
            //we have no way of verifying the types(viewHolder to recycler delegation is not possible).
            bindData(viewHolder as BindingViewHolder<UI>)
        }

        /**
         * test whenever the full content is the same
         * @param other RecyclerDelegate<*>
         * @return Boolean
         */
        fun isContentTheSameAsInternal(other: RecyclerAdapterView<*>): Boolean {
            return if (other::class.java == this::class.java) {
                @Suppress("UNCHECKED_CAST") //we have just compared the classes.
                this == other
            } else {
                false
            }
        }

        /**
         * fast comparing,so only test "basics" (the type is the same and or "id's" are the same).
         * @param other [RecyclerAdapterView]
         */
        fun isSameType(other: RecyclerAdapterView<*>): Boolean {
            return other::class.java.hashCode() == this::class.java.hashCode()
        }
    }

    /**
     * wraps the binding so delegates can access the binding.
     * @param UI : ViewBinding
     * @property binding UI
     * @constructor
     */
    class BindingViewHolder<UI : ViewBinding>(
        val binding: UI,
        private val adapter: WeakReference<DelegatingListAdapter>
    ) : RecyclerView.ViewHolder(binding.root) {

        /**
         * Allows the recycler to update this entry (say you modified the UI)
         * This can also trigger animations iff needed.
         */
        fun notifyThisUpdated() = adapter.use {
            notifyItemChanged(adapterPosition)
        }
    }

    /**
     * delegates the comparison to the delegate.
     */
    object DelegatingListAdapterDiffCompare : DiffUtil.ItemCallback<RecyclerAdapterView<*>>() {
        override fun areItemsTheSame(
            oldItem: RecyclerAdapterView<*>,
            newItem: RecyclerAdapterView<*>
        ): Boolean {
            return oldItem.isSameType(newItem)
        }

        override fun areContentsTheSame(
            oldItem: RecyclerAdapterView<*>,
            newItem: RecyclerAdapterView<*>
        ): Boolean {
            return oldItem.isContentTheSameAsInternal(newItem)
        }

    }

    class DelegatingListAdapter : ListAdapter<RecyclerAdapterView<*>, BindingViewHolder<*>>(
        DelegatingListAdapterDiffCompare
    ) {

        /**
         *  We use the hashcode of a given [RecyclerAdapterView] to be the "type" of the view.
         *  This means that we have a type to a delegated inflater.
         *  (this is used to create a [RecyclerView.ViewHolder], that then combines all necessary information)
         */
        private val typeToViewHolderConstructor: HashMap<Int, DelegationListAdapterInflater<ViewBinding>> =
            hashMapOf()

        //to improve performance
        private var cachedInflater: LayoutInflater? = null

        override fun getItemViewType(position: Int): Int {
            return getItem(position)?.let {
                //get the type's hashcode (our cool "view type")
                val hashCode = it::class.java.hashCode()
                //bind that type to a creator function.
                typeToViewHolderConstructor[hashCode] = it.createHolderFunction
                //use the class hashcode as result (to viewType)
                hashCode
            } ?: super.getItemViewType(position)
        }

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder<*> {
            //take the given viewType ( delegate class hashcode)
            return typeToViewHolderConstructor[viewType]?.let {
                val inflater = parent.getInflaterCached()
                //create the view holder which creates a "view" (by the delegate class's type)
                BindingViewHolder(it(inflater, parent, false), this.weakReference())
            } ?: throw IllegalArgumentException(
                "No ViewHolder impl found for class type=$viewType"
            )
        }

        override fun onBindViewHolder(holder: BindingViewHolder<*>, position: Int) {
            //call the delegated "bindDataInternal" that casts the type to the correct type (we do not know here)
            getItem(position)?.bindDataInternal(holder)
        }

        /**
         * Caches a context's [LayoutInflater], to avoid repeat accessing services
         * @receiver ViewGroup
         * @return LayoutInflater
         */
        fun ViewGroup.getInflaterCached(): LayoutInflater {
            return cachedInflater ?: LayoutInflater.from(context).apply {
                cachedInflater = this
            }
        }

        @Deprecated(
            "you should not set stable ids for a delegating recycler",
            level = DeprecationLevel.ERROR,
            replaceWith = ReplaceWith("")
        )
        override fun setHasStableIds(hasStableIds: Boolean) {
            throw RuntimeException("Delegating recycler does not have stable ids so you cannot enable that, also there is no reason for it.")
        }
    }