/*
 *                 Twidere - Twitter client for Android
 *
 *  Copyright (C) 2012-2015 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.content.Context
import android.database.CursorIndexOutOfBoundsException
import android.util.SparseBooleanArray
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.legacy.widget.Space
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.RequestManager
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.*
import org.mariotaku.library.objectcursor.ObjectCursor
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.iface.IGapSupportedAdapter
import org.mariotaku.twidere.adapter.iface.IGapSupportedAdapter.Companion.ITEM_VIEW_TYPE_GAP
import org.mariotaku.twidere.adapter.iface.IItemCountsAdapter
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter.Companion.ITEM_VIEW_TYPE_LOAD_INDICATOR
import org.mariotaku.twidere.adapter.iface.IStatusesAdapter
import org.mariotaku.twidere.annotation.PreviewStyle
import org.mariotaku.twidere.constant.*
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_DISPLAY_SENSITIVE_CONTENTS
import org.mariotaku.twidere.exception.UnsupportedCountIndexException
import org.mariotaku.twidere.model.ItemCounts
import org.mariotaku.twidere.model.ObjectId
import org.mariotaku.twidere.model.ParcelableStatus
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.timeline.TimelineFilter
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
import org.mariotaku.twidere.util.StatusAdapterLinkClickHandler
import org.mariotaku.twidere.util.TwidereLinkify
import org.mariotaku.twidere.util.Utils
import org.mariotaku.twidere.view.holder.EmptyViewHolder
import org.mariotaku.twidere.view.holder.GapViewHolder
import org.mariotaku.twidere.view.holder.LoadIndicatorViewHolder
import org.mariotaku.twidere.view.holder.TimelineFilterHeaderViewHolder
import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder
import java.util.*

/**
 * Created by mariotaku on 15/10/26.
 */
