//
//  AminoDecoder.swift
//  swift-amino
//

import Foundation


/// A protocol for types which can be decoded from amino.
public protocol AminoDecodable: Decodable, AminoType {
    init(fromAmino decoder: AminoDecoder) throws
    init(fromAmino decoder: AminoDecoder, _ position: UInt8?) throws
    
}

/// Provide a default implementation which calls through to `Decodable`. This
/// allows `AminoDecodable` to use the `Decodable` implementation generated by the
/// compiler.
public extension AminoDecodable {
    init(fromAmino decoder: AminoDecoder) throws {
        try self.init(from: decoder)
    }
    
    init(fromAmino decoder: AminoDecoder, _ position: UInt8?) throws {
        try self.init(fromAmino: decoder)
    }
}


/// The actual amino decoder class.
public class AminoDecoder {
    fileprivate let data: [UInt8]
    fileprivate var _cursor = 0
    fileprivate var _pinnedCursor: Int?
    
    public var cursor: Int {
        get { _cursor }
    }
    
    public init(data: [UInt8]) {
        self.data = data
    }
    
    public func pin() throws {
        if let _ = _pinnedCursor {
            throw Error.alreadyPinned
        } else {
            _pinnedCursor = _cursor
        }
    }
    
    public func rePin()  {
        _pinnedCursor = _cursor
    }
    
    public func reset() {
        if let pinned = _pinnedCursor {
            _cursor = pinned
            _pinnedCursor = nil
        }
    }
}

/// A convenience function for creating a decoder from some data and decoding it
/// into a value all in one shot.
public extension AminoDecoder {
    static func decode<T: AminoDecodable>(_ type: T.Type, data: [UInt8]) throws -> T {
        // TODO: remove length
        // TODO: if struct, remove struct?
        let prefix: UInt8 = UInt8(T.typ3) // position set to 0
        
        return try AminoDecoder(data: [prefix] + data).decode(T.self)
    }
}

/// The error type.
public extension AminoDecoder {
    /// All errors which `AminoDecoder` itself can throw.
    enum Error: Swift.Error {
        /// The decoder hit the end of the data while the values it was decoding expected
        /// more.
        case prematureEndOfData
        
        /// Attempted to decode a type which is `Decodable`, but not `AminoDecodable`. (We
        /// require `AminoDecodable` because `AminoDecoder` doesn't support full keyed
        /// coding functionality.)
        case typeNotConformingToAminoDecodable(Any.Type)
        
        /// Attempted to decode a type which is not `Decodable`.
        case typeNotConformingToDecodable(Any.Type)
        
        /// Attempted to decode a type which is not `AminoType`.
        case typeNotConformingToAminoType(Any.Type)
        
        /// Attempted to decode an `Int` which can't be represented. This happens in 32-bit
        /// code when the stored `Int` doesn't fit into 32 bits.
        case intOutOfRange(Int64)
        
        /// Attempted to decode a `UInt` which can't be represented. This happens in 32-bit
        /// code when the stored `UInt` doesn't fit into 32 bits.
        case uintOutOfRange(UInt64)
        
        /// Attempted to decode a `Bool` where the byte representing it was not a `1` or a
        /// `0`.
        case boolOutOfRange(UInt8)
        
        /// Attempted to decode a `String` but the encoded `String` data was not valid
        /// UTF-8.
        case invalidUTF8([UInt8])
        
        case UnregisteredConcreteType([UInt8])
        
        case UnexpectedConcreteType([UInt8])
        
        case missingValueForKey(CodingKey)
        
        case alreadyPinned
    }
}

/// Methods for decoding various types.
public extension AminoDecoder {
    
