import Postbox

private enum SecretChatOutgoingFileValue: Int32 {
    case remote = 0
    case uploadedRegular = 1
    case uploadedLarge = 2
}

public enum SecretChatOutgoingFileReference: PostboxCoding {
    case remote(id: Int64, accessHash: Int64)
    case uploadedRegular(id: Int64, partCount: Int32, md5Digest: String, keyFingerprint: Int32)
    case uploadedLarge(id: Int64, partCount: Int32, keyFingerprint: Int32)
    
    public init(decoder: PostboxDecoder) {
        switch decoder.decodeInt32ForKey("v", orElse: 0) {
            case SecretChatOutgoingFileValue.remote.rawValue:
                self = .remote(id: decoder.decodeInt64ForKey("i", orElse: 0), accessHash: decoder.decodeInt64ForKey("a", orElse: 0))
            case SecretChatOutgoingFileValue.uploadedRegular.rawValue:
                self = .uploadedRegular(id: decoder.decodeInt64ForKey("i", orElse: 0), partCount: decoder.decodeInt32ForKey("p", orElse: 0), md5Digest: decoder.decodeStringForKey("d", orElse: ""), keyFingerprint: decoder.decodeInt32ForKey("f", orElse: 0))
            case SecretChatOutgoingFileValue.uploadedLarge.rawValue:
                self = .uploadedLarge(id: decoder.decodeInt64ForKey("i", orElse: 0), partCount: decoder.decodeInt32ForKey("p", orElse: 0), keyFingerprint: decoder.decodeInt32ForKey("f", orElse: 0))
            default:
                assertionFailure()
                self = .remote(id: 0, accessHash: 0)
        }
    }
    
    public func encode(_ encoder: PostboxEncoder) {
        switch self {
            case let .remote(id, accessHash):
                encoder.encodeInt32(SecretChatOutgoingFileValue.remote.rawValue, forKey: "v")
                encoder.encodeInt64(id, forKey: "i")
                encoder.encodeInt64(accessHash, forKey: "a")
            case let .uploadedRegular(id, partCount, md5Digest, keyFingerprint):
                encoder.encodeInt32(SecretChatOutgoingFileValue.uploadedRegular.rawValue, forKey: "v")
                encoder.encodeInt64(id, forKey: "i")
                encoder.encodeInt32(partCount, forKey: "p")
                encoder.encodeString(md5Digest, forKey: "d")
                encoder.encodeInt32(keyFingerprint, forKey: "f")
            case let .uploadedLarge(id, partCount, keyFingerprint):
                encoder.encodeInt32(SecretChatOutgoingFileValue.uploadedLarge.rawValue, forKey: "v")
                encoder.encodeInt64(id, forKey: "i")
                encoder.encodeInt32(partCount, forKey: "p")
                encoder.encodeInt32(keyFingerprint, forKey: "f")
        }
    }
}

public struct SecretChatOutgoingFile: PostboxCoding {
    public let reference: SecretChatOutgoingFileReference
    public let size: Int32
    public let key: SecretFileEncryptionKey
    
    public init(reference: SecretChatOutgoingFileReference, size: Int32, key: SecretFileEncryptionKey) {
        self.reference = reference
        self.size = size
        self.key = key
    }
    
    public init(decoder: PostboxDecoder) {
        self.reference = decoder.decodeObjectForKey("r", decoder: { SecretChatOutgoingFileReference(decoder: $0) }) as! SecretChatOutgoingFileReference
        self.size = decoder.decodeInt32ForKey("s", orElse: 0)
        self.key = SecretFileEncryptionKey(aesKey: decoder.decodeBytesForKey("k")!.makeData(), aesIv: decoder.decodeBytesForKey("i")!.makeData())
    }
    
    public func encode(_ encoder: PostboxEncoder) {
        encoder.encodeObject(self.reference, forKey: "r")
        encoder.encodeInt32(self.size, forKey: "s")
        encoder.encodeBytes(MemoryBuffer(data: self.key.aesKey), forKey: "k")
        encoder.encodeBytes(MemoryBuffer(data: self.key.aesIv), forKey: "i")
    }
}

public enum SecretChatSequenceBasedLayer: Int32 {
    case layer46 = 46
    case layer73 = 73
    case layer101 = 101
    
