import Foundation
import UIKit
import Postbox
import TelegramCore
import PersistentStringHash

public final class VideoMediaResourceAdjustments: PostboxCoding, Equatable {
    public let data: MemoryBuffer
    public let digest: MemoryBuffer
    public let isStory: Bool
    
    public init(data: MemoryBuffer, digest: MemoryBuffer, isStory: Bool = false) {
        self.data = data
        self.digest = digest
        self.isStory = isStory
    }
    
    public init(decoder: PostboxDecoder) {
        self.data = decoder.decodeBytesForKey("d")!
        self.digest = decoder.decodeBytesForKey("h")!
        self.isStory = decoder.decodeBoolForKey("s", orElse: false)
    }
    
    public func encode(_ encoder: PostboxEncoder) {
        encoder.encodeBytes(self.data, forKey: "d")
        encoder.encodeBytes(self.digest, forKey: "h")
        encoder.encodeBool(self.isStory, forKey: "s")
    }
    
    public static func ==(lhs: VideoMediaResourceAdjustments, rhs: VideoMediaResourceAdjustments) -> Bool {
        return lhs.data == rhs.data && lhs.digest == rhs.digest && lhs.isStory == rhs.isStory
    }
}

public struct VideoLibraryMediaResourceId {
    public let localIdentifier: String
    public let adjustmentsDigest: MemoryBuffer?
    
    public var uniqueId: String {
        if let adjustmentsDigest = self.adjustmentsDigest {
            return "vi-\(self.localIdentifier.replacingOccurrences(of: "/", with: "_"))-\(adjustmentsDigest.description)"
        } else {
            return "vi-\(self.localIdentifier.replacingOccurrences(of: "/", with: "_"))"
        }
    }
    
    public var hashValue: Int {
        return self.localIdentifier.hashValue
    }
}

public enum VideoLibraryMediaResourceConversion: PostboxCoding, Equatable {
    case passthrough
    case compress(VideoMediaResourceAdjustments?)
    
    public init(decoder: PostboxDecoder) {
        switch decoder.decodeInt32ForKey("v", orElse: 0) {
            case 0:
                self = .passthrough
            case 1:
                self = .compress(decoder.decodeObjectForKey("adj", decoder: { VideoMediaResourceAdjustments(decoder: $0) }) as? VideoMediaResourceAdjustments)
            default:
                self = .compress(nil)
        }
    }
    
    public func encode(_ encoder: PostboxEncoder) {
        switch self {
            case .passthrough:
                encoder.encodeInt32(0, forKey: "v")
            case let .compress(adjustments):
                encoder.encodeInt32(1, forKey: "v")
                if let adjustments = adjustments {
                    encoder.encodeObject(adjustments, forKey: "adj")
                } else {
                    encoder.encodeNil(forKey: "adj")
                }
        }
    }
    
    public static func ==(lhs: VideoLibraryMediaResourceConversion, rhs: VideoLibraryMediaResourceConversion) -> Bool {
        switch lhs {
            case .passthrough:
                if case .passthrough = rhs {
                    return true
                } else {
                    return false
                }
            case let .compress(lhsAdjustments):
                if case let .compress(rhsAdjustments) = rhs, lhsAdjustments == rhsAdjustments {
                    return true
                } else {
                    return false
                }
        }
    }
}

public final class VideoLibraryMediaResource: TelegramMediaResource {
    public let localIdentifier: String
    public let conversion: VideoLibraryMediaResourceConversion
    
    public var size: Int64? {
        return nil
    }
    
    public var headerSize: Int32 {
        return 32 * 1024
    }
    
    public init(localIdentifier: String, conversion: VideoLibraryMediaResourceConversion) {
        self.localIdentifier = localIdentifier
        self.conversion = conversion
    }
    
    public required init(decoder: PostboxDecoder) {
        self.localIdentifier = decoder.decodeStringForKey("i", orElse: "")
        self.conversion = (decoder.decodeObjectForKey("conv", decoder: { VideoLibraryMediaResourceConversion(decoder: $0) }) as? VideoLibraryMediaResourceConversion) ?? .compress(nil)
    }
    
    public func encode(_ encoder: PostboxEncoder) {
        encoder.encodeString(self.localIdentifier, forKey: "i")
        encoder.encodeObject(self.conversion, forKey: "conv")
    }
    