    func decode(_ type: Bool.Type) throws -> Bool {
        switch try decode(UInt8.self) {
        case 0: return false
        case 1: return true
        case let x: throw Error.boolOutOfRange(x)
        }
    }
    

    
    func decode<T: Decodable>(_ type: T.Type) throws -> T {
        switch type {
        case let t as AminoDecodable.Type:
            // remove first bytes (typ3 & field_number << 3)
            let (pos, typ3) = try self.readPrefix()
            let expected_typ3 = t.typ3
            assert(typ3 == UInt8(expected_typ3))
            switch t {
            case _ as AminoCodableInterface.Type:
                // remove length
                let _ = try self.readVarint()
                // TODO: should anything be done with the length?
                // TODO: length is useful to jump to the next key
                // get hold of underlying type from the prefix
                var typeArray = Array<UInt8>(repeating: 0, count: 7)
                try self.peek(into: &typeArray)
                // get the type from the serialized data
                let t : AminoCodableInterface.Type
                if let type = ConcreteTypeRegistry.getType(typeArray) {
                    try self.read(into: &typeArray)
                    t = type
                } else if let type = ConcreteTypeRegistry.getType([typeArray[0], typeArray[1], typeArray[2], typeArray[3]]) {
                    var ta = Array<UInt8>(repeating: 0, count: 4)
                    try self.read(into: &ta)
                    t = type
                } else {
                    throw Error.UnregisteredConcreteType(typeArray)
                }
                
                // confirm the type is expected
                guard let _ = t as? T.Type else {
                    throw Error.UnexpectedConcreteType(typeArray)
                }
                
                // aminoStruct is possibly a protocol
                // TODO: if so, we need to first create a new instance of the right type and cast
                
                return try t.init(fromAmino: self, pos) as! T
                
                
                // TODO: peek 7 bytes ahead
                // check if first 4 bytes are a hit in the registry
                // if not, check if 7 bytes are a hit
                // should check if there is ambiguity in 4 versus 7
                // extract if interface or concrete type
                // if interface, then expect an address (= pointer)
                // if concrete type, get hash of expected content
                // if expected content is interface, confirm that the type is derived from that interface
                // if concrete type, check it's the same.
                
            default:
                return try t.init(fromAmino: self, pos) as! T
            }
        default:
            throw Error.typeNotConformingToAminoDecodable(type)
        }
        
    }
    
    
    func aminoDecode<T>(_ type: T.Type) throws -> T where T: AminoDecodable {
        
        switch type {
            
        case let aminoStruct as AminoCodableInterface.Type:
            // remove first bytes (typ3 & field_number << 3)
            let (pos, typ3) = try self.readPrefix()
            let expected_typ3 = aminoStruct.typ3
            assert(typ3 == UInt8(expected_typ3))
            // remove length
            let _ = try self.readVarint()
            // TODO: should anything be done with the length?
            // TODO: length is useful to jump to the next key
            // get hold of underlying type from the prefix
            var typeArray = Array<UInt8>(repeating: 0, count: 7)
            try self.peek(into: &typeArray)
            // get the type from the serialized data
            let t : AminoCodableInterface.Type
            if let type = ConcreteTypeRegistry.getType(typeArray) {
                try self.read(into: &typeArray)
                t = type
            } else if let type = ConcreteTypeRegistry.getType([typeArray[0], typeArray[1], typeArray[2], typeArray[3]]) {
                var ta = Array<UInt8>(repeating: 0, count: 4)
                try self.read(into: &ta)
                t = type
            } else {
                throw Error.UnregisteredConcreteType(typeArray)
            }
            
            // confirm the type is expected
            guard let _ = t as? T.Type else {
                throw Error.UnexpectedConcreteType(typeArray)
            }
            
            // aminoStruct is possibly a protocol
            // TODO: if so, we need to first create a new instance of the right type and cast
            
            return try t.init(fromAmino: self, pos) as! T
            
            
            // TODO: peek 7 bytes ahead
            // check if first 4 bytes are a hit in the registry
            // if not, check if 7 bytes are a hit
            // should check if there is ambiguity in 4 versus 7
            // extract if interface or concrete type
            // if interface, then expect an address (= pointer)
            // if concrete type, get hash of expected content
            // if expected content is interface, confirm that the type is derived from that interface
            // if concrete type, check it's the same.
            
        case let aminoT as AminoDecodable.Type:
            
            // remove first bytes (typ3 & field_number << 3)
            let (pos, typ3) = try self.readPrefix()
            let expected_typ3 = aminoT.typ3
            assert(typ3 == UInt8(expected_typ3))
            
            // remove length if struct/list
            //if typ3 == UInt8(AminoWireType.bytes) {
            //let _ = try self.readVarint()
            // TODO: should anything be done with the length?
            // TODO: yes if string or data... should only remove if struct
            //}
            return try aminoT.init(fromAmino: self, pos) as! T
            
        default:
            throw Error.typeNotConformingToAminoDecodable(type)
        }
    }

    
//    func decode<T: Decodable>(_ type: Array<T>.Type) throws -> Array<T> where  T: AminoComplex {
//        throw Error.typeNotConformingToAminoDecodable(type)
//    }
//
//    func aminoDecode<T: AminoDecodable>(_ type: T.Type) throws -> T {
//        return try type.init(from: self)
//    }
    
    
    /// Read the appropriate number of raw bytes directly into the given value. No byte
    /// swapping or other postprocessing is done.
    func read<T>(into: inout T) throws {
        try read(MemoryLayout<T>.size, into: &into)
    }
    