    public var secretChatLayer: SecretChatLayer {
        switch self {
            case .layer46:
                return .layer46
            case .layer73:
                return .layer73
            case .layer101:
                return .layer101
        }
    }
}

private enum SecretChatOutgoingOperationValue: Int32 {
    case initialHandshakeAccept = 0
    case sendMessage = 1
    case readMessagesContent = 2
    case deleteMessages = 3
    case screenshotMessages = 4
    case clearHistory = 5
    case resendOperations = 6
    case reportLayerSupport = 7
    case pfsRequestKey = 8
    case pfsAcceptKey = 9
    case pfsAbortSession = 10
    case pfsCommitKey = 11
    case noop = 12
    case setMessageAutoremoveTimeout = 13
    case terminate = 14
}

public enum SecretChatOutgoingOperationContents: PostboxCoding {
    case initialHandshakeAccept(gA: MemoryBuffer, accessHash: Int64, b: MemoryBuffer)
    case sendMessage(layer: SecretChatLayer, id: MessageId, file: SecretChatOutgoingFile?)
    case readMessagesContent(layer: SecretChatLayer, actionGloballyUniqueId: Int64, globallyUniqueIds: [Int64])
    case deleteMessages(layer: SecretChatLayer, actionGloballyUniqueId: Int64, globallyUniqueIds: [Int64])
    case screenshotMessages(layer: SecretChatLayer, actionGloballyUniqueId: Int64, globallyUniqueIds: [Int64], messageId: MessageId)
    case clearHistory(layer: SecretChatLayer, actionGloballyUniqueId: Int64)
    case resendOperations(layer : SecretChatSequenceBasedLayer, actionGloballyUniqueId: Int64, fromSeqNo: Int32, toSeqNo: Int32)
    case reportLayerSupport(layer: SecretChatLayer, actionGloballyUniqueId: Int64, layerSupport: Int32)
    case pfsRequestKey(layer: SecretChatSequenceBasedLayer, actionGloballyUniqueId: Int64, rekeySessionId: Int64, a: MemoryBuffer)
    case pfsAcceptKey(layer: SecretChatSequenceBasedLayer, actionGloballyUniqueId: Int64, rekeySessionId: Int64, gA: MemoryBuffer, b: MemoryBuffer)
    case pfsAbortSession(layer: SecretChatSequenceBasedLayer, actionGloballyUniqueId: Int64, rekeySessionId: Int64)
    case pfsCommitKey(layer: SecretChatSequenceBasedLayer, actionGloballyUniqueId: Int64, rekeySessionId: Int64, keyFingerprint: Int64)
    case noop(layer: SecretChatSequenceBasedLayer, actionGloballyUniqueId: Int64)
    case setMessageAutoremoveTimeout(layer: SecretChatLayer, actionGloballyUniqueId: Int64, timeout: Int32, messageId: MessageId)
    case terminate(reportSpam: Bool)
    
