/*
 *             Twidere - Twitter client for Android
 *
 *  Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.mariotaku.twidere.adapter

import android.text.TextUtils
import android.text.method.LinkMovementMethod
import android.util.SparseBooleanArray
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Space
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.contains
import org.mariotaku.microblog.library.twitter.model.TranslationResult
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.iface.IGapSupportedAdapter
import org.mariotaku.twidere.adapter.iface.IItemCountsAdapter
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.adapter.iface.IStatusesAdapter
import org.mariotaku.twidere.constant.*
import org.mariotaku.twidere.extension.model.originalId
import org.mariotaku.twidere.extension.model.retweet_sort_id
import org.mariotaku.twidere.fragment.status.StatusFragment
import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.util.StatusAdapterLinkClickHandler
import org.mariotaku.twidere.util.ThemeUtils
import org.mariotaku.twidere.util.TwidereLinkify
import org.mariotaku.twidere.view.holder.EmptyViewHolder
import org.mariotaku.twidere.view.holder.LoadIndicatorViewHolder
import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder
import org.mariotaku.twidere.view.holder.status.DetailStatusViewHolder

class StatusDetailsAdapter(
        val fragment: StatusFragment
) : LoadMoreSupportAdapter<RecyclerView.ViewHolder>(fragment.context!!, fragment.requestManager),
        IStatusesAdapter<List<ParcelableStatus>>, IItemCountsAdapter {

    override val twidereLinkify: TwidereLinkify

    override var statusClickListener: IStatusViewHolder.StatusClickListener? = null

    override val itemCounts = ItemCounts(ITEM_TYPES_SUM)

    override val nameFirst = preferences[nameFirstKey]
    override val mediaPreviewStyle = preferences[mediaPreviewStyleKey]
    override val linkHighlightingStyle = preferences[linkHighlightOptionKey]
    override val lightFont = preferences[lightFontKey]
    override val mediaPreviewEnabled = preferences[mediaPreviewKey]
    override val sensitiveContentEnabled = preferences[displaySensitiveContentsKey]
    override val useStarsForLikes = preferences[iWantMyStarsBackKey]

    private val inflater: LayoutInflater
    private val cardBackgroundColor: Int
    private val showCardActions = !preferences[hideCardActionsKey]
    private val showCardNumbers = !preferences[hideCardNumbersKey]
    private val showLinkPreview = preferences[showLinkPreviewKey]
    private var recyclerView: RecyclerView? = null
    private var detailMediaExpanded: Boolean = false

    var status: ParcelableStatus? = null
        internal set
    var translationResult: TranslationResult? = null
        internal set(translation) {
            if (translation == null || status?.originalId != translation.id) {
                field = null
            } else {
                field = translation
            }
            notifyDataSetChanged()
        }
    var statusActivity: StatusFragment.StatusActivity? = null
        internal set(value) {
            val status = status ?: return
            if (value != null && !value.isStatus(status)) {
                return
            }
            field = value
            val statusIndex = getIndexStart(ITEM_IDX_STATUS)
            notifyItemChanged(statusIndex, value)
        }
    var statusAccount: AccountDetails? = null
        internal set

    private var data: List<ParcelableStatus>? = null
    private var replyError: CharSequence? = null
    private var conversationError: CharSequence? = null
    private var replyStart: Int = 0
    private var showingActionCardPosition = RecyclerView.NO_POSITION
    private val showingFullTextStates = SparseBooleanArray()

    init {
        setHasStableIds(true)
        val context = fragment.activity
        // There's always a space at the end of the list
        itemCounts[ITEM_IDX_SPACE] = 1
        itemCounts[ITEM_IDX_STATUS] = 1
        itemCounts[ITEM_IDX_CONVERSATION_LOAD_MORE] = 1
        itemCounts[ITEM_IDX_REPLY_LOAD_MORE] = 1
        inflater = LayoutInflater.from(context)
        cardBackgroundColor = ThemeUtils.getCardBackgroundColor(context!!,
                preferences[themeBackgroundOptionKey], preferences[themeBackgroundAlphaKey])
        val listener = StatusAdapterLinkClickHandler<List<ParcelableStatus>>(context!!, preferences)
        listener.setAdapter(this)
        twidereLinkify = TwidereLinkify(listener)
    }

    override fun getStatus(position: Int, raw: Boolean): ParcelableStatus {
        when (getItemCountIndex(position, raw)) {
            ITEM_IDX_CONVERSATION -> {
                var idx = position - getIndexStart(ITEM_IDX_CONVERSATION)
                if (data!![idx].is_filtered) idx++
                return data!![idx]
            }
            ITEM_IDX_REPLY -> {
                var idx = position - getIndexStart(ITEM_IDX_CONVERSATION) -
                        getTypeCount(ITEM_IDX_CONVERSATION) - getTypeCount(ITEM_IDX_STATUS) +
                        replyStart
                if (data!![idx].is_filtered) idx++
                return data!![idx]
            }
            ITEM_IDX_STATUS -> {
                return status!!
            }
        }
        throw IndexOutOfBoundsException("index: $position")
    }

    fun getIndexStart(index: Int): Int {
        if (index == 0) return 0
        return itemCounts.getItemStartPosition(index)
    }

    override fun getStatusId(position: Int, raw: Boolean): String {
        return getStatus(position, raw).id
    }

    override fun getStatusTimestamp(position: Int, raw: Boolean): Long {
        return getStatus(position, raw).timestamp
    }

    override fun getStatusPositionKey(position: Int, raw: Boolean): Long {
        val status = getStatus(position, raw)
        return if (status.position_key > 0) status.timestamp else getStatusTimestamp(position, raw)
    }

    override fun getAccountKey(position: Int, raw: Boolean) = getStatus(position, raw).account_key

    override fun findStatusById(accountKey: UserKey, statusId: String): ParcelableStatus? {
        if (status != null && accountKey == status!!.account_key && TextUtils.equals(statusId, status!!.id)) {
            return status
        }
        return data?.firstOrNull { accountKey == it.account_key && TextUtils.equals(it.id, statusId) }
    }

    override fun getStatusCount(raw: Boolean): Int {
        return getTypeCount(ITEM_IDX_CONVERSATION) + getTypeCount(ITEM_IDX_STATUS) + getTypeCount(ITEM_IDX_REPLY)
    }

    override fun isCardNumbersShown(position: Int): Boolean {
        if (position == RecyclerView.NO_POSITION) return showCardNumbers
        return showCardNumbers || showingActionCardPosition == position
    }

    override fun isLinkPreviewShown(position: Int): Boolean {
        return showLinkPreview
    }

    override fun isCardActionsShown(position: Int): Boolean {
        if (position == RecyclerView.NO_POSITION) return showCardActions
        return showCardActions || showingActionCardPosition == position
    }

    override fun showCardActions(position: Int) {
        if (showingActionCardPosition != RecyclerView.NO_POSITION) {
            notifyItemChanged(showingActionCardPosition)
        }
        showingActionCardPosition = position
        if (position != RecyclerView.NO_POSITION) {
            notifyItemChanged(position)
        }
    }

    override fun isFullTextVisible(position: Int): Boolean {
        return showingFullTextStates.get(position)
    }

    override fun setFullTextVisible(position: Int, visible: Boolean) {
        showingFullTextStates.put(position, visible)
        if (position != RecyclerView.NO_POSITION) {
            notifyItemChanged(position)
        }
    }

    override fun setData(data: List<ParcelableStatus>?): Boolean {
        val status = this.status ?: return false
        val changed = this.data != data
        this.data = data
        if (data == null || data.isEmpty()) {
            setTypeCount(ITEM_IDX_CONVERSATION, 0)
            setTypeCount(ITEM_IDX_REPLY, 0)
            replyStart = -1
        } else {
            val sortId = if (status.is_retweet) {
                status.retweet_sort_id
            } else {
                status.sort_id
            }

            var conversationCount = 0
            var replyCount = 0
            var replyStart = -1
            data.forEachIndexed { i, item ->
                if (item.sort_id < sortId) {
                    if (!item.is_filtered) {
                        conversationCount++
                    }
                } else if (status.id == item.id) {
                    this.status = item
                } else if (item.sort_id > sortId) {
                    if (replyStart < 0) {
                        replyStart = i
                    }
                    if (!item.is_filtered) {
                        replyCount++
                    }
                }
            }
            setTypeCount(ITEM_IDX_CONVERSATION, conversationCount)
            setTypeCount(ITEM_IDX_REPLY, replyCount)
            this.replyStart = replyStart
        }
        notifyDataSetChanged()
        updateItemDecoration()
        return changed
    }

    override val showAccountsColor: Boolean
        get() = false

    var isDetailMediaExpanded: Boolean
        get() {
            if (detailMediaExpanded) return true
            if (mediaPreviewEnabled) {
                val status = this.status
                return status != null && (sensitiveContentEnabled || !status.is_possibly_sensitive)
            }
            return false
        }
        set(expanded) {
            detailMediaExpanded = expanded
            notifyDataSetChanged()
            updateItemDecoration()
        }

    override fun isGapItem(position: Int): Boolean {
        return false
    }

    override val gapClickListener: IGapSupportedAdapter.GapClickListener?
        get() = statusClickListener


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        when (viewType) {
            VIEW_TYPE_DETAIL_STATUS -> {
                val view = inflater.inflate(R.layout.header_status, parent, false)
                view.setBackgroundColor(cardBackgroundColor)
                return DetailStatusViewHolder(this, view)
            }
            VIEW_TYPE_LIST_STATUS -> {
                return ListParcelableStatusesAdapter.createStatusViewHolder(this, inflater, parent)
            }
            VIEW_TYPE_CONVERSATION_LOAD_INDICATOR, VIEW_TYPE_REPLIES_LOAD_INDICATOR -> {
                val view = inflater.inflate(R.layout.list_item_load_indicator, parent,
                        false)
                return LoadIndicatorViewHolder(view)
            }
            VIEW_TYPE_SPACE -> {
                return EmptyViewHolder(Space(context))
            }
            VIEW_TYPE_REPLY_ERROR -> {
                val view = inflater.inflate(R.layout.adapter_item_status_error, parent,
                        false)
                return StatusErrorItemViewHolder(view)
            }
        }
        return EmptyViewHolder(View(context))
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: List<Any>) {
        var handled = false
        when (holder.itemViewType) {
            VIEW_TYPE_DETAIL_STATUS -> {
                holder as DetailStatusViewHolder
                payloads.forEach { it ->
                    when (it) {
                        is StatusFragment.StatusActivity -> {
                            holder.updateStatusActivity(it)
                        }
                        is ParcelableStatus -> {
                            holder.displayStatus(statusAccount, status, statusActivity,
                                    translationResult)
                        }
                    }
                    handled = true
                }
            }
        }
        if (handled) return
        super.onBindViewHolder(holder, position, payloads)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder.itemViewType) {
            VIEW_TYPE_DETAIL_STATUS -> {
                val status = getStatus(position)
                val detailHolder = holder as DetailStatusViewHolder
                detailHolder.displayStatus(statusAccount, status, statusActivity, translationResult)
            }
            VIEW_TYPE_LIST_STATUS -> {
                val status = getStatus(position)
                val statusHolder = holder as IStatusViewHolder
                // Display 'in reply to' for first item
                // useful to indicate whether first tweet has reply or not
                // We only display that indicator for first conversation item
                val itemType = getItemType(position)
                val displayInReplyTo = itemType == ITEM_IDX_CONVERSATION && position - getItemTypeStart(position) == 0
                statusHolder.display(status = status, displayInReplyTo = displayInReplyTo)
            }
            VIEW_TYPE_REPLY_ERROR -> {
                val errorHolder = holder as StatusErrorItemViewHolder
                errorHolder.showError(replyError!!)
            }
            VIEW_TYPE_CONVERSATION_ERROR -> {
                val errorHolder = holder as StatusErrorItemViewHolder
                errorHolder.showError(conversationError!!)
            }
            VIEW_TYPE_CONVERSATION_LOAD_INDICATOR -> {
                val indicatorHolder = holder as LoadIndicatorViewHolder
                indicatorHolder.setLoadProgressVisible(isConversationsLoading)
            }
            VIEW_TYPE_REPLIES_LOAD_INDICATOR -> {
                val indicatorHolder = holder as LoadIndicatorViewHolder
                indicatorHolder.setLoadProgressVisible(isRepliesLoading)
            }
        }
    }

    override fun getItemViewType(position: Int): Int {
        return getItemViewTypeByItemType(getItemType(position))
    }

    override fun addGapLoadingId(id: ObjectId) {

    }

    override fun removeGapLoadingId(id: ObjectId) {

    }

    private fun getItemViewTypeByItemType(type: Int): Int {
        when (type) {
            ITEM_IDX_CONVERSATION, ITEM_IDX_REPLY -> return VIEW_TYPE_LIST_STATUS
            ITEM_IDX_CONVERSATION_LOAD_MORE -> return VIEW_TYPE_CONVERSATION_LOAD_INDICATOR
            ITEM_IDX_REPLY_LOAD_MORE -> return VIEW_TYPE_REPLIES_LOAD_INDICATOR
            ITEM_IDX_STATUS -> return VIEW_TYPE_DETAIL_STATUS
            ITEM_IDX_SPACE -> return VIEW_TYPE_SPACE
            ITEM_IDX_REPLY_ERROR -> return VIEW_TYPE_REPLY_ERROR
            ITEM_IDX_CONVERSATION_ERROR -> return VIEW_TYPE_CONVERSATION_ERROR
        }
        throw IllegalStateException()
    }

    private fun getItemCountIndex(position: Int, raw: Boolean): Int {
        return itemCounts.getItemCountIndex(position)
    }

    fun getItemType(position: Int): Int {
        var typeStart = 0
        for (type in 0 until ITEM_TYPES_SUM) {
            val typeCount = getTypeCount(type)
            val typeEnd = typeStart + typeCount
            if (position in typeStart until typeEnd) return type
            typeStart = typeEnd
        }
        throw IllegalStateException("Unknown position " + position)
    }

    fun getItemTypeStart(position: Int): Int {
        var typeStart = 0
        for (type in 0 until ITEM_TYPES_SUM) {
            val typeCount = getTypeCount(type)
            val typeEnd = typeStart + typeCount
            if (position in typeStart until typeEnd) return typeStart
            typeStart = typeEnd
        }
        throw IllegalStateException()
    }

    override fun getItemId(position: Int): Long {
        val countIndex = getItemCountIndex(position)
        when (countIndex) {
            ITEM_IDX_CONVERSATION, ITEM_IDX_STATUS, ITEM_IDX_REPLY -> {
                val status = getStatus(position)
                val hashCode = ParcelableStatus.calculateHashCode(status.account_key, status.id)
                return (countIndex.toLong() shl 32) or hashCode.toLong()
            }
        }
        val countPos = (position - getItemStartPosition(countIndex)).toLong()
        return (countIndex.toLong() shl 32) or countPos
    }

    override fun getItemCount(): Int {
        if (status == null) return 0
        return itemCounts.itemCount
    }

    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
        super.onAttachedToRecyclerView(recyclerView)
        this.recyclerView = recyclerView
    }

    override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
        super.onDetachedFromRecyclerView(recyclerView)
        this.recyclerView = null
    }

    private fun setTypeCount(idx: Int, size: Int) {
        itemCounts[idx] = size
        notifyDataSetChanged()
    }

    fun getTypeCount(idx: Int): Int {
        return itemCounts[idx]
    }

    fun setReplyError(error: CharSequence?) {
        replyError = error
        setTypeCount(ITEM_IDX_REPLY_ERROR, if (error != null) 1 else 0)
        updateItemDecoration()
    }

    fun setConversationError(error: CharSequence?) {
        conversationError = error
        setTypeCount(ITEM_IDX_CONVERSATION_ERROR, if (error != null) 1 else 0)
        updateItemDecoration()
    }

    fun setStatus(status: ParcelableStatus, account: AccountDetails?): Boolean {
        val oldStatus = this.status
        val oldAccount = this.statusAccount
        val changed = oldStatus != status && oldAccount != account
        this.status = status
        this.statusAccount = account
        if (changed) {
            notifyDataSetChanged()
            updateItemDecoration()
        } else {
            val statusIndex = getIndexStart(ITEM_IDX_STATUS)
            notifyItemChanged(statusIndex, status)
        }
        return changed
    }

    fun updateItemDecoration() {
        if (recyclerView == null) return
    }

    fun getFirstPositionOfItem(itemIdx: Int): Int {
        var position = 0
        for (i in 0 until ITEM_TYPES_SUM) {
            if (itemIdx == i) return position
            position += getTypeCount(i)
        }
        return RecyclerView.NO_POSITION
    }


    fun getData(): List<ParcelableStatus>? {
        return data
    }

    var isConversationsLoading: Boolean
        get() = ILoadMoreSupportAdapter.START in loadMoreIndicatorPosition
        set(loading) {
            if (loading) {
                loadMoreIndicatorPosition = loadMoreIndicatorPosition or ILoadMoreSupportAdapter.START
            } else {
                loadMoreIndicatorPosition = loadMoreIndicatorPosition and ILoadMoreSupportAdapter.START.inv()
            }
            updateItemDecoration()
        }

    var isRepliesLoading: Boolean
        get() = ILoadMoreSupportAdapter.END in loadMoreIndicatorPosition
        set(loading) {
            if (loading) {
                loadMoreIndicatorPosition = loadMoreIndicatorPosition or ILoadMoreSupportAdapter.END
            } else {
                loadMoreIndicatorPosition = loadMoreIndicatorPosition and ILoadMoreSupportAdapter.END.inv()
            }
            updateItemDecoration()
        }

    class StatusErrorItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val textView = itemView.findViewById<TextView>(android.R.id.text1)

        init {
            textView.movementMethod = LinkMovementMethod.getInstance()
            textView.linksClickable = true
        }

        fun showError(text: CharSequence) {
            textView.text = text
        }
    }

    companion object {

        const val VIEW_TYPE_LIST_STATUS = 0
        const val VIEW_TYPE_DETAIL_STATUS = 1
        const val VIEW_TYPE_CONVERSATION_LOAD_INDICATOR = 2
        const val VIEW_TYPE_REPLIES_LOAD_INDICATOR = 3
        const val VIEW_TYPE_REPLY_ERROR = 4
        const val VIEW_TYPE_CONVERSATION_ERROR = 5
        const val VIEW_TYPE_SPACE = 6
        const val VIEW_TYPE_EMPTY = 7

        const val ITEM_IDX_CONVERSATION_LOAD_MORE = 0
        const val ITEM_IDX_CONVERSATION_ERROR = 1
        const val ITEM_IDX_CONVERSATION = 2
        const val ITEM_IDX_STATUS = 3
        const val ITEM_IDX_REPLY = 4
        const val ITEM_IDX_REPLY_ERROR = 5
        const val ITEM_IDX_REPLY_LOAD_MORE = 6
        const val ITEM_IDX_SPACE = 7
        const val ITEM_TYPES_SUM = 8
    }
}