content: package com.acgist.snail.net.torrent;
  
  import java.io.File;
  import java.io.IOException;
  import java.nio.file.Files;
  import java.nio.file.Paths;
  import java.util.List;
  import java.util.Map;
  import java.util.concurrent.ConcurrentHashMap;
  import java.util.stream.Collectors;
  
  import com.acgist.snail.context.IContext;
  import com.acgist.snail.format.BEncodeDecoder;
  import com.acgist.snail.format.BEncodeEncoder;
  import com.acgist.snail.logger.Logger;
  import com.acgist.snail.logger.LoggerFactory;
  import com.acgist.snail.net.DownloadException;
  import com.acgist.snail.net.NetException;
  import com.acgist.snail.utils.StringUtils;
  
  /**
   * Torrent上下文
   * 
   * @author acgist
   */
  public final class TorrentContext implements IContext {
  
      private static final Logger LOGGER = LoggerFactory.getLogger(TorrentContext.class);
      
      private static final TorrentContext INSTANCE = new TorrentContext();
      
      public static final TorrentContext getInstance() {
          return INSTANCE;
      }
      
      /**
       * BT任务信息MAP
       * InfoHashHex=BT任务信息
       */
      private final Map<String, TorrentSession> torrentSessions;
      
      private TorrentContext() {
          this.torrentSessions = new ConcurrentHashMap<>();
      }
      
      /**
       * 获取所有InfoHash拷贝
       * 
       * @return 所有InfoHash拷贝
       */
      public List<InfoHash> allInfoHash() {
          return this.torrentSessions.values().stream()
              .map(TorrentSession::infoHash)
              .collect(Collectors.toList());
      }
  
      /**
       * 获取所有TorrentSession拷贝
       * 
       * @return 所有TorrentSession拷贝
       */
      public List<TorrentSession> allTorrentSession() {
          return this.torrentSessions.values().stream()
              .collect(Collectors.toList());
      }
      
      /**
       * 获取BT任务信息
       * 
       * @param infoHashHex InfoHashHex
       * 
       * @return BT任务信息
       */
      public TorrentSession torrentSession(String infoHashHex) {
          return this.torrentSessions.get(infoHashHex);
      }
      
      /**
       * 删除BT任务信息
       * 
       * @param infoHashHex InfoHashHex
       * 
       * @return BT任务信息
       */
      public TorrentSession remove(String infoHashHex) {
          LOGGER.debug("删除BT任务信息：{}", infoHashHex);
          return this.torrentSessions.remove(infoHashHex);
      }
      
      /**
       * 判断BT任务信息是否存在
       * 
       * @param infoHashHex InfoHashHex
       * 
       * @return 是否存在
       */
      public boolean exist(String infoHashHex) {
          return this.torrentSessions.containsKey(infoHashHex);
      }
  
      /**
       * 新建TorrentSession
       * 
       * @param path 种子文件路径
       * 
       * @return BT任务信息
       * 
       * @throws DownloadException 下载异常
       */
      public TorrentSession newTorrentSession(String path) throws DownloadException {
          final Torrent torrent = loadTorrent(path);
          return this.newTorrentSession(torrent.infoHash(), torrent);
      }
      
      /**
       * 新建TorrentSession
       * 种子文件路径可以为空：磁力链接只要InfoHashHex
       * 
       * @param infoHashHex InfoHashHex
       * @param path        种子文件路径
       * 
       * @return BT任务信息
       * 
       * @throws DownloadException 下载异常
       */
      public TorrentSession newTorrentSession(String infoHashHex, String path) throws DownloadException {
          final var session = this.torrentSession(infoHashHex);
          if(session != null) {
              return session;
          }
          if(StringUtils.isEmpty(path)) {
              return this.newTorrentSession(InfoHash.newInstance(infoHashHex), null);
          } else {
              return this.newTorrentSession(path);
          }
      }
      
      /**
       * 新建TorrentSession
       * 
       * @param infoHash InfoHash
       * @param torrent  种子信息
       * 
       * @return BT任务信息
       * 
       * @throws DownloadException 下载异常
       */
      private TorrentSession newTorrentSession(InfoHash infoHash, Torrent torrent) throws DownloadException {
          if(infoHash == null) {
              throw new DownloadException("新建TorrentSession失败（InfoHash为空）");
          }
          final String infoHashHex = infoHash.getInfoHashHex();
          var torrentSession = this.torrentSession(infoHashHex);
          if(torrentSession == null) {
              torrentSession = TorrentSession.newInstance(infoHash, torrent);
              this.torrentSessions.put(infoHashHex, torrentSession);
          }
          return torrentSession;
      }
      
      /**
       * 种子文件加载
       * 
       * @param path 种子文件地址
       * 
       * @return 种子信息
       * 
       * @throws DownloadException 下载异常
       */
      public static final Torrent loadTorrent(String path) throws DownloadException {
          final File file = new File(path);
          if(!file.exists()) {
              throw new DownloadException("不存在的种子文件");
          }
          try {
              final var bytes = Files.readAllBytes(Paths.get(file.getAbsolutePath()));
              final var decoder = BEncodeDecoder.newInstance(bytes).next();
              if(decoder.isEmpty()) {
                  throw new DownloadException("种子文件格式错误");
              }
              final var torrent = Torrent.valueOf(decoder);
              // 直接转储原始信息：防止顺序不对导致种子Hash计算错误
              final var info = decoder.getMap(Torrent.ATTR_INFO);
              final var infoHash = InfoHash.newInstance(BEncodeEncoder.encodeMap(info));
              torrent.infoHash(infoHash);
              return torrent;
          } catch (NetException | IOException e) {
              throw new DownloadException("种子文件加载失败", e);
          }
      }
  
  }
  