content: package org.allaymc.server.scroreboard;
  
  import lombok.extern.slf4j.Slf4j;
  import org.allaymc.api.scoreboard.Scoreboard;
  import org.allaymc.api.scoreboard.ScoreboardLine;
  import org.allaymc.api.scoreboard.ScoreboardStorage;
  import org.allaymc.api.scoreboard.data.DisplaySlot;
  import org.allaymc.api.scoreboard.data.SortOrder;
  import org.allaymc.api.scoreboard.scorer.EntityScorer;
  import org.allaymc.api.scoreboard.scorer.FakeScorer;
  import org.allaymc.api.scoreboard.scorer.PlayerScorer;
  import org.allaymc.api.utils.config.Config;
  import org.cloudburstmc.protocol.bedrock.data.ScoreInfo;
  
  import java.io.IOException;
  import java.nio.file.Files;
  import java.nio.file.Path;
  import java.util.*;
  
  /**
   * @author daoge_cmd
   */
  @Slf4j
  public class JsonScoreboardStorage implements ScoreboardStorage {
  
      protected Path filePath;
      protected Config json;
  
      public JsonScoreboardStorage(Path path) {
          this.filePath = path;
          try {
              if (!Files.exists(this.filePath.getParent())) {
                  Files.createDirectories(this.filePath.getParent());
              }
              if (!Files.exists(this.filePath)) {
                  Files.createFile(this.filePath);
              }
              json = new Config(this.filePath.toFile(), Config.JSON);
          } catch (IOException e) {
              log.error("Failed to create file", e);
          }
      }
  
      @Override
      public void save(Scoreboard scoreboard) {
          json.set("scoreboard." + scoreboard.getObjectiveName(), serializeToMap(scoreboard));
          json.save();
      }
  
      @Override
      public void save(Collection<Scoreboard> scoreboards) {
          scoreboards.forEach(this::save);
      }
  
      @Override
      public void saveDisplay(Map<DisplaySlot, Scoreboard> display) {
          for (var entry : display.entrySet()) {
              json.set("display." + entry.getKey().name(), entry.getValue() != null ? entry.getValue().getObjectiveName() : null);
          }
          json.save();
      }
  
      @Override
      public Map<String, Scoreboard> readAll() {
          Map<String, Object> scoreboards = (Map<String, Object>) json.get("scoreboard");
          Map<String, Scoreboard> result = new HashMap<>();
          if (scoreboards == null) return result;
          for (Map.Entry<String, Object> entry : scoreboards.entrySet())
              result.put(entry.getKey(), deserializeFromMap((Map<String, Object>) entry.getValue()));
          return result;
      }
  
      @Override
      public Scoreboard read(String name) {
          return json.get("scoreboard." + name) == null ? null : deserializeFromMap((Map<String, Object>) json.get("scoreboard." + name));
      }
  
      @Override
      public Map<DisplaySlot, String> readDisplay() {
          Map<DisplaySlot, String> result = new HashMap<>();
          if (json.get("display") == null) return result;
          for (Map.Entry<String, String> e : ((Map<String, String>) json.get("display")).entrySet()) {
              DisplaySlot slot = DisplaySlot.valueOf(e.getKey());
              result.put(slot, e.getValue());
          }
          return result;
      }
  
      @Override
      public void remove(String name) {
          json.remove("scoreboard." + name);
          json.save();
      }
  
      @Override
      public void removeAll() {
          json.remove("scoreboard");
          json.save();
      }
  
      @Override
      public boolean contain(String name) {
          return json.exists("scoreboard." + name);
      }
  
      private Map<String, Object> serializeToMap(Scoreboard scoreboard) {
          Map<String, Object> map = new HashMap<>();
          map.put("objectiveName", scoreboard.getObjectiveName());
          map.put("displayName", scoreboard.getDisplayName());
          map.put("criteriaName", scoreboard.getCriteriaName());
          map.put("sortOrder", scoreboard.getSortOrder().name());
  
          List<Map<String, Object>> lines = new ArrayList<>();
          for (var line : scoreboard.getLines().values()) {
              Map<String, Object> data = new HashMap<>();
              data.put("score", line.getScore());
              data.put("scorerType", line.getScorer().getScorerType().name());
              data.put("name", switch (line.getScorer()) {
                  case PlayerScorer player -> player.getUuid().toString();
                  case EntityScorer entity -> "" + entity.getUniqueId();
                  case FakeScorer fake -> fake.getFakeName();
              });
              lines.add(data);
          }
  
          map.put("lines", lines);
          return map;
      }
  
      private Scoreboard deserializeFromMap(Map<String, Object> map) {
          var objectiveName = map.get("objectiveName").toString();
          var displayName = map.get("displayName").toString();
          var criteriaName = map.get("criteriaName").toString();
          var sortOrder = SortOrder.valueOf(map.get("sortOrder").toString());
  
          var scoreboard = new Scoreboard(objectiveName, displayName, criteriaName, sortOrder);
          for (var line : (List<Map<String, Object>>) map.get("lines")) {
              var score = ((Double) line.get("score")).intValue();
              var name = (String) line.get("name");
              var scorer = switch (ScoreInfo.ScorerType.valueOf(line.get("scorerType").toString())) {
                  case PLAYER -> new PlayerScorer(UUID.fromString(name));
                  case ENTITY -> new EntityScorer(Long.parseLong(name));
                  case FAKE -> new FakeScorer(name);
                  default -> throw new IllegalStateException("Unexpected value: " + line.get("scorerType"));
              };
              scoreboard.addLine(new ScoreboardLine(scoreboard, scorer, score));
          }
  
          return scoreboard;
      }
  }
  