    public var id: MediaResourceId {
        var adjustmentsDigest: MemoryBuffer?
        switch self.conversion {
            case .passthrough:
                break
            case let .compress(adjustments):
                adjustmentsDigest = adjustments?.digest
        }
        return MediaResourceId(VideoLibraryMediaResourceId(localIdentifier: self.localIdentifier, adjustmentsDigest: adjustmentsDigest).uniqueId)
    }
    
    public func isEqual(to: MediaResource) -> Bool {
        if let to = to as? VideoLibraryMediaResource {
            return self.localIdentifier == to.localIdentifier && self.conversion == to.conversion
        } else {
            return false
        }
    }
}

public struct LocalFileVideoMediaResourceId {
    public let randomId: Int64
    
    public var uniqueId: String {
        return "lvi-\(self.randomId)"
    }
    
    public var hashValue: Int {
        return self.randomId.hashValue
    }
}

public final class LocalFileVideoMediaResource: TelegramMediaResource {
    public var size: Int64? {
        return nil
    }
    
    public let randomId: Int64
    public let paths: [String]
    public let adjustments: VideoMediaResourceAdjustments?
    
    public var headerSize: Int32 {
        return 32 * 1024
    }
    
    public init(randomId: Int64, path: String, adjustments: VideoMediaResourceAdjustments?) {
        self.randomId = randomId
        self.paths = [path]
        self.adjustments = adjustments
    }
    
    public init(randomId: Int64, paths: [String], adjustments: VideoMediaResourceAdjustments?) {
        self.randomId = randomId
        self.paths = paths
        self.adjustments = adjustments
    }
    
    public required init(decoder: PostboxDecoder) {
        self.randomId = decoder.decodeInt64ForKey("i", orElse: 0)
        let paths = decoder.decodeStringArrayForKey("ps")
        if !paths.isEmpty {
            self.paths = paths
        } else {
            self.paths = [decoder.decodeStringForKey("p", orElse: "")]
        }
        self.adjustments = decoder.decodeObjectForKey("a", decoder: { VideoMediaResourceAdjustments(decoder: $0) }) as? VideoMediaResourceAdjustments
    }
    
    public func encode(_ encoder: PostboxEncoder) {
        encoder.encodeInt64(self.randomId, forKey: "i")
        encoder.encodeStringArray(self.paths, forKey: "ps")
        if let adjustments = self.adjustments {
            encoder.encodeObject(adjustments, forKey: "a")
        } else {
            encoder.encodeNil(forKey: "a")
        }
    }
    
    public var id: MediaResourceId {
        return MediaResourceId(LocalFileVideoMediaResourceId(randomId: self.randomId).uniqueId)
    }
    
    public func isEqual(to: MediaResource) -> Bool {
        if let to = to as? LocalFileVideoMediaResource {
            return self.randomId == to.randomId && self.paths == to.paths && self.adjustments == to.adjustments
        } else {
            return false
        }
    }
}

public struct PhotoLibraryMediaResourceId {
    public let localIdentifier: String
    public let resourceId: Int64
    
    public var uniqueId: String {
        if self.resourceId != 0 {
            return "ph-\(self.localIdentifier.replacingOccurrences(of: "/", with: "_"))-\(self.resourceId)"
        } else {
            return "ph-\(self.localIdentifier.replacingOccurrences(of: "/", with: "_"))"
        }
    }
    
    public var hashValue: Int {
        return self.localIdentifier.hashValue
    }
}

public enum MediaImageFormat: Int32 {
    case jpeg
    case jxl
}

public class PhotoLibraryMediaResource: TelegramMediaResource {
    public var size: Int64? {
        return nil
    }
    
    public let localIdentifier: String
    public let uniqueId: Int64
    public let width: Int32?
    public let height: Int32?
    public let format: MediaImageFormat?
    public let quality: Int32?
    
    public init(localIdentifier: String, uniqueId: Int64, width: Int32? = nil, height: Int32? = nil, format: MediaImageFormat? = nil, quality: Int32? = nil) {
        self.localIdentifier = localIdentifier
        self.uniqueId = uniqueId
        self.width = width
        self.height = height
        self.format = format
        self.quality = quality
    }
    
    public required init(decoder: PostboxDecoder) {
        self.localIdentifier = decoder.decodeStringForKey("i", orElse: "")
        self.uniqueId = decoder.decodeInt64ForKey("uid", orElse: 0)
        self.width = decoder.decodeOptionalInt32ForKey("w")
        self.height = decoder.decodeOptionalInt32ForKey("h")
        self.format = decoder.decodeOptionalInt32ForKey("f").flatMap(MediaImageFormat.init(rawValue:))
        self.quality = decoder.decodeOptionalInt32ForKey("q")
    }
    
