package com.twitter.recos.user_tweet_entity_graph

import com.twitter.graphjet.bipartite.api.EdgeTypeMask
import com.twitter.recos.recos_common.thriftscala.SocialProofType
import com.twitter.recos.util.Action

/**
 * The bit mask is used to encode edge types in the top bits of an integer,
 * e.g. favorite, retweet, reply and click. Under current segment configuration, each segment
 * stores up to 128M edges. Assuming that each node on one side is unique, each segment
 * stores up to 128M unique nodes on one side, which occupies the lower 27 bits of an integer.
 * This leaves five bits to encode the edge types, which at max can store 32 edge types.
 * The following implementation utilizes the top four bits and leaves one free bit out.
 */
class UserTweetEdgeTypeMask extends EdgeTypeMask {
  import UserTweetEdgeTypeMask._

  override def encode(node: Int, edgeType: Byte): Int = {
    if (edgeType < 0 || edgeType > SIZE || edgeType == Click.id.toByte) {
      throw new IllegalArgumentException("encode: Illegal edge type argument " + edgeType)
    } else {
      node | (edgeType << 28)
    }
  }

  override def edgeType(node: Int): Byte = {
    (node >>> 28).toByte
  }

  override def restore(node: Int): Int = {
    node & MASK
  }
}

object UserTweetEdgeTypeMask extends Enumeration {

  type UserTweetEdgeTypeMask = Value

  /**
   * Byte values corresponding to the action taken on a tweet, which will be encoded in the
   * top 4 bits in a tweet Id
   * NOTE: THERE CAN ONLY BE UP TO 16 TYPES
   */
  val Click: UserTweetEdgeTypeMask = Value(0)
  val Favorite: UserTweetEdgeTypeMask = Value(1)
  val Retweet: UserTweetEdgeTypeMask = Value(2)
  val Reply: UserTweetEdgeTypeMask = Value(3)
  val Tweet: UserTweetEdgeTypeMask = Value(4)
  val IsMentioned: UserTweetEdgeTypeMask = Value(5)
  val IsMediatagged: UserTweetEdgeTypeMask = Value(6)
  val Quote: UserTweetEdgeTypeMask = Value(7)
  val Unfavorite: UserTweetEdgeTypeMask = Value(8)

  /**
   * Reserve the top four bits of each integer to encode the edge type information.
   */
  val MASK: Int = Integer.parseInt("00001111111111111111111111111111", 2)
  val SIZE: Int = this.values.size

  /**
   * Map valid social proof types specified by clients to an array of bytes. If clients do not
   * specify any social proof types in thrift, it will return all available social types by
   * default.
   *
   * @param socialProofTypes are the valid socialProofTypes specified by clients
   * @return an array of bytes representing valid social proof types
   */
  def getUserTweetGraphSocialProofTypes(
    socialProofTypes: Option[Seq[SocialProofType]]
  ): Array[Byte] = {
    socialProofTypes
      .map { _.map { _.getValue }.toArray }
      .getOrElse((0 until SIZE).toArray)
      .map { _.toByte }
  }

  /**
   * Converts the action byte in the RecosHoseMessage into GraphJet internal byte mapping
   */
  def actionTypeToEdgeType(actionByte: Byte): Byte = {
    val edgeType = Action(actionByte) match {
      case Action.Favorite => Favorite.id
      case Action.Retweet => Retweet.id
      case Action.Reply => Reply.id
      case Action.Tweet => Tweet.id
      case Action.IsMentioned => IsMentioned.id
      case Action.IsMediaTagged => IsMediatagged.id
      case Action.Quote => Quote.id
      case Action.Unfavorite => Unfavorite.id
      case _ =>
        throw new IllegalArgumentException("getEdgeType: Illegal edge type argument " + actionByte)
    }
    edgeType.toByte
  }
}