    public init(decoder: PostboxDecoder) {
        switch decoder.decodeInt32ForKey("r", orElse: 0) {
            case SecretChatOutgoingOperationValue.initialHandshakeAccept.rawValue:
                self = .initialHandshakeAccept(gA: decoder.decodeBytesForKey("g")!, accessHash: decoder.decodeInt64ForKey("h", orElse: 0), b: decoder.decodeBytesForKey("b")!)
            case SecretChatOutgoingOperationValue.sendMessage.rawValue:
                self = .sendMessage(layer: SecretChatLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, id: MessageId(peerId: PeerId(decoder.decodeInt64ForKey("i.p", orElse: 0)), namespace: decoder.decodeInt32ForKey("i.n", orElse: 0), id: decoder.decodeInt32ForKey("i.i", orElse: 0)), file: decoder.decodeObjectForKey("f", decoder: { SecretChatOutgoingFile(decoder: $0) }) as? SecretChatOutgoingFile)
            case SecretChatOutgoingOperationValue.readMessagesContent.rawValue:
                self = .readMessagesContent(layer: SecretChatLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, actionGloballyUniqueId: decoder.decodeInt64ForKey("i", orElse: 0), globallyUniqueIds: decoder.decodeInt64ArrayForKey("u"))
            case SecretChatOutgoingOperationValue.deleteMessages.rawValue:
                self = .deleteMessages(layer: SecretChatLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, actionGloballyUniqueId: decoder.decodeInt64ForKey("i", orElse: 0), globallyUniqueIds: decoder.decodeInt64ArrayForKey("u"))
            case SecretChatOutgoingOperationValue.screenshotMessages.rawValue:
                self = .screenshotMessages(layer: SecretChatLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, actionGloballyUniqueId: decoder.decodeInt64ForKey("i", orElse: 0), globallyUniqueIds: decoder.decodeInt64ArrayForKey("u"), messageId: MessageId(peerId: PeerId(decoder.decodeInt64ForKey("m.p", orElse: 0)), namespace: decoder.decodeInt32ForKey("m.n", orElse: 0), id: decoder.decodeInt32ForKey("m.i", orElse: 0)))
            case SecretChatOutgoingOperationValue.clearHistory.rawValue:
                self = .clearHistory(layer: SecretChatLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, actionGloballyUniqueId: decoder.decodeInt64ForKey("i", orElse: 0))
            case SecretChatOutgoingOperationValue.resendOperations.rawValue:
                self = .resendOperations(layer: SecretChatSequenceBasedLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, actionGloballyUniqueId: decoder.decodeInt64ForKey("i", orElse: 0), fromSeqNo: decoder.decodeInt32ForKey("f", orElse: 0), toSeqNo: decoder.decodeInt32ForKey("t", orElse: 0))
            case SecretChatOutgoingOperationValue.reportLayerSupport.rawValue:
                self = .reportLayerSupport(layer: SecretChatLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, actionGloballyUniqueId: decoder.decodeInt64ForKey("i", orElse: 0), layerSupport: decoder.decodeInt32ForKey("l", orElse: 0))
            case SecretChatOutgoingOperationValue.pfsRequestKey.rawValue:
                self = .pfsRequestKey(layer: SecretChatSequenceBasedLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, actionGloballyUniqueId: decoder.decodeInt64ForKey("i", orElse: 0), rekeySessionId: decoder.decodeInt64ForKey("s", orElse: 0), a: decoder.decodeBytesForKey("a")!)
            case SecretChatOutgoingOperationValue.pfsAcceptKey.rawValue:
                self = .pfsAcceptKey(layer: SecretChatSequenceBasedLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, actionGloballyUniqueId: decoder.decodeInt64ForKey("i", orElse: 0), rekeySessionId: decoder.decodeInt64ForKey("s", orElse: 0), gA: decoder.decodeBytesForKey("g")!, b: decoder.decodeBytesForKey("b")!)
            case SecretChatOutgoingOperationValue.pfsAbortSession.rawValue:
                self = .pfsAbortSession(layer: SecretChatSequenceBasedLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, actionGloballyUniqueId: decoder.decodeInt64ForKey("i", orElse: 0), rekeySessionId: decoder.decodeInt64ForKey("s", orElse: 0))
            case SecretChatOutgoingOperationValue.pfsCommitKey.rawValue:
                self = .pfsCommitKey(layer: SecretChatSequenceBasedLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, actionGloballyUniqueId: decoder.decodeInt64ForKey("i", orElse: 0), rekeySessionId: decoder.decodeInt64ForKey("s", orElse: 0), keyFingerprint: decoder.decodeInt64ForKey("f", orElse: 0))
            case SecretChatOutgoingOperationValue.noop.rawValue:
                self = .noop(layer: SecretChatSequenceBasedLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, actionGloballyUniqueId: decoder.decodeInt64ForKey("i", orElse: 0))
            case SecretChatOutgoingOperationValue.setMessageAutoremoveTimeout.rawValue:
                self = .setMessageAutoremoveTimeout(layer: SecretChatLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, actionGloballyUniqueId: decoder.decodeInt64ForKey("i", orElse: 0), timeout: decoder.decodeInt32ForKey("t", orElse: 0), messageId: MessageId(peerId: PeerId(decoder.decodeInt64ForKey("m.p", orElse: 0)), namespace: decoder.decodeInt32ForKey("m.n", orElse: 0), id: decoder.decodeInt32ForKey("m.i", orElse: 0)))
            case SecretChatOutgoingOperationValue.terminate.rawValue:
                self = .terminate(reportSpam: decoder.decodeInt32ForKey("rs", orElse: 0) != 0)
            default:
                self = .noop(layer: SecretChatSequenceBasedLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, actionGloballyUniqueId: 0)
                assertionFailure()
        }
    }
    
