package scredis.protocol

import java.nio.ByteBuffer

import scredis.exceptions._
import scredis.serialization.UTF8StringReader

import scala.concurrent.{Future, Promise}

/** A trait for requests which operate on at least one key.
  * Needed to handle cluster sharding. */
trait Key {
  val key: String
}

/** Marker trait for requests that make sense on any member of a cluster. */
trait Cluster

abstract class Request[A](command: Command, args: Any*) {
  private var promise = Promise[A]()
  private var _buffer: ByteBuffer = null
  private var _bytes: Array[Byte] = null
  def future: Future[A] = promise.future
  val repliesCount = 1

  /** Reset the state of this Request to reuse it. */
  private[scredis] def reset(): Unit = {
    promise = Promise[A]()
    _buffer = null
    _bytes = null
  }
  
  private[scredis] def encode(): Unit = if (_buffer == null && _bytes == null) {
    command match {
      case x: ZeroArgCommand  => _bytes = x.encoded
      case _                  => _buffer = command.encode(args.toList)
    }
  }
  
  private[scredis] def encoded: Either[Array[Byte], ByteBuffer] = if (_bytes != null) {
    Left(_bytes)
  } else {
    Right(_buffer)
  }
  
  private[scredis] def complete(response: Response): Unit = {
    response match {
      case SimpleStringResponse("QUEUED") =>
      case ClusterErrorResponse(error,message) => failure(RedisClusterErrorResponseException(error,message))
      case ErrorResponse(message) => failure(RedisErrorResponseException(message))
      case response => try {
        success(decode(response))
      } catch {
        case e @ RedisTransactionAbortedException => failure(e)
        case e: RedisReaderException => failure(e)
        case e: Throwable => failure(
            RedisProtocolException(s"Unexpected response for request '$this': $response", e)
           )
      }
    }
  }
  
  private[scredis] def success(value: Any): Unit = {
    try {
      promise.success(value.asInstanceOf[A])
    } catch {
      case e: IllegalStateException =>
    } finally {
      Protocol.release()
    }
  }
  
  private[scredis] def failure(throwable: Throwable): Unit = {
    try {
      promise.failure(throwable)
    } catch {
      case e: IllegalStateException =>
    } finally {
      Protocol.release()
    }
  }
  
  def decode: Decoder[A]

  def argsCount: Int = args.size
  def isReadOnly: Boolean = command.isReadOnly
  
  override def toString: String = (command +: args).map {
    case bytes: Array[Byte] => UTF8StringReader.read(bytes)
    case x                  => x.toString
  }.mkString(" ")
  
}