    /// Peek the appropriate number of raw bytes directly into the given value. No byte
    /// swapping or other postprocessing is done.
    func peek<T>(into: inout T) throws {
        try peek(MemoryLayout<T>.size, into: &into)
    }
    
    func read<T>(into: inout Array<T>) throws {
        try read(MemoryLayout<T>.size * into.count, into: &into)
    }
    
    
    
    func peek<T>(into: inout Array<T>) throws {
        try peek(MemoryLayout<T>.size * into.count, into: &into)
    }
    
    func readPrefix() throws -> (UInt8, UInt8) {
        var prefix = UInt8(0)
        try read(into: &prefix)
        let position = prefix >> 3
        let typ3 = prefix & 0x7 // get 3 LSB
        return (position, typ3)
    }
    
    
    func peekPrefix() throws -> (UInt8, UInt8) {
        var prefix = UInt8(0)
        try peek(into: &prefix)
        let position = prefix >> 3
        let typ3 = prefix & 0x7 // get 3 LSB
        return (position, typ3)
    }
    
    func readListType() throws -> UInt8 {
        var prefix = UInt8(0)
        try read(into: &prefix)
        let typ3 = prefix & 0xF // get 4 LSB (includes nillable bit)
        return typ3
    }
    
    func readVarint() throws -> [UInt8] {
        // reads a Varint
        var varintArray = [UInt8]()
        var more = true
        while (more) {
            var val = UInt8(0)
            try read(into: &val)
            varintArray.append(val)
            if (val >> 7 == 0) {
                more = false
            }
        }
        return varintArray
    }
    
    func readUnsignedVarint<T: UnsignedInteger>(into: inout T) throws {
        let array = try readVarint()
        into = T(truncatingIfNeeded: varintDecode(array))
    }
    
    func readSignedVarint<T: SignedInteger>(into: inout T) throws {
        // zigZag encoded
        let array = try readVarint()
        let negative: Bool = array.last! & 0x1 == 1
        // check if the size of T
        assert(array.count * 7 / 8 <= MemoryLayout<T>.size)
        let value = array.reduce(0, { (r, u) -> T in
            return r << 7 + T(u & 0x7F)
        })
        into = (negative ? -value - 1: value) / 2
    }
}