    public func encode(_ encoder: PostboxEncoder) {
        switch self {
            case let .initialHandshakeAccept(gA, accessHash, b):
                encoder.encodeInt32(SecretChatOutgoingOperationValue.initialHandshakeAccept.rawValue, forKey: "r")
                encoder.encodeBytes(gA, forKey: "g")
                encoder.encodeInt64(accessHash, forKey: "h")
                encoder.encodeBytes(b, forKey: "b")
            case let .sendMessage(layer, id, file):
                encoder.encodeInt32(SecretChatOutgoingOperationValue.sendMessage.rawValue, forKey: "r")
                encoder.encodeInt32(layer.rawValue, forKey: "l")
                encoder.encodeInt64(id.peerId.toInt64(), forKey: "i.p")
                encoder.encodeInt32(id.namespace, forKey: "i.n")
                encoder.encodeInt32(id.id, forKey: "i.i")
                if let file = file {
                    encoder.encodeObject(file, forKey: "f")
                } else {
                    encoder.encodeNil(forKey: "f")
                }
            case let .readMessagesContent(layer, actionGloballyUniqueId, globallyUniqueIds):
                encoder.encodeInt32(SecretChatOutgoingOperationValue.readMessagesContent.rawValue, forKey: "r")
                encoder.encodeInt32(layer.rawValue, forKey: "l")
                encoder.encodeInt64(actionGloballyUniqueId, forKey: "i")
                encoder.encodeInt64Array(globallyUniqueIds, forKey: "u")
            case let .deleteMessages(layer, actionGloballyUniqueId, globallyUniqueIds):
                encoder.encodeInt32(SecretChatOutgoingOperationValue.deleteMessages.rawValue, forKey: "r")
                encoder.encodeInt32(layer.rawValue, forKey: "l")
                encoder.encodeInt64(actionGloballyUniqueId, forKey: "i")
                encoder.encodeInt64Array(globallyUniqueIds, forKey: "u")
            case let .screenshotMessages(layer, actionGloballyUniqueId, globallyUniqueIds, messageId):
                encoder.encodeInt32(SecretChatOutgoingOperationValue.screenshotMessages.rawValue, forKey: "r")
                encoder.encodeInt32(layer.rawValue, forKey: "l")
                encoder.encodeInt64(actionGloballyUniqueId, forKey: "i")
                encoder.encodeInt64Array(globallyUniqueIds, forKey: "u")
                encoder.encodeInt64(messageId.peerId.toInt64(), forKey: "m.p")
                encoder.encodeInt32(messageId.namespace, forKey: "m.n")
                encoder.encodeInt32(messageId.id, forKey: "m.i")
            case let .clearHistory(layer, actionGloballyUniqueId):
                encoder.encodeInt32(SecretChatOutgoingOperationValue.clearHistory.rawValue, forKey: "r")
                encoder.encodeInt32(layer.rawValue, forKey: "l")
                encoder.encodeInt64(actionGloballyUniqueId, forKey: "i")
            case let .resendOperations(layer, actionGloballyUniqueId, fromSeqNo, toSeqNo):
                encoder.encodeInt32(SecretChatOutgoingOperationValue.resendOperations.rawValue, forKey: "r")
                encoder.encodeInt32(layer.rawValue, forKey: "l")
                encoder.encodeInt64(actionGloballyUniqueId, forKey: "i")
                encoder.encodeInt32(fromSeqNo, forKey: "f")
                encoder.encodeInt32(toSeqNo, forKey: "t")
            case let .reportLayerSupport(layer, actionGloballyUniqueId, layerSupport):
                encoder.encodeInt32(SecretChatOutgoingOperationValue.reportLayerSupport.rawValue, forKey: "r")
                encoder.encodeInt32(layer.rawValue, forKey: "l")
                encoder.encodeInt64(actionGloballyUniqueId, forKey: "i")
                encoder.encodeInt32(layerSupport, forKey: "l")
            case let .pfsRequestKey(layer, actionGloballyUniqueId, rekeySessionId, a):
                encoder.encodeInt32(SecretChatOutgoingOperationValue.pfsRequestKey.rawValue, forKey: "r")
                encoder.encodeInt32(layer.rawValue, forKey: "l")
                encoder.encodeInt64(actionGloballyUniqueId, forKey: "i")
                encoder.encodeInt64(rekeySessionId, forKey: "s")
                encoder.encodeBytes(a, forKey: "a")
            case let .pfsAcceptKey(layer, actionGloballyUniqueId, rekeySessionId, gA, b):
                encoder.encodeInt32(SecretChatOutgoingOperationValue.pfsAcceptKey.rawValue, forKey: "r")
                encoder.encodeInt32(layer.rawValue, forKey: "l")
                encoder.encodeInt64(actionGloballyUniqueId, forKey: "i")
                encoder.encodeInt64(rekeySessionId, forKey: "s")
                encoder.encodeBytes(gA, forKey: "g")
                encoder.encodeBytes(b, forKey: "b")
            case let .pfsAbortSession(layer, actionGloballyUniqueId, rekeySessionId):
                encoder.encodeInt32(SecretChatOutgoingOperationValue.pfsAbortSession.rawValue, forKey: "r")
                encoder.encodeInt32(layer.rawValue, forKey: "l")
                encoder.encodeInt64(actionGloballyUniqueId, forKey: "i")
                encoder.encodeInt64(rekeySessionId, forKey: "s")
            case let .pfsCommitKey(layer, actionGloballyUniqueId, rekeySessionId, keyFingerprint):
                encoder.encodeInt32(SecretChatOutgoingOperationValue.pfsCommitKey.rawValue, forKey: "r")
                encoder.encodeInt32(layer.rawValue, forKey: "l")
                encoder.encodeInt64(actionGloballyUniqueId, forKey: "i")
                encoder.encodeInt64(rekeySessionId, forKey: "s")
                encoder.encodeInt64(keyFingerprint, forKey: "f")
            case let .noop(layer, actionGloballyUniqueId):
                encoder.encodeInt32(SecretChatOutgoingOperationValue.noop.rawValue, forKey: "r")
                encoder.encodeInt32(layer.rawValue, forKey: "l")
                encoder.encodeInt64(actionGloballyUniqueId, forKey: "i")
            case let .setMessageAutoremoveTimeout(layer, actionGloballyUniqueId, timeout, messageId):
                encoder.encodeInt32(SecretChatOutgoingOperationValue.setMessageAutoremoveTimeout.rawValue, forKey: "r")
                encoder.encodeInt32(layer.rawValue, forKey: "l")
                encoder.encodeInt64(actionGloballyUniqueId, forKey: "i")
                encoder.encodeInt32(timeout, forKey: "t")
                encoder.encodeInt64(messageId.peerId.toInt64(), forKey: "m.p")
                encoder.encodeInt32(messageId.namespace, forKey: "m.n")
                encoder.encodeInt32(messageId.id, forKey: "m.i")
            case let .terminate(reportSpam):
                encoder.encodeInt32(SecretChatOutgoingOperationValue.terminate.rawValue, forKey: "r")
                encoder.encodeInt32(reportSpam ? 1 : 0, forKey: "rs")
        }
    }
}

