import UIKit
import RealmSwift
import DifferenceKit
import ExceptionCatcher

typealias FilterPredicate<T> = (T) -> Bool

class RealmSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource
        where   Item: Object & Identifiable & Dated & Differentiable & Cloneable,
                Cell: UITableViewCell & ConfigurableCell,
                Cell.Item == Item {
    
    private var tv: UITableView
    private var data: Results<Item>
    private var notificationToken: NotificationToken?
    private var sections: [DateSection<Item>] = []
    private var cellIdentifier: String
    private var filterPredicate: FilterPredicate<Item>?
    
    init(table: UITableView, data: Results<Item>, cellIdentifier: String = String(describing: Cell.self)) {
        self.tv = table
        self.data = data
        self.cellIdentifier = cellIdentifier
        super.init()
        self.tv.dataSource = self
        self.tv.reloadData()
        
        self.notificationToken = self.data.observe { changes in
            switch changes {
            case .initial:
                self.reload(animated: false)
            case .update(_, _, _, _):
                self.reload(animated: true)
            case .error(let err):
                print("Realm observer error: \(err)")
            }
        }
    }

    // MARK: - UITableViewDataSource

    func numberOfSections(in tableView: UITableView) -> Int {
        return self.sections.count
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.sections[section].elements.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: self.cellIdentifier, for: indexPath) as? Cell else {
            return UITableViewCell()
        }

        let item = self.sections[indexPath.section].elements[indexPath.row]
        cell.configure(with: item)
        return cell
    }

    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return self.sections[section].header
    }
    
    // MARK: - Public
    
    func item(at indexPath: IndexPath) -> Item {
        return self.sections[indexPath.section].elements[indexPath.row]
    }
    
    func reload(animated: Bool = true) {
        var items = self.data.toArray().map { $0.clone() }
        if let predicate = self.filterPredicate {
            items = items.filter(predicate)
        }

        DispatchQueue.global().async {
            let newSections = items.groupedByDate()
            let changeset = StagedChangeset(source: self.sections, target: newSections)
            DispatchQueue.main.async {
                self.tv.reload(using: changeset, with: animated ? .fade : .none) { newSects in
                    self.sections = newSects
                }
            }
        }
    }
    
    func setFilterPredicate(_ predicate: FilterPredicate<Item>?) {
        self.filterPredicate = predicate
        self.reload()
    }
    
    func makeCsv() throws -> String {
        guard let type = Item.self as? Exportable.Type else {
            throw CocoaError.error("Type \(Item.self) is not exportable")
        }
        
        var result = ""
        let newLine: Character = "\r\n" //"\u{000B}"
        result.append(type.csvHeader)
        result.append(newLine)
        
        for item in self.data {
            if let exportableItem = item as? Exportable {
                result.append(exportableItem.csvLine)
                result.append(newLine)
            }
        }
        
        return result
    }
}
