Contact.scala

package wechaty.user

import com.typesafe.scalalogging.LazyLogging
import wechaty.Wechaty.PuppetResolver
import wechaty.puppet.ResourceBox
import wechaty.puppet.schemas.Contact.{ContactGender, ContactPayload, ContactType}
import wechaty.puppet.schemas.Puppet

import scala.concurrent.{Await, Future}
import scala.concurrent.duration._
import scala.language.implicitConversions

/**
  *
  * All wechat contacts(friend) will be encapsulated as a Contact.
  * [Examples/Contact-Bot]{@link https://github.com/wechaty/wechaty/blob/1523c5e02be46ebe2cc172a744b2fbe53351540e/examples/contact-bot.ts}
  *
  * @property {string}  id               - Get Contact id.
  *           This function is depending on the Puppet Implementation, see [puppet-compatible-table](https://github.com/wechaty/wechaty/wiki/Puppet#3-puppet-compatible-table)
  * @author <a href="mailto:jcai@ganshane.com">Jun Tsai</a>
  * @since 2020-06-03
  */
class Contact(contactId: String)(implicit resolver: PuppetResolver) extends Conversation(contactId) with LazyLogging {
  //  lazy val payload: schemas.Contact.ContactPayload = resolver.puppet.contactPayload(contactId)
  def payload: ContactPayload = {
    //TODO use async
    val f = resolver.puppet.contactPayload(contactId)
    Await.result(f,10 seconds)
  }


  /**
    * Get the name from a contact
    *
    * @returns {string}
    * @example
    * const name = contact.name()
    */
  def name: String = {
    if (this.payload != null)
      this.payload.name
    else null
  }



  /**
    * GET / SET / DELETE the alias for a contact
    *
    * Tests show it will failed if set alias too frequently(60 times in one minute).
    *
    * @param {(none | string | null)} newAlias
    * @returns {(Promise<null | string | void>)}
    * @example <caption> GET the alias for a contact, return {(Promise<string | null>)}</caption>
    *          const alias = await contact.alias()
    *          if (alias === null) {
    *   console.log('You have not yet set any alias for contact ' + contact.name())
    *          } else {
    *   console.log('You have already set an alias for contact ' + contact.name() + ':' + alias)
    *          }
    * @example <caption>SET the alias for a contact</caption>
    *          try {
    *          await contact.alias('lijiarui')
    *   console.log(`change ${contact.name()}'s alias successfully!`)
    *          } catch (e) {
    *   console.log(`failed to change ${contact.name()} alias!`)
    *          }
    * @example <caption>DELETE the alias for a contact</caption>
    *          try {
    *          const oldAlias = await contact.alias(null)
    *   console.log(`delete ${contact.name()}'s alias successfully!`)
    *   console.log('old alias is ${oldAlias}`)
    *          } catch (e) {
    *   console.log(`failed to delete ${contact.name()}'s alias!`)
    *          }
    **/
  def alias(newAlias: String): String = {

    if (this.payload == null) {
      throw new Error("no payload")
    }


    resolver.puppet.contactAlias(this.id, newAlias)
    resolver.puppet.contactPayloadDirty(this.id)
    this.payload.alias
  }

  /**
    *
    * @description
    * Should use { @link Contact#friend} instead
    * @deprecated
    * @ignore
    */
  def stranger(): Boolean = {
    if (this.payload == null) return false
    else !this.friend()
  }

  /**
    * Check if contact is friend
    *
    * > Tips:
    * This function is depending on the Puppet Implementation, see [puppet-compatible-table](https://github.com/wechaty/wechaty/wiki/Puppet#3-puppet-compatible-table)
    *
    * @returns {boolean | null}
    *
    *          <br>True for friend of the bot <br>
    *          False for not friend of the bot, null for unknown.
    * @example
    * const isFriend = contact.friend()
    */
  def friend(): Boolean = {
    if (this.payload == null) false
    else this.payload.friend
  }