public final class SecretChatOutgoingOperation: PostboxCoding {
    public let contents: SecretChatOutgoingOperationContents
    public let mutable: Bool
    public let delivered: Bool
    
    public init(contents: SecretChatOutgoingOperationContents, mutable: Bool, delivered: Bool) {
        self.contents = contents
        self.mutable = mutable
        self.delivered = delivered
    }
    
    public init(decoder: PostboxDecoder) {
        self.contents = decoder.decodeObjectForKey("c", decoder: { SecretChatOutgoingOperationContents(decoder: $0) }) as! SecretChatOutgoingOperationContents
        self.mutable = decoder.decodeInt32ForKey("m", orElse: 0) != 0
        self.delivered = decoder.decodeInt32ForKey("d", orElse: 0) != 0
    }
    
    public func encode(_ encoder: PostboxEncoder) {
        encoder.encodeObject(self.contents, forKey: "c")
        encoder.encodeInt32(self.mutable ? 1 : 0, forKey: "m")
        encoder.encodeInt32(self.delivered ? 1 : 0, forKey: "d")
    }
    
    public func withUpdatedDelivered(_ delivered: Bool) -> SecretChatOutgoingOperation {
        return SecretChatOutgoingOperation(contents: self.contents, mutable: self.mutable, delivered: delivered)
    }
}