/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import Foundation

public enum TMessageType: Int32 {
  case call = 1
  case reply = 2
  case exception = 3
  case oneway = 4
}

public enum TType: Int32 {
  case stop     = 0
  case void     = 1
  case bool     = 2
  case i8       = 3
  case double   = 4
  case i16      = 6
  case i32      = 8
  case i64      = 10
  case string   = 11
  case `struct` = 12
  case map      = 13
  case set      = 14
  case list     = 15
  case utf8     = 16
  case utf16    = 17
}

public protocol TProtocol {
  var transport: TTransport { get set }
  init(on transport: TTransport)
  // Reading Methods
  
  func readMessageBegin() throws -> (String, TMessageType, Int32)
  func readMessageEnd() throws
  func readStructBegin() throws -> String
  func readStructEnd() throws
  func readFieldBegin() throws -> (String, TType, Int32)
  func readFieldEnd() throws
  func readMapBegin() throws -> (TType, TType, Int32)
  func readMapEnd() throws
  func readSetBegin() throws -> (TType, Int32)
  func readSetEnd() throws
  func readListBegin() throws -> (TType, Int32)
  func readListEnd() throws
  
  func read() throws -> String
  func read() throws -> Bool
  func read() throws -> UInt8
  func read() throws -> Int16
  func read() throws -> Int32
  func read() throws -> Int64
  func read() throws -> Double
  func read() throws -> Data
  
  // Writing methods
  
  func writeMessageBegin(name: String, type messageType: TMessageType, sequenceID: Int32) throws
  func writeMessageEnd() throws
  func writeStructBegin(name: String) throws
  func writeStructEnd() throws
  func writeFieldBegin(name: String, type fieldType: TType, fieldID: Int32) throws
  func writeFieldStop() throws
  func writeFieldEnd() throws
  func writeMapBegin(keyType: TType, valueType: TType, size: Int32) throws
  func writeMapEnd() throws
  func writeSetBegin(elementType: TType, size: Int32) throws
  func writeSetEnd() throws
  func writeListBegin(elementType: TType, size: Int32) throws
  func writeListEnd() throws

  func write(_ value: String) throws
  func write(_ value: Bool) throws
  func write(_ value: UInt8) throws
  func write(_ value: Int16) throws
  func write(_ value: Int32) throws
  func write(_ value: Int64) throws
  func write(_ value: Double) throws
  func write(_ value: Data) throws
}

public extension TProtocol {
  func writeFieldValue(_ value: TSerializable, name: String, type: TType, id: Int32) throws {
    try writeFieldBegin(name: name, type: type, fieldID: id)
    try value.write(to: self)
    try writeFieldEnd()
  }

  func validateValue(_ value: Any?, named name: String) throws {
    if value == nil {
      throw TProtocolError(error: .unknown, message: "Missing required value for field: \(name)")
    }
  }
  
  func readResultMessageBegin() throws {
    let (_, type, _) = try readMessageBegin();
    if type == .exception {
      let x = try readException()
      throw x
    }
    return
  }
  
  func readException() throws -> TApplicationError {
    return try TApplicationError.read(from: self)
  }
  
  func writeException(messageName name: String, sequenceID: Int32, ex: TApplicationError) throws {
    try writeMessageBegin(name: name, type: .exception, sequenceID: sequenceID)
    try ex.write(to: self)
    try writeMessageEnd()
  }
  
  func skip(type: TType) throws {
    switch type {
    case .bool:   _ = try read() as Bool
    case .i8:   _ = try read() as UInt8
    case .i16:    _ = try read() as Int16
    case .i32:    _ = try read() as Int32
    case .i64:    _ = try read() as Int64
    case .double: _ = try read() as Double
    case .string: _ = try read() as String
      
    case .struct:
      _ = try readStructBegin()
      while true {
        let (_, fieldType, _) = try readFieldBegin()
        if fieldType == .stop {
          break
        }
        try skip(type: fieldType)
        try readFieldEnd()
      }
      try readStructEnd()
      
      
    case .map:
      let (keyType, valueType, size) = try readMapBegin()
      for _ in 0..<size {
        try skip(type: keyType)
        try skip(type: valueType)
      }
      try readMapEnd()
      
      
    case .set:
      let (elemType, size) = try readSetBegin()
      for _ in 0..<size {
        try skip(type: elemType)
      }
      try readSetEnd()
      
    case .list:
      let (elemType, size) = try readListBegin()
      for _ in 0..<size {
        try skip(type: elemType)
      }
      try readListEnd()
      
    default:
      throw TProtocolError(error: .invalidData, message: "Invalid data")
    }
  }
}
