content: package com.acgist.snail.config;
  
  import java.util.HashMap;
  import java.util.Map;
  
  import com.acgist.snail.logger.Logger;
  import com.acgist.snail.logger.LoggerFactory;
  import com.acgist.snail.net.torrent.peer.DhtExtensionMessageHandler;
  import com.acgist.snail.net.torrent.peer.ExtensionMessageHandler;
  import com.acgist.snail.net.torrent.peer.PeerSubMessageHandler;
  import com.acgist.snail.net.torrent.peer.extension.DontHaveExtensionMessageHandler;
  import com.acgist.snail.net.torrent.peer.extension.HolepunchMessageHnadler;
  import com.acgist.snail.net.torrent.peer.extension.MetadataMessageHandler;
  import com.acgist.snail.net.torrent.peer.extension.PeerExchangeMessageHandler;
  import com.acgist.snail.net.torrent.peer.extension.UploadOnlyExtensionMessageHandler;
  import com.acgist.snail.utils.ArrayUtils;
  import com.acgist.snail.utils.EnumUtils;
  import com.acgist.snail.utils.PeerUtils;
  
  /**
   * Peer配置
   * 
   * 保留位协议：http://www.bittorrent.org/beps/bep_0004.html
   * Peer Exchange（PEX）协议：http://www.bittorrent.org/beps/bep_0011.html
   * Peer ID Conventions协议：http://www.bittorrent.org/beps/bep_0020.html
   * 
   * @author acgist
   */
  public final class PeerConfig extends PropertiesConfig {
      
      private static final Logger LOGGER = LoggerFactory.getLogger(PeerConfig.class);
      
      /**
       * 协议消息类型
       * 协议链接：http://www.bittorrent.org/beps/bep_0004.html
       * 
       * @author acgist
       */
      public enum Type {
          
          /**
           * 阻塞
           */
          CHOKE((byte) 0x00),
          /**
           * 解除阻塞
           */
          UNCHOKE((byte) 0x01),
          /**
           * 感兴趣
           */
          INTERESTED((byte) 0x02),
          /**
           * 不感兴趣
           */
          NOT_INTERESTED((byte) 0x03),
          /**
           * 包含Piece
           */
          HAVE((byte) 0x04),
          /**
           * Piece位图
           */
          BITFIELD((byte) 0x05),
          /**
           * 请求
           */
          REQUEST((byte) 0x06),
          /**
           * 数据
           */
          PIECE((byte) 0x07),
          /**
           * 取消
           */
          CANCEL((byte) 0x08),
          /**
           * DHT
           */
          DHT((byte) 0x09),
          /**
           * 推荐Piece
           */
          SUGGEST_PIECE((byte) 0x0D),
          /**
           * 所有Piece
           */
          HAVE_ALL((byte) 0x0E),
          /**
           * 没有Piece
           */
          HAVE_NONE((byte) 0x0F),
          /**
           * 拒绝请求
           */
          REJECT_REQUEST((byte) 0x10),
          /**
           * 快速允许
           */
          ALLOWED_FAST((byte) 0x11),
          /**
           * 扩展
           */
          EXTENSION((byte) 0x14);
          
          /**
           * 消息ID
           */
          private final byte id;
          
          /**
           * 索引数据
           */
          private static final Type[] INDEX = EnumUtils.index(Type.class, Type::getId);
          
          /**
           * @param id 消息ID
           */
          private Type(byte id) {
              this.id = id;
          }
          
          /**
           * @return 消息ID
           */
          public byte getId() {
              return this.id;
          }
          
          /**
           * @param id 消息ID
           * 
           * @return 协议消息类型
           */
          public static final Type of(byte id) {
              if(id < 0 || id >= INDEX.length) {
                  return null;
              }
              return INDEX[id];
          }
          
      }
      
      /**
       * Peer来源
       * 
       * @author acgist
       */
      public enum Source {
          
          /**
           * PEX
           */
          PEX((byte) (1 << 0)),
          /**
           * DHT
           */
          DHT((byte) (1 << 1)),
          /**
           * 本地发现
           */
          LSD((byte) (1 << 2)),
          /**
           * Tracker
           */
          TRACKER((byte) (1 << 3)),
          /**
           * 主动接入
           */
          CONNECT((byte) (1 << 4)),
          /**
           * holepunch
           */
          HOLEPUNCH((byte) (1 << 5));
          
          /**
           * 来源标识
           */
          private final byte value;
          
          /**
           * @param value 来源标识
           */
          private Source(byte value) {
              this.value = value;
          }
          
          /**
           * @return 来源标识
           */
          public byte getValue() {
              return this.value;
          }
          
          /**
           * @return 是否优先使用
           * 
           * @see #PEX
           * @see #LSD
           * @see #CONNECT
           */
          public boolean preference() {
              return this == PEX || this == LSD || this == CONNECT;
          }
          
      }
      
      /**
       * 扩展协议消息类型
       * 
       * @author acgist
       * 
       * @see Type#EXTENSION
       */
      public enum ExtensionType {
          
          /**
           * 握手
           * 
           * @see ExtensionMessageHandler
           */
          HANDSHAKE((byte) 0x00, "handshake", true, false),
          /**
           * ut_pex
           * 
           * @see PeerExchangeMessageHandler
           */
          UT_PEX((byte) 0x01, "ut_pex", true, true),
          /**
           * ut_metadata
           * 
           * @see MetadataMessageHandler
           */
          UT_METADATA((byte) 0x02, "ut_metadata", true, true),
          /**
           * ut_holepunch
           * 
           * @see HolepunchMessageHnadler
           */
          UT_HOLEPUNCH((byte) 0x03, "ut_holepunch", true, true),
          /**
           * upload_only
           * 
           * @see UploadOnlyExtensionMessageHandler
           */
          UPLOAD_ONLY((byte) 0x04, "upload_only", true, true),
          /**
           * lt_donthave
           * 
           * @see DontHaveExtensionMessageHandler
           */
          LT_DONTHAVE((byte) 0x05, "lt_donthave", true, true);
  
          /**
           * 消息ID
           * 客户端自定义
           */
          private final byte id;
          /**
           * 协议名称
           */
          private final String value;
          /**
           * 是否支持
           */
          private final boolean support;
          /**
           * 是否通知
           * 握手是否通知支持扩展
           */
          private final boolean notice;
          
          /**
           * @param id      消息ID
           * @param value   协议名称
           * @param support 是否支持
           * @param notice  是否通知
           */
          private ExtensionType(byte id, String value, boolean support, boolean notice) {
              this.id      = id;
              this.value   = value;
              this.support = support;
              this.notice  = notice;
          }
          
          /**
           * @return 消息ID
           */
          public byte getId() {
              return this.id;
          }
          
          /**
           * @return 协议名称
           */
          public String getValue() {
              return this.value;
          }
          
          /**
           * @return 是否支持
           */
          public boolean getSupport() {
              return this.support;
          }
          
          /**
           * @return 是否通知
           */
          public boolean getNotice() {
              return this.notice;
          }
          
          /**
           * @param id 消息ID
           * 
           * @return 扩展协议消息类型
           */
          public static final ExtensionType of(byte id) {
              final ExtensionType[] types = ExtensionType.values();
              for (final ExtensionType type : types) {
                  if(type.id == id) {
                      return type;
                  }
              }
              return null;
          }
          
          /**
           * @param value 协议名称
           * 
           * @return 扩展协议消息类型
           */
          public static final ExtensionType of(String value) {
              final ExtensionType[] types = ExtensionType.values();
              for (final ExtensionType type : types) {
                  if(type.value.equalsIgnoreCase(value)) {
                      return type;
                  }
              }
              return null;
          }
  
          @Override
          public String toString() {
              return this.value;
          }
          
      }
      
      /**
       * Metadata协议消息类型
       * 
       * @author acgist
       * 
       * @see ExtensionType#UT_METADATA
       */
      public enum MetadataType {
          
          /**
           * 请求
           */
          REQUEST((byte) 0x00),
          /**
           * 数据
           */
          DATA((byte) 0x01),
          /**
           * 拒绝
           */
          REJECT((byte) 0x02);
          
          /**
           * 消息ID
           */
          private final byte id;
          
          /**
           * @param id 消息ID
           */
          private MetadataType(byte id) {
              this.id = id;
          }
          
          /**
           * @return 消息ID
           */
          public byte getId() {
              return this.id;
          }
          
          /**
           * @param id 消息ID
           * 
           * @return Metadata协议消息类型
           */
          public static final MetadataType of(byte id) {
              final MetadataType[] types = MetadataType.values();
              for (final MetadataType type : types) {
                  if(type.id == id) {
                      return type;
                  }
              }
              return null;
          }
          
      }
  
      /**
       * Holepunch协议消息类型
       * 
       * @author acgist
       * 
       * @see ExtensionType#UT_HOLEPUNCH
       */
      public enum HolepunchType {
          
          /**
           * 约定
           */
          RENDEZVOUS((byte) 0x00),
          /**
           * 连接
           */
          CONNECT((byte) 0x01),
          /**
           * 错误
           */
          ERROR((byte) 0x02);
          
          /**
           * 消息ID
           */
          private final byte id;
          
          /**
           * @param id 消息ID
           */
          private HolepunchType(byte id) {
              this.id = id;
          }
          
          /**
           * @return 消息ID
           */
          public byte getId() {
              return this.id;
          }
          
          /**
           * @param id 消息ID
           * 
           * @return Holepunch协议消息类型
           */
          public static final HolepunchType of(byte id) {
              final HolepunchType[] types = HolepunchType.values();
              for (final HolepunchType type : types) {
                  if(type.id == id) {
                      return type;
                  }
              }
              return null;
          }
          
      }
      
      /**
       * Holepunch协议错误编码
       * 
       * @author acgist
       * 
       * @see HolepunchType#ERROR
       */
      public enum HolepunchErrorCode {
          
          /**
           * 成功
           */
          CODE_00((byte) 0x00),
          /**
           * 目标无效：NoSuchPeer
           */
          CODE_01((byte) 0x01),
          /**
           * 目标未连接：NotConnected
           */
          CODE_02((byte) 0x02),
          /**
           * 目标不支持：NoSupport
           */
          CODE_03((byte) 0x03),
          /**
           * 目标属于中继：NoSelf
           */
          CODE_04((byte) 0x04);
          
          /**
           * 错误编码
           */
          private final byte code;
          
          /**
           * @param code 错误编码
           */
          private HolepunchErrorCode(byte code) {
              this.code = code;
          }
          
          /**
           * @return 错误编码
           */
          public byte getCode() {
              return this.code;
          }
          
      }
      
      /**
       * 任务动作
       * 
       * @author acgist
       */
      public enum Action {
          
          /**
           * 磁力链接
           */
          MAGNET,
          /**
           * BT任务
           */
          TORRENT;
          
      }
      
      /**
       * 未知终端
       */
      public static final String UNKNOWN = "unknown";
      /**
       * Peer最大连接失败次数
       * 超过以后不会再次连接
       */
      public static final int MAX_FAIL_TIMES = 3;
      /**
       * PeerId长度
       */
      public static final int PEER_ID_LENGTH = 20;
      /**
       * 保留位
       * 
       * @see #RESERVED_DHT_PROTOCOL
       * @see #RESERVED_PEER_EXCHANGE
       * @see #RESERVED_FAST_PROTOCOL
       * @see #RESERVED_NAT_TRAVERSAL
       * @see #RESERVED_EXTENSION_PROTOCOL
       */
      public static final byte[] RESERVED = {0, 0, 0, 0, 0, 0, 0, 0};
      /**
       * 保留位长度
       * 
       * @see #RESERVED
       */
      public static final int RESERVED_LENGTH = RESERVED.length;
      /**
       * DHT协议保留位
       * [7]-0x01：DHT Protocol
       * 
       * @see DhtExtensionMessageHandler
       */
      public static final byte RESERVED_DHT_PROTOCOL = 1 << 0;
      /**
       * PEX协议保留位
       * [7]-0x02：Peer Exchange
       * 
       * @see PeerExchangeMessageHandler
       */
      public static final byte RESERVED_PEER_EXCHANGE = 1 << 1;
      /**
       * FAST协议保留位
       * [7]-0x04：FAST Protocol
       * 
       * @see PeerSubMessageHandler
       */
      public static final byte RESERVED_FAST_PROTOCOL = 1 << 2;
      /**
       * NAT保留位
       * [7]-0x08：NAT Traversal
       * 
       * @see #enableNat()
       */
      public static final byte RESERVED_NAT_TRAVERSAL = 1 << 3;
      /**
       * 扩展协议保留位
       * [5]-0x10：Extension Protocol
       * 
       * @see ExtensionMessageHandler
       */
      public static final byte RESERVED_EXTENSION_PROTOCOL = 1 << 4;
      /**
       * 握手消息长度
       */
      public static final int HANDSHAKE_LENGTH = 68;
      /**
       * 协议名称
       */
      public static final String PROTOCOL_NAME = "BitTorrent protocol";
      /**
       * 协议名称字节数组
       * 
       * @see #PROTOCOL_NAME
       */
      public static final byte[] PROTOCOL_NAME_BYTES = PROTOCOL_NAME.getBytes();
      /**
       * 协议名称字节数组长度
       * 
       * @see #PROTOCOL_NAME_BYTES
       */
      public static final int PROTOCOL_NAME_LENGTH = PROTOCOL_NAME_BYTES.length;
      /**
       * Peer状态：上传
       */
      public static final byte STATUS_UPLOAD = 1 << 1;
      /**
       * Peer状态：下载
       */
      public static final byte STATUS_DOWNLOAD = 1 << 0;
      /**
       * pex flags
       * 偏爱加密：0x01
       */
      public static final byte PEX_PREFER_ENCRYPTION = 1 << 0;
      /**
       * pex flags
       * 只上传不下载：0x02
       */
      public static final byte PEX_UPLOAD_ONLY = 1 << 1;
      /**
       * pex flags
       * 支持UTP协议：0x04
       */
      public static final byte PEX_UTP = 1 << 2;
      /**
       * pex flags
       * 支持holepunch协议：0x08
       */
      public static final byte PEX_HOLEPUNCH = 1 << 3;
      /**
       * pex flags
       * 可以连接：0x10
       */
      public static final byte PEX_OUTGO = 1 << 4;
      /**
       * holepunch连接超时时间（毫秒）
       */
      public static final long HOLEPUNCH_TIMEOUT = 2L * SystemConfig.ONE_SECOND_MILLIS;
      /**
       * PeerId名称配置
       */
      public static final String CLIENT_NAME_CONFIG = "/config/client.name.properties";
      /**
       * PeerId和客户端名称配置
       * PeerId名称=ClientName
       * 
       * 命名方式
       * Azureus-style：-名称（2）版本（4）-随机数（-SA1000-...）
       * Shadow's-style：名称（1）版本（4）-----随机数（S1000-----...）
       */
      private static final Map<String, String> CLIENT_NAME_MAPPING = new HashMap<>();
      /**
       * PeerId名称
       * AS=ACGIST Snail
       */
      private static final String PEER_ID_NAME = "AS";
      /**
       * 版本信息长度
       */
      private static final int PEER_ID_VERSION_LENGTH = 4;
      /**
       * Piece最小索引
       */
      private static final int PIECE_MIN = 0;
      /**
       * Piece最大索引（2^15）
       */
      private static final int PIECE_MAX = 32768;
      
      /**
       * PeerId
       */
      private final byte[] peerId;
      /**
       * PeerId（HTTP编码）
       */
      private final String peerIdUrl;
      
      static {
          // 保留位
          RESERVED[7] |= RESERVED_DHT_PROTOCOL;
          RESERVED[7] |= RESERVED_PEER_EXCHANGE;
          RESERVED[7] |= RESERVED_FAST_PROTOCOL;
          RESERVED[5] |= RESERVED_EXTENSION_PROTOCOL;
      }
      
      private static final PeerConfig INSTANCE = new PeerConfig();
      
      public static final PeerConfig getInstance() {
          return INSTANCE;
      }
      
      private PeerConfig() {
          super(CLIENT_NAME_CONFIG);
          this.peerId    = this.buildPeerId();
          this.peerIdUrl = PeerUtils.urlEncode(this.peerId);
          LOGGER.debug("PeerId：{}", this.peerId);
          LOGGER.debug("PeerIdUrl：{}", this.peerIdUrl);
          this.init();
          this.release();
      }
      
      @Override
      public void init() {
          this.properties.forEach((key, value) -> CLIENT_NAME_MAPPING.put(key.toString(), value.toString()));
      }
      
      /**
       * @return PeerId
       */
      private byte[] buildPeerId() {
          final byte[] peerId = new byte[PeerConfig.PEER_ID_LENGTH];
          // 名称版本：-ASXXXX-
          final StringBuilder builder = new StringBuilder(8);
          builder.append(SymbolConfig.Symbol.MINUS.toString()).append(PEER_ID_NAME);
          final String version    = SystemConfig.getVersion().replace(SymbolConfig.Symbol.DOT.toString(), "");
          final int versionLength = version.length();
          if(versionLength > PEER_ID_VERSION_LENGTH) {
              builder.append(version.substring(0, PEER_ID_VERSION_LENGTH));
          } else {
              builder.append(version);
              builder.append(SymbolConfig.Symbol.ZERO.toString().repeat(PEER_ID_VERSION_LENGTH - versionLength));
          }
          builder.append(SymbolConfig.Symbol.MINUS.toString());
          final byte[] nameVersion    = builder.toString().getBytes();
          final int nameVersionLength = nameVersion.length;
          System.arraycopy(nameVersion, 0, peerId, 0, nameVersionLength);
          // 随机填充
          final int paddingLength = PeerConfig.PEER_ID_LENGTH - nameVersionLength;
          final byte[] padding    = ArrayUtils.random(paddingLength);
          System.arraycopy(padding, 0, peerId, nameVersionLength, paddingLength);
          return peerId;
      }
      
      /**
       * @return PeerId
       */
      public byte[] getPeerId() {
          return this.peerId;
      }
      
      /**
       * @return PeerId（HTTP编码）
       */
      public String getPeerIdUrl() {
          return this.peerIdUrl;
      }
      
      /**
       * 设置支持NAT
       */
      public static final void enableNat() {
          RESERVED[7] |= RESERVED_NAT_TRAVERSAL;
      }
      
      /**
       * 判断Piece索引是否正确
       * 防止索引长度过大导致内存溢出
       * 
       * @param index Piece索引
       * 
       * @return 是否正确
       * 
       * @see #PIECE_MIN
       * @see #PIECE_MAX
       */
      public static final boolean checkPiece(int index) {
          return index >= PIECE_MIN && index <= PIECE_MAX;
      }
      
      /**
       * @param peerId PeerId
       * 
       * @return 客户端名称
       */
      public static final String clientName(byte[] peerId) {
          if(peerId == null || peerId.length < PeerConfig.PEER_ID_LENGTH) {
              return UNKNOWN;
          }
          String key;
          final char first = (char) peerId[0];
          if(first == SymbolConfig.Symbol.MINUS.toChar()) {
              key = new String(peerId, 1, 2);
          } else {
              key = new String(peerId, 0, 1);
          }
          return CLIENT_NAME_MAPPING.getOrDefault(key, UNKNOWN);
      }
      
  }
  