abstract class ParcelableStatusesAdapter(
        context: Context,
        requestManager: RequestManager
) : LoadMoreSupportAdapter<RecyclerView.ViewHolder>(context, requestManager),
        IStatusesAdapter<List<ParcelableStatus>>, IItemCountsAdapter {

    protected val inflater: LayoutInflater = LayoutInflater.from(context)

    final override val twidereLinkify: TwidereLinkify
    @PreviewStyle
    final override val mediaPreviewStyle: Int = preferences[mediaPreviewStyleKey]
    final override val nameFirst: Boolean = preferences[nameFirstKey]
    final override val useStarsForLikes: Boolean = preferences[iWantMyStarsBackKey]
    @TwidereLinkify.HighlightStyle
    final override val linkHighlightingStyle: Int = preferences[linkHighlightOptionKey]
    final override val lightFont: Boolean = preferences[lightFontKey]
    final override val mediaPreviewEnabled: Boolean = Utils.isMediaPreviewEnabled(context, preferences)
    final override val sensitiveContentEnabled: Boolean = preferences.getBoolean(KEY_DISPLAY_SENSITIVE_CONTENTS, false)
    private val showCardActions: Boolean = !preferences[hideCardActionsKey]
    private val showCardNumbers: Boolean = !preferences[hideCardNumbersKey]
    private val showLinkPreview: Boolean = preferences[showLinkPreviewKey]

    private val gapLoadingIds: MutableSet<ObjectId> = HashSet()

    override var statusClickListener: IStatusViewHolder.StatusClickListener? = null

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

    override var showAccountsColor: Boolean = false
        set(value) {
            if (field == value) return
            field = value
            notifyDataSetChanged()
        }

    var isShowInReplyTo: Boolean = false
        set(value) {
            if (field == value) return
            field = value
            notifyDataSetChanged()
        }

    var pinnedStatuses: List<ParcelableStatus>? = null
        set(value) {
            field = value
            value?.forEach { it.is_pinned_status = true }
            updateItemCount()
            notifyDataSetChanged()
        }

    var timelineFilter: TimelineFilter? = null
        set(value) {
            field = value
            notifyDataSetChanged()
        }

    private var data: List<ParcelableStatus>? = null
    private var displayPositions: IntArray? = null
    private var displayDataCount: Int = 0
    private var showingActionCardId = RecyclerView.NO_ID
    private val showingFullTextStates = SparseBooleanArray()
    private val reuseStatus = ParcelableStatus()
    private var infoCache: Array<StatusInfo?>? = null

    override val itemCounts = ItemCounts(5)

    val statusStartIndex: Int
        get() = getItemStartPosition(ITEM_INDEX_STATUS)

    override var loadMoreIndicatorPosition: Long
        get() = super.loadMoreIndicatorPosition
        set(value) {
            super.loadMoreIndicatorPosition = value
            updateItemCount()
        }

    override var loadMoreSupportedPosition: Long
        get() = super.loadMoreSupportedPosition
        set(value) {
            super.loadMoreSupportedPosition = value
            updateItemCount()
        }

    init {
        val handler = StatusAdapterLinkClickHandler<List<ParcelableStatus>>(context, preferences)
        twidereLinkify = TwidereLinkify(handler)
        handler.setAdapter(this)
        isShowInReplyTo = true
        setHasStableIds(true)
    }

    override fun isGapItem(position: Int): Boolean {
        return getFieldValue(position, { info ->
            return@getFieldValue info.gap
        }, { status ->
            return@getFieldValue status.is_gap
        }, false)
    }

    override fun getStatus(position: Int, raw: Boolean): ParcelableStatus {
        return getStatusInternal(position, getItemCountIndex(position, raw), raw, reuse = false)
    }

    override fun getStatusCount(raw: Boolean): Int {
        if (raw) return data?.size ?: 0
        return displayDataCount
    }

    override fun setData(data: List<ParcelableStatus>?): Boolean {
        var changed = true
        if (data == null) {
            displayPositions = null
            displayDataCount = 0
        } else if (data is ObjectCursor) {
            displayPositions = null
            displayDataCount = data.size
        } else {
            var filteredCount = 0
            displayPositions = IntArray(data.size).apply {
                data.forEachIndexed { i, item ->
                    if (!item.is_gap && item.is_filtered) {
                        filteredCount++
                    } else {
                        this[i - filteredCount] = i
                    }
                }
            }
            displayDataCount = data.size - filteredCount
            changed = this.data != data
        }
        this.data = data
        this.infoCache = if (data != null) arrayOfNulls(data.size) else null
        gapLoadingIds.clear()
        updateItemCount()
        notifyDataSetChanged()
        return changed
    }

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

    override fun getItemId(position: Int): Long {
        val countIndex = getItemCountIndex(position)
        return (countIndex.toLong() shl 32) or when (countIndex) {
            ITEM_INDEX_PINNED_STATUS -> {
                val status = pinnedStatuses!![position - getItemStartPosition(ITEM_INDEX_PINNED_STATUS)]
                return status.hashCode().toLong()
            }
            ITEM_INDEX_STATUS -> getFieldValue(position, { (_, accountKey, id) ->
                return@getFieldValue ParcelableStatus.calculateHashCode(accountKey, id)
            }, { status ->
                return@getFieldValue status.hashCode()
            }, -1).toLong()
            else -> position.toLong()
        }
    }

    override fun getStatusId(position: Int, raw: Boolean): String {
        return getFieldValue(position, { info ->
            return@getFieldValue info.id
        }, { status ->
            return@getFieldValue status.id
        }, "")
    }

    fun getStatusSortId(position: Int, raw: Boolean): Long {
        return getFieldValue(position, { info ->
            return@getFieldValue info.sortId
        }, { status ->
            return@getFieldValue status.sort_id
        }, -1L, raw)
    }

    override fun getStatusTimestamp(position: Int, raw: Boolean): Long {
        return getFieldValue(position, { info ->
            return@getFieldValue info.timestamp
        }, { status ->
            return@getFieldValue status.timestamp
        }, -1L)
    }

    override fun getStatusPositionKey(position: Int, raw: Boolean): Long {
        return getFieldValue(position, { info ->
            if (info.positionKey > 0) return@getFieldValue info.positionKey
            return@getFieldValue info.timestamp
        }, { status ->
            val positionKey = status.position_key
            if (positionKey > 0) return@getFieldValue positionKey
            return@getFieldValue status.timestamp
        }, -1L)
    }

    override fun getAccountKey(position: Int, raw: Boolean): UserKey {
        val def: UserKey? = null
        return getFieldValue(position, { info ->
            return@getFieldValue info.accountKey
        }, { status ->
            return@getFieldValue status.account_key
        }, def, raw)!!
    }

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

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

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

    override fun showCardActions(position: Int) {
        if (showingActionCardId != RecyclerView.NO_ID) {
            val pos = findPositionByItemId(showingActionCardId)
            if (pos != RecyclerView.NO_POSITION) {
                notifyItemChanged(pos)
            }

        }
        showingActionCardId = getItemId(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 onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        when (viewType) {
            ITEM_VIEW_TYPE_GAP -> {
                val view = inflater.inflate(GapViewHolder.layoutResource, parent, false)
                return GapViewHolder(this, view)
            }
            ITEM_VIEW_TYPE_LOAD_INDICATOR -> {
                val view = inflater.inflate(R.layout.list_item_load_indicator, parent, false)
                return LoadIndicatorViewHolder(view)
            }
            VIEW_TYPE_STATUS -> {
                return onCreateStatusViewHolder(parent) as RecyclerView.ViewHolder
            }
            VIEW_TYPE_EMPTY -> {
                return EmptyViewHolder(Space(context))
            }
            VIEW_TYPE_FILTER_HEADER -> {
                val view = inflater.inflate(TimelineFilterHeaderViewHolder.layoutResource,
                        parent, false)
                return TimelineFilterHeaderViewHolder(this, view)
            }
        }
        throw IllegalStateException("Unknown view type " + viewType)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder.itemViewType) {
            VIEW_TYPE_STATUS -> {
                val countIndex: Int = getItemCountIndex(position)
                val status = getStatusInternal(position, countIndex = countIndex, reuse = true)
                (holder as IStatusViewHolder).display(status, displayInReplyTo = isShowInReplyTo,
                        displayPinned = countIndex == ITEM_INDEX_PINNED_STATUS)
            }
            VIEW_TYPE_FILTER_HEADER -> {
                (holder as TimelineFilterHeaderViewHolder).display(timelineFilter!!)
            }
            ITEM_VIEW_TYPE_GAP -> {
                val status = getStatusInternal(position, reuse = true)
                val loading = gapLoadingIds.any { it.accountKey == status.account_key && it.id == status.id }
                (holder as GapViewHolder).display(loading)
            }
        }
    }

    override fun getItemViewType(position: Int): Int {
        if (position == 0 && ILoadMoreSupportAdapter.START in loadMoreIndicatorPosition) {
            return ITEM_VIEW_TYPE_LOAD_INDICATOR
        }
        val countIndex = getItemCountIndex(position)
        when (countIndex) {
            ITEM_INDEX_LOAD_START_INDICATOR, ITEM_INDEX_LOAD_END_INDICATOR -> {
                return ITEM_VIEW_TYPE_LOAD_INDICATOR
            }
            ITEM_INDEX_PINNED_STATUS -> {
                return VIEW_TYPE_STATUS
            }
            ITEM_INDEX_STATUS -> return if (isGapItem(position)) {
                ITEM_VIEW_TYPE_GAP
            } else {
                VIEW_TYPE_STATUS
            }
            ITEM_INDEX_FILTER_HEADER -> {
                return VIEW_TYPE_FILTER_HEADER
            }
            else -> throw UnsupportedCountIndexException(countIndex, position)
        }
    }

    protected abstract fun onCreateStatusViewHolder(parent: ViewGroup): IStatusViewHolder

    override fun addGapLoadingId(id: ObjectId) {
        gapLoadingIds.add(id)
    }

    override fun removeGapLoadingId(id: ObjectId) {
        gapLoadingIds.remove(id)
    }

    override fun getItemCount(): Int {
        return itemCounts.itemCount
    }

    override fun findStatusById(accountKey: UserKey, statusId: String): ParcelableStatus? {
        for (i in 0 until getStatusCount(true)) {
            if (accountKey == getAccountKey(i, true) && statusId == getStatusId(i, true)) {
                return getStatus(i, true)
            }
        }
        return null
    }

    fun isStatus(position: Int, raw: Boolean = false): Boolean {
        return position < getStatusCount(raw)
    }

    fun getRowId(adapterPosition: Int, raw: Boolean = false): Long {
        return getFieldValue(adapterPosition, readInfoValueAction = {
            it._id
        }, readStatusValueAction = { status ->
            status.hashCode().toLong()
        }, defValue = -1L, raw = raw)
    }

    fun findPositionByPositionKey(positionKey: Long, raw: Boolean = false): Int {
        // Assume statuses are descend sorted by id, so break at first status with id
        // lesser equals than read position
        if (positionKey <= 0) return RecyclerView.NO_POSITION
        val range = rangeOfSize(statusStartIndex, getStatusCount(raw))
        if (range.isEmpty() || range.start < 0) return RecyclerView.NO_POSITION
        if (positionKey < getStatusPositionKey(range.last, raw)) {
            return range.last
        }
        return range.indexOfFirst { positionKey >= getStatusPositionKey(it, raw) }
    }

    fun findPositionBySortId(sortId: Long, raw: Boolean = false): Int {
        // Assume statuses are descend sorted by id, so break at first status with id
        // lesser equals than read position
        if (sortId <= 0) return RecyclerView.NO_POSITION
        val range = rangeOfSize(statusStartIndex, getStatusCount(raw))
        if (range.isEmpty() || range.start < 0) return RecyclerView.NO_POSITION
        if (sortId < getStatusSortId(range.last, raw)) {
            return range.last
        }
        return range.indexOfFirst { sortId >= getStatusSortId(it, raw) }
    }

    private fun getItemCountIndex(position: Int, raw: Boolean): Int {
        if (!raw) return itemCounts.getItemCountIndex(position)
        var sum = 0
        for (i in 0 until itemCounts.size) {
            sum += when (i) {
                ITEM_INDEX_STATUS -> data?.size ?: 0
                else -> itemCounts[i]
            }
            if (position < sum) {
                return i
            }
        }
        return -1
    }

    private inline fun <T> getFieldValue(position: Int,
            readInfoValueAction: (StatusInfo) -> T,
            readStatusValueAction: (status: ParcelableStatus) -> T,
            defValue: T, raw: Boolean = false): T {
        val data = this.data
        if (data is ObjectCursor) {
            val dataPosition = position - statusStartIndex
            if (dataPosition < 0 || dataPosition >= getStatusCount(true)) {
                throw CursorIndexOutOfBoundsException("index: $position, valid range is $0..${getStatusCount(true)}")
            }
            val info = infoCache?.get(dataPosition) ?: run {
                val cursor = data.cursor
                if (!cursor.safeMoveToPosition(dataPosition)) return defValue
                val indices = data.indices
                val _id = cursor.safeGetLong(indices[Statuses._ID])
                val accountKey = UserKey.valueOf(cursor.safeGetString(indices[Statuses.ACCOUNT_KEY]))
                val id = cursor.safeGetString(indices[Statuses.ID])
                val timestamp = cursor.safeGetLong(indices[Statuses.TIMESTAMP])
                val sortId = cursor.safeGetLong(indices[Statuses.SORT_ID])
                val positionKey = cursor.safeGetLong(indices[Statuses.POSITION_KEY])
                val gap = cursor.safeGetInt(indices[Statuses.IS_GAP]) == 1
                val newInfo = StatusInfo(_id, accountKey, id, timestamp, sortId, positionKey, gap)
                infoCache?.set(dataPosition, newInfo)
                return@run newInfo
            }
            return readInfoValueAction(info)
        }
        return readStatusValueAction(getStatus(position, raw))
    }

    private fun getStatusInternal(position: Int, countIndex: Int = getItemCountIndex(position),
            raw: Boolean = false, reuse: Boolean): ParcelableStatus {
        when (countIndex) {
            ITEM_INDEX_PINNED_STATUS -> {
                return pinnedStatuses!![position - getItemStartPosition(ITEM_INDEX_PINNED_STATUS)]
            }
            ITEM_INDEX_STATUS -> {
                val data = this.data!!
                val dataPosition = position - statusStartIndex
                val positions = displayPositions
                val listPosition = if (positions != null && !raw) {
                    positions[dataPosition]
                } else {
                    dataPosition
                }
                if (reuse && data is ObjectCursor) {
                    reuseStatus.is_filtered = false
                    return data.setInto(listPosition, reuseStatus)
                } else {
                    return data[listPosition]
                }
            }
        }
        val validStart = getItemStartPosition(ITEM_INDEX_PINNED_STATUS)
        val validEnd = getItemStartPosition(ITEM_INDEX_STATUS) + getStatusCount(raw) - 1
        throw IndexOutOfBoundsException("index: $position, valid range is $validStart..$validEnd")
    }

    private fun updateItemCount() {
        itemCounts[ITEM_INDEX_LOAD_START_INDICATOR] = if (ILoadMoreSupportAdapter.START in loadMoreIndicatorPosition) 1 else 0
        itemCounts[ITEM_INDEX_FILTER_HEADER] = if (timelineFilter != null) 1 else 0
        itemCounts[ITEM_INDEX_PINNED_STATUS] = pinnedStatuses?.size ?: 0
        itemCounts[ITEM_INDEX_STATUS] = getStatusCount(false)
        itemCounts[ITEM_INDEX_LOAD_END_INDICATOR] = if (ILoadMoreSupportAdapter.END in loadMoreIndicatorPosition) 1 else 0
    }


    data class StatusInfo(
            val _id: Long,
            val accountKey: UserKey,
            val id: String,
            val timestamp: Long,
            val sortId: Long,
            val positionKey: Long,
            val gap: Boolean
    )


    companion object {
        const val VIEW_TYPE_STATUS = 2
        const val VIEW_TYPE_EMPTY = 3
        const val VIEW_TYPE_FILTER_HEADER = 4

        const val ITEM_INDEX_LOAD_START_INDICATOR = 0
        const val ITEM_INDEX_FILTER_HEADER = 1
        const val ITEM_INDEX_PINNED_STATUS = 2
        const val ITEM_INDEX_STATUS = 3
        const val ITEM_INDEX_LOAD_END_INDICATOR = 4
    }


}
