package logbook.api;

import java.time.Duration;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import javax.json.JsonArray;
import javax.json.JsonObject;

import javafx.application.Platform;
import logbook.bean.AppBouyomiConfig;
import logbook.bean.AppCondition;
import logbook.bean.AppConfig;
import logbook.bean.Basic;
import logbook.bean.DeckPort;
import logbook.bean.DeckPortCollection;
import logbook.bean.Material;
import logbook.bean.Ndock;
import logbook.bean.NdockCollection;
import logbook.bean.Ship;
import logbook.bean.ShipCollection;
import logbook.bean.SlotItem;
import logbook.bean.SlotItemCollection;
import logbook.internal.Audios;
import logbook.internal.BouyomiChanUtils;
import logbook.internal.BouyomiChanUtils.Type;
import logbook.internal.JsonHelper;
import logbook.internal.gui.Tools;
import logbook.internal.log.LogWriter;
import logbook.internal.log.MaterialLogFormat;
import logbook.proxy.RequestMetaData;
import logbook.proxy.ResponseMetaData;

/**
 * /kcsapi/api_port/port
 *
 */
@API("/kcsapi/api_port/port")
public class ApiPortPort implements APIListenerSpi {

    @Override
    public void accept(JsonObject json, RequestMetaData req, ResponseMetaData res) {
        JsonObject data = json.getJsonObject("api_data");
        if (data != null) {
            this.apiBasic(data.getJsonObject("api_basic"));
            this.apiShip(data.getJsonArray("api_ship"));
            this.apiDeckPort(data.getJsonArray("api_deck_port"));
            this.apiNdock(data.getJsonArray("api_ndock"));
            this.apiMaterial(data.getJsonArray("api_material"));
            this.apiCombinedFlag(data);
            this.condition();
            this.akashiTimer();
            this.detectGimmick(data);
        }
    }

    /**
     * api_data.api_basic
     *
     * @param object api_basic
     */
    private void apiBasic(JsonObject object) {
        Basic.updateBasic(Basic.get(), object);
    }

    /**
     * api_data.api_ship
     *
     * @param array api_ship
     */
    private void apiShip(JsonArray array) {
        // 差し替え前
        Map<Integer, Ship> before = ShipCollection.get()
                .getShipMap();
        // 差し替え前のID
        Set<Integer> beforeShipIds = before.keySet();
        // 差し替え後
        Map<Integer, Ship> afterShipMap = JsonHelper.toMap(array, Ship::getId, Ship::toShip);
        // 差し替え後のID
        Set<Integer> afterShipIds = afterShipMap.keySet();

        // cond値が更新されたかを検出
        Predicate<Ship> update = beforeShip -> {
            Ship afterShip = afterShipMap.get(beforeShip.getId());
            if (afterShip != null) {
                return !beforeShip.getCond().equals(afterShip.getCond());
            }
            return false;
        };
        if (before.values().stream()
                .filter(ship -> ship.getCond() <= 49)
                .anyMatch(update)) {
            ZonedDateTime time = ZonedDateTime.now(ZoneId.systemDefault());
            AppCondition.get().setCondUpdateTime(time.toEpochSecond());
        }

        // 差し替え前に存在して、差し替え後に存在しない艦娘の装備を廃棄する
        beforeShipIds.stream()
                .filter(((Predicate<Integer>) afterShipIds::contains).negate())
                .map(before::get)
                .forEach(this::destryItem);

        ShipCollection.get()
                .setShipMap(afterShipMap);
    }

    /**
     * api_data.api_deck_port
     *
     * @param array api_deck_port
     */
    private void apiDeckPort(JsonArray array) {
        Map<Integer, DeckPort> deckMap = JsonHelper.toMap(array, DeckPort::getId, DeckPort::toDeckPort);
        DeckPortCollection.get()
                .setDeckPortMap(deckMap);
        DeckPortCollection.get()
                .setMissionShips(deckMap.values()
                        .stream()
                        .filter(d -> d.getMission().get(0) != 0)
                        .map(DeckPort::getShip)
                        .flatMap(List::stream)
                        .collect(Collectors.toCollection(LinkedHashSet::new)));
    }

