package logbook.internal.gui;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Path;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.StringJoiner;
import java.util.stream.Collectors;

import javafx.collections.ObservableList;
import javafx.css.PseudoClass;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextInputDialog;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import logbook.bean.AppCondition;
import logbook.bean.AppConfig;
import logbook.bean.DeckPort;
import logbook.bean.NdockCollection;
import logbook.bean.Ship;
import logbook.bean.ShipCollection;
import logbook.bean.ShipMst;
import logbook.bean.ShipgraphCollection;
import logbook.internal.Items;
import logbook.internal.LoggerHolder;
import logbook.internal.Ships;
import logbook.plugin.gui.FleetTabRemark;
import logbook.plugin.gui.Plugin;
import logbook.plugin.gui.Updateable;
import lombok.val;

/**
 * 艦隊タブ
 *
 */
public class FleetTabPane extends ScrollPane {

    /** 艦隊 */
    private DeckPort port;

    /** 艦娘達 */
    private List<Ship> shipList;

    /** 艦隊のハッシュ・コード */
    private int portHashCode;

    /** 艦娘達のハッシュ・コード */
    private int shipsHashCode;

    /** 入渠中の艦娘達のハッシュ・コード */
    private int ndocksHashCode;

    /** Tabのクラス名(タブ色を変えるのに使用) */
    private String tabCssClass;

    /** 分岐点係数 */
    private double branchCoefficient = 1;

    /** メッセージ */
    @FXML
    private Label message;

    /** 艦娘達 */
    @FXML
    private VBox ships;

    /** 制空値アイコン */
    @FXML
    private ImageView airSuperiorityImg;

    /** 制空値 */
    @FXML
    private Label airSuperiority;

    /** 触接開始率アイコン */
    @FXML
    private ImageView touchPlaneStartProbabilityImg;

    /** 触接開始率 */
    @FXML
    private Label touchPlaneStartProbability;

    /** 判定式(33)アイコン */
    @FXML
    private ImageView decision33Img;

    /** 判定式(33) */
    @FXML
    private Label decision33;

    /** 分岐点係数ボタン */
    @FXML
    private Button branchCoefficientButton;

    /** 艦娘レベル計アイコン */
    @FXML
    private ImageView lvsumImg;

    /** 艦娘レベル計 */
    @FXML
    private Label lvsum;

    /** 疲労 */
    @FXML
    private Label cond;

    /** 火力合計アイコン */
    @FXML
    private ImageView karyokusumImg;

    /** 火力合計 */
    @FXML
    private Label karyokusum;

    /** 対空合計アイコン */
    @FXML
    private ImageView taikusumImg;

    /** 対空合計 */
    @FXML
    private Label taikusum;

    /** 対潜合計アイコン */
    @FXML
    private ImageView taissumImg;

    /** 対潜合計 */
    @FXML
    private Label taissum;

    /** 索敵合計アイコン */
    @FXML
    private ImageView sakutekisumImg;

    /** 索敵合計 */
    @FXML
    private Label sakutekisum;

    /** 注釈 */
    @FXML
    private VBox remark;

    /**
     * 艦隊ペインのコンストラクタ
     *
     * @param port 艦隊
     */
    public FleetTabPane(DeckPort port) {
        this.port = port;
        try {
            FXMLLoader loader = InternalFXMLLoader.load("logbook/gui/fleet_tab.fxml");
            loader.setRoot(this);
            loader.setController(this);
            loader.load();
        } catch (IOException e) {
            LoggerHolder.get().error("FXMLのロードに失敗しました", e);
        }
    }

    @FXML
    void initialize() {
        this.update();
        this.setIcon();
        this.initializeRemarkPlugin();
        this.updateRemarkPlugin(this.port);
    }

    /**
     * 分岐点係数を変更する
     *
     * @param event ActionEvent
     */
    @FXML
    void changeBranchCoefficient(ActionEvent event) {
        TextInputDialog dialog = new TextInputDialog(Double.toString(this.branchCoefficient));
        dialog.getDialogPane().getStylesheets().add("logbook/gui/application.css");
        InternalFXMLLoader.setGlobal(dialog.getDialogPane());
        dialog.initOwner(this.getScene().getWindow());
        dialog.setTitle("分岐点係数を変更");
        dialog.setHeaderText("分岐点係数を数値で入力してください 例)\n"
                + "沖ノ島沖 G,I,Jマス 係数: 1.0\n"
                + "北方AL海域 Gマス 係数: 4.0\n"
                + "中部海域哨戒線 G,Hマス 係数: 4.0\n"
                + "MS諸島沖 E,Iマス 係数: 3.0\n"
                + "グアノ環礁沖海域 Hマス 係数: 3.0");

        val result = dialog.showAndWait();
        if (result.isPresent()) {
            String value = result.get();
            if (!value.isEmpty()) {
                try {
                    this.branchCoefficient = Double.parseDouble(value);
                    this.setDecision33();
                } catch (NumberFormatException e) {
                }
            }
        }
    }