/// Internal methods for decoding raw data.
private extension AminoDecoder {
    /// Read the given number of bytes into the given pointer, advancing the cursor
    /// appropriately.
    func read(_ byteCount: Int, into: UnsafeMutableRawPointer) throws {
        try peek(byteCount, into: into)
        _cursor += byteCount
    }
    /// Peek the given number of bytes into the given pointer, leaving the cursor
    /// as it is.
    func peek(_ byteCount: Int, into: UnsafeMutableRawPointer) throws {
        if _cursor + byteCount > data.count {
            throw Error.prematureEndOfData
        }
        
        data.withUnsafeBytes({
            let from = $0.baseAddress! + _cursor
            memcpy(into, from, byteCount)
        })
    }
}

extension AminoDecoder: Decoder {
    public var codingPath: [CodingKey] { return [] }
    
    public var userInfo: [CodingUserInfoKey : Any] { return [:] }
    
    public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey {
        return KeyedDecodingContainer(KeyedContainer<Key>(decoder: self))
    }
    
    public func unkeyedContainer() throws -> UnkeyedDecodingContainer {
        return UnkeyedContainer(decoder: self)
    }
    
    public func singleValueContainer() throws -> SingleValueDecodingContainer {
        return UnkeyedContainer(decoder: self)
    }
    
    private struct KeyedContainer<Key: CodingKey>: KeyedDecodingContainerProtocol {
        var decoder: AminoDecoder
        
        var codingPath: [CodingKey] { return [] }
        
        var allKeys: [Key] { return [] }
        
        func contains(_ key: Key) -> Bool {
            return true
        }
        
        func typeId<T>(_ type: T.Type) where T: Sequence{
            print("Array type: \(type)")
        }
        
        func typeId<T>(_ type: T.Type) {
            print("scalar type \(type)")
        }
        
        
        func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable {
            let (pos,_) = try decoder.peekPrefix()
            // TODO: fields are not necessarily in order...
            // TODO: so we should look for the first item with this position in the stream
            // TODO:
            if (pos == key.intValue! + 1) { // index of position 1 is encoded as 1, not 0
                // TODO: if this is a non packed array, this should iterate over the remaining data rather than just get the first element
                // TODO: this would also allow to correctly form the array
                //if let tt = T.self as? AminoList.Type {
                typeId(type)
                //}
                switch type {
                case let s as [AminoComplex].Type:
                    print(s)
                    return try decoder.decode(T.self)
                    
                default:
                    return try decoder.decode(T.self)
                }
                
            } else { // not present
                switch type {
                // TODO: if it's an array, it could mean that the array is empty
                case let optional as ExpressibleByNilLiteral.Type:
                    return optional.init(nilLiteral: ()) as! T
                default:
                    throw Error.missingValueForKey(key)
                }
            }
        }
        
        func decodeNil(forKey key: Key) throws -> Bool {
            let (pos,_) = try decoder.peekPrefix()
            return pos != key.intValue! + 1 // index of position 1 is encoded as 1, not 0
        }
        
        func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
            return try decoder.container(keyedBy: type)
        }
        
        func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
            return try decoder.unkeyedContainer()
        }
        
        func superDecoder() throws -> Decoder {
            return decoder
        }
        
        func superDecoder(forKey key: Key) throws -> Decoder {
            return decoder
        }
    }
    
    private struct UnkeyedContainer: UnkeyedDecodingContainer, SingleValueDecodingContainer {
        var decoder: AminoDecoder
        
        var codingPath: [CodingKey] { return [] }
        
        var count: Int? { return nil }
        
        var currentIndex: Int { return 0 }
        
        var isAtEnd: Bool { return false }
        
        func decode<T>(_ type: T.Type) throws -> T where T : Decodable {
            return try decoder.decode(type)
        }
        
        func decodeNil() -> Bool {
            return true
        }
        
        func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
            return try decoder.container(keyedBy: type)
        }
        
        func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
            return self
        }
        
        func superDecoder() throws -> Decoder {
            return decoder
        }
    }
}

private extension FixedWidthInteger {
    static func from(aminoDecoder: AminoDecoder) throws -> Self {
        var v = Self.init()
        try aminoDecoder.read(into: &v)
        return self.init(bigEndian: v)
    }
}
