import Foundation
import UIKit
import TelegramCore
import SwiftSignalKit
import Display
import MergeLists
import ItemListUI
import AccountContext

struct CallListNodeView {
    let originalView: EngineCallList
    let filteredEntries: [CallListNodeEntry]
    let presentationData: ItemListPresentationData
}

enum CallListNodeViewTransitionReason {
    case initial
    case interactiveChanges
    case reload
    case reloadAnimated
}

struct CallListNodeViewTransitionInsertEntry {
    let index: Int
    let previousIndex: Int?
    let entry: CallListNodeEntry
    let directionHint: ListViewItemOperationDirectionHint?
}

struct CallListNodeViewTransitionUpdateEntry {
    let index: Int
    let previousIndex: Int
    let entry: CallListNodeEntry
    let directionHint: ListViewItemOperationDirectionHint?
}

struct CallListNodeViewTransition {
    let callListView: CallListNodeView
    let deleteItems: [ListViewDeleteItem]
    let insertEntries: [CallListNodeViewTransitionInsertEntry]
    let updateEntries: [CallListNodeViewTransitionUpdateEntry]
    let options: ListViewDeleteAndInsertOptions
    let scrollToItem: ListViewScrollToItem?
    let stationaryItemRange: (Int, Int)?
}

enum CallListNodeViewScrollPosition {
    case index(index: EngineMessage.Index, position: ListViewScrollPosition, directionHint: ListViewScrollToItemDirectionHint, animated: Bool)
}

func preparedCallListNodeViewTransition(from fromView: CallListNodeView?, to toView: CallListNodeView, reason: CallListNodeViewTransitionReason, disableAnimations: Bool, context: AccountContext, scrollPosition: CallListNodeViewScrollPosition?) -> Signal<CallListNodeViewTransition, NoError> {
    return Signal { subscriber in
        let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromView?.filteredEntries ?? [], rightList: toView.filteredEntries, allUpdated: fromView?.presentationData != toView.presentationData)
        
        var adjustedDeleteIndices: [ListViewDeleteItem] = []
        let previousCount: Int
        if let fromView = fromView {
            previousCount = fromView.filteredEntries.count
        } else {
            previousCount = 0;
        }
        for index in deleteIndices {
            adjustedDeleteIndices.append(ListViewDeleteItem(index: previousCount - 1 - index, directionHint: nil))
        }
        var adjustedIndicesAndItems: [CallListNodeViewTransitionInsertEntry] = []
        var adjustedUpdateItems: [CallListNodeViewTransitionUpdateEntry] = []
        let updatedCount = toView.filteredEntries.count
        
        var options: ListViewDeleteAndInsertOptions = []
        var maxAnimatedInsertionIndex = -1
        let stationaryItemRange: (Int, Int)? = nil
        var scrollToItem: ListViewScrollToItem? = nil
        
        var wasEmpty = false
        if let fromView = fromView, fromView.originalView.items.isEmpty {
            wasEmpty = true
        }
        
        switch reason {
        case .initial:
            let _ = options.insert(.LowLatency)
            let _ = options.insert(.Synchronous)
            let _ = options.insert(.PreferSynchronousResourceLoading)
        case .interactiveChanges:
            if wasEmpty {
                let _ = options.insert(.Synchronous)
                let _ = options.insert(.PreferSynchronousResourceLoading)
            } else {
                let _ = options.insert(.AnimateAlpha)
                if !disableAnimations {
                    let _ = options.insert(.AnimateInsertion)
                }
                
                for (index, _, _) in indicesAndItems.sorted(by: { $0.0 > $1.0 }) {
                    let adjustedIndex = updatedCount - 1 - index
                    if adjustedIndex == maxAnimatedInsertionIndex + 1 {
                        maxAnimatedInsertionIndex += 1
                    }
                }
            }
        case .reload:
            break
        case .reloadAnimated:
            let _ = options.insert(.LowLatency)
            let _ = options.insert(.Synchronous)
            let _ = options.insert(.AnimateCrossfade)
            let _ = options.insert(.PreferSynchronousResourceLoading)
        }
        
        for (index, entry, previousIndex) in indicesAndItems {
            let adjustedIndex = updatedCount - 1 - index
            
            let adjustedPrevousIndex: Int?
            if let previousIndex = previousIndex {
                adjustedPrevousIndex = previousCount - 1 - previousIndex
            } else {
                adjustedPrevousIndex = nil
            }
            
            var directionHint: ListViewItemOperationDirectionHint?
            if maxAnimatedInsertionIndex >= 0 && adjustedIndex <= maxAnimatedInsertionIndex {
                directionHint = .Down
            }
            
            adjustedIndicesAndItems.append(CallListNodeViewTransitionInsertEntry(index: adjustedIndex, previousIndex: adjustedPrevousIndex, entry: entry, directionHint: directionHint))
        }
        
        for (index, entry, previousIndex) in updateIndices {
            let adjustedIndex = updatedCount - 1 - index
            let adjustedPreviousIndex = previousCount - 1 - previousIndex
            
            let directionHint: ListViewItemOperationDirectionHint? = nil
            adjustedUpdateItems.append(CallListNodeViewTransitionUpdateEntry(index: adjustedIndex, previousIndex: adjustedPreviousIndex, entry: entry, directionHint: directionHint))
        }
        
        if let scrollPosition = scrollPosition {
            switch scrollPosition {
            case let .index(scrollIndex, position, directionHint, animated):
                var index = toView.filteredEntries.count - 1
                for entry in toView.filteredEntries {
                    if case let .message(messageIndex) = entry.sortIndex {
                        if messageIndex >= scrollIndex {
                            scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: .Default(duration: nil), directionHint: directionHint)
                            break
                        }
                    }
                    index -= 1
                }
                
                if scrollToItem == nil {
                    var index = 0
                    for entry in toView.filteredEntries.reversed() {
                        if case let .message(messageIndex) = entry.sortIndex {
                            if messageIndex < scrollIndex {
                                scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: .Default(duration: nil), directionHint: directionHint)
                                break
                            }
                        }
                        index += 1
                    }
                }
            }
        }
        
        subscriber.putNext(CallListNodeViewTransition(callListView: toView, deleteItems: adjustedDeleteIndices, insertEntries: adjustedIndicesAndItems, updateEntries: adjustedUpdateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange))
        subscriber.putCompletion()
        
        return EmptyDisposable
    }
}