    /**
     * 画面を更新します
     *
     * @param port 艦隊
     */
    public void update(DeckPort port) {
        this.port = port;
        this.update();
    }

    /**
     * 画面を更新します
     */
    public void update() {
        Map<Integer, Ship> shipMap = ShipCollection.get()
                .getShipMap();
        this.shipList = this.port.getShip()
                .stream()
                .map(shipMap::get)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

        int ndocksHashCode = NdockCollection.get().getNdockSet().hashCode();

        if (this.portHashCode != this.port.hashCode() || this.shipsHashCode != this.shipList.hashCode()
                || this.ndocksHashCode != ndocksHashCode) {
            this.updateShips();
        }
        this.portHashCode = this.port.hashCode();
        this.shipsHashCode = this.shipList.hashCode();
        this.ndocksHashCode = ndocksHashCode;
    }

    /**
     * タブに設定するCSSクラス名
     *
     * @return CSSクラス名
     */
    public String tabCssClass() {
        return this.tabCssClass;
    }

    private void updateShips() {
        if (AppConfig.get().isVisiblePoseImageOnFleetTab() && !this.shipList.isEmpty()) {
            Ship ship = this.shipList.get(0);

            Path path = Ships.shipStandingPoseImagePath(ship);
            if (path != null) {
                StringBuilder sb = new StringBuilder();
                sb.append("-fx-background-image: url('" + path.toUri() + "');");
                sb.append("-fx-background-position: ");
                sb.append(Ships.shipMst(ship)
                        .map(ShipMst::getId)
                        .flatMap(id -> Optional.ofNullable(ShipgraphCollection.get().getShipgraphMap().get(id)))
                        .map(g -> ((g.getWeda().get(0) * -1) + 100) + "px " + (g.getWeda().get(1) * -1) + "px")
                        .orElse("center top"));
                sb.append(";");
                this.setStyle(sb.toString());
                this.pseudoClassStateChanged(PseudoClass.getPseudoClass("enablebgimage"), true);
            } else {
                this.setStyle("-fx-background-image: null");
                this.pseudoClassStateChanged(PseudoClass.getPseudoClass("enablebgimage"), false);
            }
        } else {
            this.setStyle("-fx-background-image: null");
            this.pseudoClassStateChanged(PseudoClass.getPseudoClass("enablebgimage"), false);
        }

        this.message.setText(this.port.getName());

        List<Ship> withoutEscape = this.shipList.stream()
                .filter(s -> !Ships.isEscape(s))
                .collect(Collectors.toList());

        // 制空値
        this.airSuperiority
                .setText(Integer.toString(withoutEscape.stream()
                        .mapToInt(Ships::airSuperiority)
                        .sum()));
        // 触接開始率
        this.touchPlaneStartProbability
                .setText((int) Math.floor(Ships.touchPlaneStartProbability(withoutEscape) * 100) + "%");
        // 判定式(33)
        this.setDecision33();
        // 艦娘レベル計
        this.lvsum.setText(Integer.toString(withoutEscape.stream().mapToInt(Ship::getLv).sum()));
        // 火力合計
        this.karyokusum.setText(Integer.toString(withoutEscape.stream().mapToInt(ship -> ship.getKaryoku().get(0)).sum()));
        // 対空合計
        this.taikusum.setText(Integer.toString(withoutEscape.stream().mapToInt(ship -> ship.getTaiku().get(0)).sum()));
        // 対潜合計
        this.taissum.setText(Integer.toString(withoutEscape.stream().mapToInt(ship -> ship.getTaisen().get(0)).sum()));
        // 索敵合計
        this.sakutekisum.setText(Integer.toString(withoutEscape.stream().mapToInt(ship -> ship.getSakuteki().get(0)).sum()));

        ObservableList<Node> childs = this.ships.getChildren();
        childs.clear();
        this.shipList.stream()
                .map(FleetTabShipPane::new)
                .forEach(childs::add);

        if (this.shipList.stream().anyMatch(Ships::isBadlyDamage)) {
            // 大破時
            this.tabCssClass = "alert";
        } else if (this.shipList.stream().anyMatch(Ships::isHalfDamage)) {
            // 中破時
            this.tabCssClass = "warn";
        } else if (this.shipList.stream()
                .anyMatch(ship -> !ship.getFuel().equals(Ships.shipMst(ship).map(ShipMst::getFuelMax).orElse(0)) ||
                        !ship.getBull().equals(Ships.shipMst(ship).map(ShipMst::getBullMax).orElse(0)))) {
            // 未補給時
            this.tabCssClass = "shortage";
        } else if (this.port.getId() > 1 && this.port.getMission().get(0) == 0L) {
            // 遠征未出撃
            this.tabCssClass = "empty";
        } else {
            this.tabCssClass = null;
        }
        // 疲労
        int minCond = this.shipList.stream()
                .mapToInt(Ship::getCond)
                .min()
                .orElse(49);
        if (minCond < 49) {

            long cut = AppCondition.get().getCondUpdateTime();
            // 疲労抜け想定時刻(エポック秒)
            long end = cut + (-Math.floorDiv(49 - minCond, -3) * 180);

            // 現在時刻
            ZonedDateTime now = ZonedDateTime.now(ZoneId.systemDefault());
            // 現在時刻(エポック秒)
            long nowepoch = now.toEpochSecond();

            if (end > nowepoch) {
                ZonedDateTime disp = ZonedDateTime.ofInstant(Instant.ofEpochSecond(end), ZoneId.systemDefault());
                DateTimeFormatter format = DateTimeFormatter.ofPattern("HH:mm");

                this.cond.setText(format.format(disp));
            } else {
                this.cond.setText("");
            }
        } else {
            this.cond.setText("");
        }
        this.updateRemarkPlugin(this.port);
    }