  /**
    * Enum for ContactType
    *
    * @enum {number}
    * @property {number} Unknown    - ContactType.Unknown    (0) for Unknown
    * @property {number} Personal   - ContactType.Personal   (1) for Personal
    * @property {number} Official   - ContactType.Official   (2) for Official
    */

  /**
    * Return the type of the Contact
    * > Tips: ContactType is enum here.</br>
    *
    * @returns {ContactType.Unknown | ContactType.Personal | ContactType.Official}
    * @example
    * const bot = new Wechaty()
    * await bot.start()
    * const isOfficial = contact.type() === bot.Contact.Type.Official
    */
  def `type`(): ContactType.Type = {
    if (this.payload == null) {
      throw new Error("no payload")
    }
    this.payload.`type`
  }

  /**
    * @ignore
    * TODO
    * Check if the contact is star contact.
    * @returns {boolean | null} - True for star friend, False for no star friend.
    * @example
    * const isStar = contact.star()
    */
  def star(): Boolean = {
    if (this.payload == null) false
    else this.payload.star
  }

  /**
    * Contact gender
    * > Tips: ContactGender is enum here. </br>
    *
    * @returns {ContactGender.Unknown | ContactGender.Male | ContactGender.Female}
    * @example
    * const gender = contact.gender() === bot.Contact.Gender.Male
    */
  def gender(): ContactGender.Type = {
    if (this.payload == null) ContactGender.Unknown
    else payload.gender
  }

  /**
    * Get the region 'province' from a contact
    *
    * @returns {string | null}
    * @example
    * const province = contact.province()
    */
  def province(): String = {
    if (this.payload == null) null
    else this.payload.province
  }

  /**
    * Get the region 'city' from a contact
    *
    * @returns {string | null}
    * @example
    * const city = contact.city()
    */
  def city(): String = {
    if (this.payload == null) null
    else this.payload.city
  }

  def avatar: Future[ResourceBox] =  {
    resolver.puppet.contactAvatar(this.id)
  }

  /**
    * Get all tags of contact
    *
    * @returns {Promise<Tag[]>}
    * @example
    * const tags = await contact.tags()
    */
  def tags(): Array[Tag] = {

    val tagIdList = resolver.puppet.tagContactList(this.id)
    tagIdList.map(id => new Tag(id))
  }


  /**
    * Force reload data for Contact, Sync data from lowlevel API again.
    *
    * @returns {Promise<this>}
    * @example
    * await contact.sync()
    */
  def sync(): Unit = {
    this.ready(true)
  }

  /**
    * `ready()` is For FrameWork ONLY!
    *
    * Please not to use `ready()` at the user land.
    * If you want to sync data, uyse `sync()` instead.
    *
    * @ignore
    */
  def ready(forceSync: Boolean = false): Unit = {

    if (!forceSync && this.isReady()) { // already ready
      logger.debug("Contact ready() isReady() true")
      return
    }

    if (forceSync) {
      resolver.puppet.contactPayloadDirty(this.id)
    }
  }

  /**
    * @ignore
    */
  def isReady(): Boolean = {
    this.payload != null && !Puppet.isBlank(this.payload.name)
  }

  /**
    * Check if contact is self
    *
    * @returns {boolean} True for contact is self, False for contact is others
    * @example
    * const isSelf = contact.self()
    */
  def self(): Boolean = {
    val userIdOpt = resolver.puppet.selfIdOpt()
    userIdOpt match {
      case Some(userId) =>
        this.id == userId
      case _ => false
    }
  }

  /**
    * Get the weixin number from a contact.
    *
    * Sometimes cannot get weixin number due to weixin security mechanism, not recommend.
    *
    * @ignore
    * @returns {string | null}
    * @example
    * const weixin = contact.weixin()
    */
  def weixin(): String = {
    if (this.payload == null) null
    else payload.weixin
  }

  override def toString: String = {

    val identity =
      if (!Puppet.isBlank(payload.alias))
        this.payload.alias
      else if (!Puppet.isBlank(payload.name))
        payload.name
      else if (!Puppet.isBlank(this.id))
        id
      else "loading..."

    s"Contact<${identity}>"
  }
}