    public func encode(_ encoder: PostboxEncoder) {
        encoder.encodeString(self.localIdentifier, forKey: "i")
        encoder.encodeInt64(self.uniqueId, forKey: "uid")
        if let width = self.width {
            encoder.encodeInt32(width, forKey: "w")
        } else {
            encoder.encodeNil(forKey: "w")
        }
        if let height = self.height {
            encoder.encodeInt32(height, forKey: "h")
        } else {
            encoder.encodeNil(forKey: "h")
        }
        if let format = self.format {
            encoder.encodeInt32(format.rawValue, forKey: "f")
        } else {
            encoder.encodeNil(forKey: "f")
        }
        if let quality = self.quality {
            encoder.encodeInt32(quality, forKey: "q")
        } else {
            encoder.encodeNil(forKey: "q")
        }
    }
    
    public var id: MediaResourceId {
        return MediaResourceId(PhotoLibraryMediaResourceId(localIdentifier: self.localIdentifier, resourceId: self.uniqueId).uniqueId)
    }
    
    public func isEqual(to: MediaResource) -> Bool {
        if let to = to as? PhotoLibraryMediaResource {
            if self.localIdentifier != to.localIdentifier {
                return false
            }
            if self.uniqueId != to.uniqueId {
                return false
            }
            if self.width != to.width {
                return false
            }
            if self.height != to.height {
                return false
            }
            if self.format != to.format {
                return false
            }
            if self.quality != to.quality {
                return false
            }
            return true
        } else {
            return false
        }
    }
}

public struct LocalFileGifMediaResourceId {
    public let randomId: Int64
    
    public var uniqueId: String {
        return "lgi-\(self.randomId)"
    }
    
    public var hashValue: Int {
        return self.randomId.hashValue
    }
}

public final class LocalFileGifMediaResource: TelegramMediaResource {
    public var size: Int64? {
        return nil
    }
    
    public let randomId: Int64
    public let path: String
    
    public var headerSize: Int32 {
        return 32 * 1024
    }
    
    public init(randomId: Int64, path: String) {
        self.randomId = randomId
        self.path = path
    }
    
    public required init(decoder: PostboxDecoder) {
        self.randomId = decoder.decodeInt64ForKey("i", orElse: 0)
        self.path = decoder.decodeStringForKey("p", orElse: "")
    }
    
    public func encode(_ encoder: PostboxEncoder) {
        encoder.encodeInt64(self.randomId, forKey: "i")
        encoder.encodeString(self.path, forKey: "p")
    }
    
    public var id: MediaResourceId {
        return MediaResourceId(LocalFileGifMediaResourceId(randomId: self.randomId).uniqueId)
    }
    
    public func isEqual(to: MediaResource) -> Bool {
        if let to = to as? LocalFileGifMediaResource {
            return self.randomId == to.randomId && self.path == to.path
        } else {
            return false
        }
    }
}


public struct BundleResourceId {
    public let nameHash: Int64
    
    public var uniqueId: String {
        return "bundle-\(nameHash)"
    }
    
    public var hashValue: Int {
        return self.nameHash.hashValue
    }
}

public class BundleResource: TelegramMediaResource {
    public var size: Int64? {
        return nil
    }
    
    public let nameHash: Int64
    public let path: String
    
    public init(name: String, path: String) {
        self.nameHash = Int64(bitPattern: name.persistentHashValue)
        self.path = path
    }
    
    public required init(decoder: PostboxDecoder) {
        self.nameHash = decoder.decodeInt64ForKey("h", orElse: 0)
        self.path = decoder.decodeStringForKey("p", orElse: "")
    }
    
    public func encode(_ encoder: PostboxEncoder) {
        encoder.encodeInt64(self.nameHash, forKey: "h")
        encoder.encodeString(self.path, forKey: "p")
    }
    
    public var id: MediaResourceId {
        return MediaResourceId(BundleResourceId(nameHash: self.nameHash).uniqueId)
    }
    
    public func isEqual(to: MediaResource) -> Bool {
        if let to = to as? BundleResource {
            return self.nameHash == to.nameHash
        } else {
            return false
        }
    }
}