    /**
     * 判定式(33) を設定する
     */
    private void setDecision33() {
        Ships.Decision33 decision33 = Ships.decision33(this.shipList, this.branchCoefficient);
        // 判定式(33)
        this.decision33.setText(BigDecimal.valueOf(decision33.get())
                .setScale(3, RoundingMode.FLOOR)
                .toPlainString());
        PopOver<Ships.Decision33> popover = new PopOver<>((node, data) -> {
            String content = new StringJoiner("\n")
                    .add("判定式(33):" + data.get() + "(分岐点係数:" + data.getBranchCoefficient() + ")")
                    .add("装備索敵:" + data.getItemView())
                    .add("艦娘索敵:" + data.getShipView())
                    .add("司令部スコア:" + data.getLevelScore())
                    .add("艦隊スコア:" + data.getFleetScore())
                    .toString();
            return new PopOverPane("判定式(33)", content);
        });
        popover.install(this.decision33, decision33);
        this.branchCoefficientButton.setText("分岐点係数:" + this.branchCoefficient);
    }

    private void setIcon() {
        this.airSuperiorityImg.setImage(Items.itemImageByType(6));
        this.touchPlaneStartProbabilityImg.setImage(Items.itemImageByType(10));
        this.decision33Img.setImage(Items.itemImageByType(9));
        this.lvsumImg.setImage(Items.itemImageByType(28));
        this.karyokusumImg.setImage(Items.itemImageByType(3));
        this.taikusumImg.setImage(Items.itemImageByType(15));
        this.taissumImg.setImage(Items.itemImageByType(17));
        this.sakutekisumImg.setImage(Items.itemImageByType(11));
    }

    /**
     * 注釈プラグインの初期化
     */
    private void initializeRemarkPlugin() {
        for (Updateable<DeckPort> plugin : Plugin.getContent(FleetTabRemark.class)) {
            Node node;
            if (plugin instanceof Node) {
                node = ((Node) plugin);
            } else {
                node = new RemarkLabel(plugin);
            }
            this.remark.getChildren().add(node);
        }
    }

    /**
     * 注釈プラグインを更新する
     * 
     * @param port 艦隊
     */
    @SuppressWarnings("unchecked")
    private void updateRemarkPlugin(DeckPort port) {
        for (Node node : this.remark.getChildren()) {
            if (node instanceof Updateable) {
                ((Updateable<DeckPort>) node).updateItem(port);
            }
        }
    }

    /**
     * Labelを使う注釈の実装
     */
    private static class RemarkLabel extends Label implements Updateable<DeckPort> {

        private Updateable<DeckPort> plugin;

        public RemarkLabel(Updateable<DeckPort> plugin) {
            this.plugin = plugin;
            this.setText(this.plugin.toString());
        }

        @Override
        public void updateItem(DeckPort item) {
            this.plugin.updateItem(item);
            this.setText(this.plugin.toString());
        }
    }
}