    /**
     * api_data.api_ndock
     *
     * @param array api_ndock
     */
    private void apiNdock(JsonArray array) {
        // 入渠
        Map<Integer, Ndock> map = JsonHelper.toMap(array, Ndock::getId, Ndock::toNdock);
        NdockCollection.get()
                .setNdockMap(map);
        // 入渠中の艦娘
        NdockCollection.get()
                .setNdockSet(map.entrySet()
                        .stream()
                        .map(Map.Entry::getValue)
                        .map(Ndock::getShipId)
                        .collect(Collectors.toCollection(LinkedHashSet::new)));
    }

    /**
     * api_data.api_material
     *
     * @param array api_material
     */
    private void apiMaterial(JsonArray array) {
        Duration duration = Duration.ofMillis(System.currentTimeMillis() - AppCondition.get()
                .getWroteMaterialLogLast());
        if (duration.compareTo(Duration.ofSeconds(AppConfig.get().getMaterialLogInterval())) >= 0) {
            Map<Integer, Material> material = JsonHelper.toMap(array, Material::getId, Material::toMaterial);
            LogWriter.getInstance(MaterialLogFormat::new)
                    .write(material);
            AppCondition.get()
                    .setWroteMaterialLogLast(System.currentTimeMillis());
        }
    }

    /**
     * api_data.api_combined_flag
     *
     * @param object api_data
     */
    private void apiCombinedFlag(JsonObject object) {
        Integer combinedType;
        Boolean combinedFlag;
        if (object.containsKey("api_combined_flag")) {
            combinedType = JsonHelper.toInteger(object.getJsonNumber("api_combined_flag"));
        } else {
            combinedType = 0;
        }
        combinedFlag = combinedType > 0;
        AppCondition.get().setCombinedType(combinedType);
        AppCondition.get().setCombinedFlag(combinedFlag);
    }

    /**
     * 艦娘の装備を廃棄する
     *
     * @param ship 艦娘
     */
    private void destryItem(Ship ship) {
        Map<Integer, SlotItem> itemMap = SlotItemCollection.get()
                .getSlotitemMap();
        // 持っている装備を廃棄する
        for (Integer itemId : ship.getSlot()) {
            itemMap.remove(itemId);
        }
        // 補強増設
        itemMap.remove(ship.getSlotEx());
    }

    /**
     * 出撃などの状態を更新する
     */
    private void condition() {
        AppCondition condition = AppCondition.get();
        // 出撃中ではない
        condition.setMapStart(false);
        // 退避を削除
        condition.getEscape().clear();
        // 戦闘結果を削除
        condition.setBattleResultConfirm(null);
        // 戦闘回数リセット
        condition.setBattleCount(0);
        // ルート削除
        condition.setRoute(new ArrayList<>());
    }

    /**
     * 泊地修理タイマーを更新する
     */
    private void akashiTimer() {
        long timer = AppCondition.get().getAkashiTimer();
        if (System.currentTimeMillis() - timer >= Duration.ofMinutes(20).toMillis()) {
            AppCondition.get().setAkashiTimer(System.currentTimeMillis());
        }
    }

    /**
     * api_data.api_event_object
     * 
     * @param object api_data
     */
    private void detectGimmick(JsonObject object) {
        if (object.containsKey("api_event_object")) {
            JsonObject eventObject = object.getJsonObject("api_event_object");
            if (eventObject.containsKey("api_m_flag2")) {
                if (JsonHelper.toInteger(eventObject.get("api_m_flag2")) > 0) {
                    Platform.runLater(
                            () -> Tools.Conrtols.showNotify(null, "ギミック解除", "ギミックの達成を確認しました。",
                                    javafx.util.Duration.seconds(15)));
                    // 通知音再生
                    if (AppConfig.get().isUseSound()) {
                        Platform.runLater(Audios.playDefaultNotifySound());
                    }
                    // 棒読みちゃん連携
                    if (AppBouyomiConfig.get().isEnable()) {
                        BouyomiChanUtils.speak(Type.AchievementGimmick2);
                    }
                }
            }
        }
    }
}