/**
 * 
 */
package logbook.internal;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.json.JsonArray;
import javax.json.JsonNumber;
import javax.json.JsonObject;
import javax.json.JsonValue;

import logbook.constants.AppConstants;
import logbook.dto.ItemInfoDto;
import logbook.dto.JsonData;
import logbook.dto.ShipInfoDto;
import logbook.dto.UseItemDto;
import logbook.gui.ApplicationMain;
import logbook.util.BeanUtils;

import org.apache.commons.lang3.ArrayUtils;

/**
 * @author Nekopanda
 *
 */
public class MasterData {

    /** ロガー */
    private static final LoggerHolder LOG = new LoggerHolder(MasterData.class);

    private static class Holder {
        public static MasterData instance = null;
    }

    /** 変更があったか */
    private static boolean modified = false;

    /**
     * 
     * 設定ファイルに書き込みます
     */
    public static void store() throws IOException {
        if (Holder.instance == null) {
            return;
        }
        // 最終更新日時がファイル更新日時より新しい時だけ書き込む
        if (modified) {
            ApplicationMain.sysPrint("マスターファイル更新");
            BeanUtils.writeObject(AppConstants.MASTER_DATA_CONFIG, Holder.instance);
            modified = false;
        }
    }

    private static void load() {
        try {
            MasterData masterData = BeanUtils.readObject(AppConstants.MASTER_DATA_CONFIG, MasterData.class);
            if ((masterData != null) && (masterData.getVersion() >= 2)) {
                Holder.instance = masterData;
                Holder.instance.start2.loadCompleted();
            }
        } catch (Exception e) {
            LOG.get().warn("艦娘のIDと名前の紐付けを設定ファイルから読み込みますに失敗しました", e);
        }
        if (Holder.instance == null) {
            Holder.instance = new MasterData();
        }
    }

    public static final boolean INIT_COMPLETE;
    static {
        load();
        INIT_COMPLETE = true;
    }

    /** バージョン
     * 0: 初期
     * 1: 1.6.3以降
     * 2: 1.8.7以降
     */
    private int version = 2;

    private Start2Dto start2 = new Start2Dto();

    /** マップクリア情報 -1: 非表示 or 未更新 0: クリアしていない 1: クリア済み */
    private Map<Integer, Integer> mapState = new TreeMap<Integer, Integer>();

    /** 遠征クリア情報 -1: 非表示 or 未更新 0: NEW 1: 無印 2:　達成 */
    private Map<Integer, Integer> missionState = new TreeMap<Integer, Integer>();

    /** 最終更新日時 */
    private Date lastUpdateTime = new Date(0);

    public MasterData() {
    }

    /** START2のマスターデータで更新 */
    public static void updateMaster(JsonObject data) {
        Holder.instance.doMater(data);
    }

    /** 出撃マップを更新 */
    public static void updateMapInfo(JsonArray json_mapinfo) {
        Holder.instance.doMapInfo(json_mapinfo);
    }

    /** 遠征データを更新 */
    public static void updateMission(JsonArray json_mission) {
        Holder.instance.doMission(json_mission);
    }

    public static MasterData get() {
        return Holder.instance;
    }

    public static Start2Dto getMaster() {
        return Holder.instance.getStart2();
    }

    private void doMater(JsonObject data) {
        this.start2 = new Start2Dto(data);
        this.lastUpdateTime = new Date();
        modified = true;
    }

    /** 艦種情報を取得 */
    public static ShipTypeDto getShipType(int stype) {
        return Holder.instance.start2.getShipType(stype);
    }

    /** （装備でない）アイテム情報を取得 */
    public static UseItemInfoDto getUseItem(int id) {
        return Holder.instance.start2.getUseItem(id);
    }

    /** マップ情報を取得 */
    public static MapInfoDto getMapInfo(int area, int no) {
        return Holder.instance.start2.getMapInfo(area, no);
    }

    private void doMapInfo(JsonArray json_mapinfo) {
        if (json_mapinfo != null) {
            Map<Integer, Integer> newState = new HashMap<Integer, Integer>();
            for (JsonValue elem : json_mapinfo) {
                JsonObject obj = (JsonObject) elem;
                newState.put(obj.getInt("api_id"), obj.getInt("api_cleared"));
            }
            if (newState.equals(this.mapState) == false) {
                this.mapState = newState;
                this.lastUpdateTime = new Date();
                modified = true;
            }
        }
    }

    private void doMission(JsonArray json_mission) {
        if (json_mission != null) {
            Map<Integer, Integer> newState = new HashMap<Integer, Integer>();
            for (JsonValue elem : json_mission) {
                JsonObject obj = (JsonObject) elem;
                newState.put(obj.getInt("api_mission_id"), obj.getInt("api_state"));
            }
            if (newState.equals(this.missionState) == false) {
                this.missionState = newState;
                this.lastUpdateTime = new Date();
                modified = true;
            }
        }
    }

    /**
     * @return mapState
     */
    public Map<Integer, Integer> getMapState() {
        return this.mapState;
    }

    /**
     * @param mapState セットする mapState
     */
    public void setMapState(Map<Integer, Integer> mapState) {
        this.mapState = mapState;
    }

    /**
     * @return missionState
     */
    public Map<Integer, Integer> getMissionState() {
        return this.missionState;
    }

    /**
     * @param missionState セットする missionState
     */
    public void setMissionState(Map<Integer, Integer> missionState) {
        this.missionState = missionState;
    }

    /**
     * @return masterUpdateTime
     */
    public Date getMasterUpdateTime() {
        return this.start2.getTime();
    }

    /**
     * @return lastUpdateTime
     */
    public Date getLastUpdateTime() {
        return this.lastUpdateTime;
    }

    /**
     * @param lastUpdateTime セットする lastUpdateTime
     */
    public void setLastUpdateTime(Date lastUpdateTime) {
        this.lastUpdateTime = lastUpdateTime;
    }

    /**
     * @return version
     */
    public int getVersion() {
        return this.version;
    }

    /**
     * @param version セットする version
     */
    public void setVersion(int version) {
        this.version = version;
    }

    /**
     * @return start2
     */
    public Start2Dto getStart2() {
        return this.start2;
    }

    /**
     * @param start2 セットする start2
     */
    public void setStart2(Start2Dto start2) {
        this.start2 = start2;
    }

    /**
     * api_start2
     * @author Nekopanda
     */
    public class Start2Dto extends JsonData {

        /** 最終更新日時 */
        private Date time = new Date(0);

        /** 艦娘 */
        private final Map<Integer, ShipInfoDto> ships = new TreeMap<>();

        /** アイテム */
        private final Map<Integer, ItemInfoDto> items = new TreeMap<>();

        /** 1-, 2-, ... , イベント海域 */
        private final ArrayList<MapAreaDto> maparea = new ArrayList<MapAreaDto>();

        /** 1-1, 1-2, ... , 2-1, 2-2, ... */
        private final Map<Integer, MapInfoDto> mapinfo = new TreeMap<Integer, MapInfoDto>();

        /** 全ての遠征  */
        private final Map<Integer, MissionDto> mission = new TreeMap<Integer, MissionDto>();

        /** 艦種 */
        private final List<ShipTypeDto> stype = new ArrayList<ShipTypeDto>();

        /** UseItem　（バケツとか、家具箱とか） */
        private final Map<Integer, UseItemInfoDto> useItem = new TreeMap<Integer, UseItemInfoDto>();

        public Start2Dto() {
        }

        public Start2Dto(JsonObject json) {
            super(json);
            this.readJson();
        }

        public void loadCompleted() {
            this.readJson();
        }

        private void readJson() {

            // 艦種
            JsonArray json_stype = this.json.getJsonArray("api_mst_stype");
            if (json_stype != null) {
                this.stype.clear();
                for (JsonValue elem : json_stype) {
                    this.stype.add(new ShipTypeDto((JsonObject) elem));
                }
            }

            // 装備一覧
            JsonArray apiMstSlotitem = this.json.getJsonArray("api_mst_slotitem");
            if (apiMstSlotitem != null) {
                this.items.clear();
                for (int i = 0; i < apiMstSlotitem.size(); i++) {
                    JsonObject object = (JsonObject) apiMstSlotitem.get(i);
                    ItemInfoDto item = new ItemInfoDto(object);
                    int id = object.getJsonNumber("api_id").intValue();
                    this.items.put(id, item);
                }
            }

            // 艦娘一覧
            JsonArray apiMstShip = this.json.getJsonArray("api_mst_ship");
            if (apiMstShip != null) {
                this.ships.clear();
                for (int i = 0; i < apiMstShip.size(); i++) {
                    JsonObject object = (JsonObject) apiMstShip.get(i);
                    int id = object.getInt("api_id");
                    this.ships.put(id, this.toShipInfoDto(object));
                }
            }
            this.setCharId();

            JsonArray json_maparea = this.json.getJsonArray("api_mst_maparea");
            if (json_maparea != null) {
                this.maparea.clear();
                for (JsonValue elem : json_maparea) {
                    this.maparea.add(new MapAreaDto((JsonObject) elem));
                }
            }

            JsonArray json_mapinfo = this.json.getJsonArray("api_mst_mapinfo");
            if (json_mapinfo != null) {
                this.mapinfo.clear();
                for (JsonValue elem : json_mapinfo) {
                    MapInfoDto dto = new MapInfoDto((JsonObject) elem);
                    this.mapinfo.put(dto.getId(), dto);
                }
            }

            JsonArray json_mission = this.json.getJsonArray("api_mst_mission");
            if (json_mission != null) {
                this.mission.clear();
                for (JsonValue elem : json_mission) {
                    MissionDto dto = new MissionDto((JsonObject) elem);
                    this.mission.put(dto.getId(), dto);
                }
            }

            JsonArray json_useitem = this.json.getJsonArray("api_mst_useitem");
            if (json_useitem != null) {
                this.useItem.clear();
                for (JsonValue elem : json_useitem) {
                    UseItemInfoDto dto = new UseItemInfoDto((JsonObject) elem);
                    this.useItem.put(dto.getId(), dto);
                }
            }

            this.time = new Date();
        }

        private void visitShip(ShipInfoDto ship, int[] charId) {
            if ((ship != null) && (ship.getData() == null)) {
                ship.setData(charId);

                if (ship.getAftershipid() != 0) {
                    this.visitShip(this.ships.get(ship.getAftershipid()), charId);
                }
                if (ship.getBeforeshpids() != null) {
                    for (int shipid : ship.getBeforeshpids()) {
                        this.visitShip(this.ships.get(shipid), charId);
                    }
                }
            }
        }

        /**
         * 初期艦IDを計算します
         */
        private void setCharId() {
            // リセット
            for (ShipInfoDto dto : this.ships.values()) {
                dto.setData(null);
                dto.setBeforeshpids(null);
            }
            // beforeshipidsを生成
            for (ShipInfoDto dto : this.ships.values()) {
                if (dto.getAftershipid() != 0) {
                    ShipInfoDto afterShip = this.ships.get(dto.getAftershipid());
                    if (afterShip != null) {
                        afterShip.setBeforeshpids(ArrayUtils.add(afterShip.getBeforeshpids(), dto.getShipId()));
                    }
                }
            }
            // 同じ島に同じint[]を配置
            for (ShipInfoDto dto : this.ships.values()) {
                this.visitShip(dto, new int[] { -1 });
            }
            // 初期艦のIDを探す
            for (ShipInfoDto dto : this.ships.values()) {
                if (dto.getBeforeshpids() == null) {
                    int[] charId = (int[]) dto.getData();
                    // 複数見つかったら（普通はありえないが）小さい方にしておく
                    if ((charId[0] == -1) || (charId[0] > dto.getShipId())) {
                        charId[0] = dto.getShipId();
                    }
                }
            }
            // IDをセット
            for (ShipInfoDto dto : this.ships.values()) {
                int[] charId = (int[]) dto.getData();
                dto.setCharId(charId[0]);
                dto.setData(null);
            }
        }

        /**
         * 艦娘を作成します
         *
         * @param object
         * @return
         */
        private ShipInfoDto toShipInfoDto(JsonObject object) {
            String name = object.getString("api_name");

            if ("なし".equals(name)) {
                return ShipInfoDto.EMPTY;
            }

            return new ShipInfoDto(object);
        }

        /** 艦種情報を取得 */
        public ShipTypeDto getShipType(int stype) {
            if ((stype <= 0) || (stype > this.stype.size())) {
                return null;
            }
            return this.stype.get(stype - 1);
        }

        /** （装備でない）アイテム情報を取得 */
        public UseItemInfoDto getUseItem(int id) {
            Integer key = id;
            return this.useItem.get(key);
        }

        /** マップ情報を取得 */
        public MapInfoDto getMapInfo(int area, int no) {
            int id = (area * 10) + no;
            return this.mapinfo.get(id);
        }

        public String getMissionName(int missionId) {
            MissionDto dto = this.mission.get(missionId);
            if (dto != null) {
                return dto.getName();
            }
            return null;
        }

        /**
         * @return maparea
         */
        public ArrayList<MapAreaDto> getMaparea() {
            return this.maparea;
        }

        /**
         * @return mapinfo
         */
        public Map<Integer, MapInfoDto> getMapinfo() {
            return this.mapinfo;
        }

        /**
         * @return mission
         */
        public Map<Integer, MissionDto> getMission() {
            return this.mission;
        }

        /**
         * @return stype
         */
        public List<ShipTypeDto> getStype() {
            return this.stype;
        }

        /**
         * @return useItem
         */
        public Map<Integer, UseItemInfoDto> getUseItem() {
            return this.useItem;
        }

        /**
         * @return time
         */
        public Date getTime() {
            return this.time;
        }

        /**
         * @param time セットする time
         */
        public void setTime(Date time) {
            this.time = time;
        }

        /**
         * @return ships
         */
        public Map<Integer, ShipInfoDto> getShips() {
            return this.ships;
        }

        /**
         * @return items
         */
        public Map<Integer, ItemInfoDto> getItems() {
            return this.items;
        }
    }

    /**
     * api_mst_maparea
     * @author Nekopanda
     */
    public static class MapAreaDto extends JsonData {

        public MapAreaDto() {
        }

        public MapAreaDto(JsonObject json) {
            super(json);
        }

        /**
         * @return id
         */
        public int getId() {
            return this.json.getInt("api_id");
        }

        /**
         * @return name
         */
        public String getName() {
            return this.json.getString("api_name");
        }
    }

    /**
     * api_mst_mapinfo
     * @author Nekopanda
     */
    public static class MapInfoDto extends JsonData {

        public MapInfoDto() {
        }

        public MapInfoDto(JsonObject json) {
            super(json);
        }

        /**
         * @return id
         */
        public int getId() {
            return this.json.getInt("api_id");
        }

        /**
         * @return maparea_id
         */
        public int getMaparea_id() {
            return this.json.getInt("api_maparea_id");
        }

        /**
         * @return no
         */
        public int getNo() {
            return this.json.getInt("api_no");
        }

        /**
         * @return name
         */
        public String getName() {
            return this.json.getString("api_name");
        }

    }

    /**
     * api_mst_mission
     * @author Nekopanda
     */
    public static class MissionDto extends JsonData {

        public MissionDto() {
        }

        public MissionDto(JsonObject json) {
            super(json);
        }

        /**
         * 遠征ID
         * @return id
         */
        public int getId() {
            return this.json.getInt("api_id");
        }

        /**
         * 遠征のマップエリアID
         * @return maparea_id
         */
        public int getMapareaId() {
            return this.json.getInt("api_maparea_id");
        }

        /**
         * 遠征名
         * @return name
         */
        public String getName() {
            return this.json.getString("api_name");
        }

        /**
         * 遠征にかかる時間
         * @return time
         */
        public int getTime() {
            return this.json.getInt("api_time");
        }

        /**
         * もらえるかもしれないアイテム
         * @return winItem
         */
        public UseItemDto[] getWinItem() {
            return new UseItemDto[] {
                    new UseItemDto(this.json.getJsonArray("api_win_item1")),
                    new UseItemDto(this.json.getJsonArray("api_win_item2")) };
        }
    }

    /**
     * api_mst_stype
     * @author Nekopanda
     */
    public static class ShipTypeDto extends JsonData {

        public ShipTypeDto() {
        }

        public ShipTypeDto(JsonObject json) {
            super(json);
        }

        /**
         * @return name
         */
        public String getName() {
            return this.json.getString("api_name");
        }

        /**
         * @return id
         */
        public int getId() {
            return this.json.getJsonNumber("api_id").intValue();
        }

        /**
         * @return equipType
         */
        public List<Boolean> getEquipType() {
            List<Boolean> equipType = new ArrayList<Boolean>();
            JsonObject json_equip_type = this.json.getJsonObject("api_equip_type");
            for (int i = 1;; ++i) {
                JsonNumber number = json_equip_type.getJsonNumber(String.valueOf(i));
                if (number == null)
                    break;
                equipType.add(number.intValue() != 0);
            }
            return equipType;
        }
    }

    /**
     * api_mst_useitem
     * @author Nekopanda
     */
    public static class UseItemInfoDto extends JsonData {

        public UseItemInfoDto() {
        }

        public UseItemInfoDto(JsonObject json) {
            super(json);
        }

        /**
         * @return id
         */
        public int getId() {
            return this.json.getJsonNumber("api_id").intValue();
        }

        /**
         * @return usetype
         */
        public int getUsetype() {
            return this.json.getJsonNumber("api_usetype").intValue();
        }

        /**
         * @return name
         */
        public String getName() {
            return this.json.getString("api_name");
        }

        /**
         * @return description
         */
        public String getDescription() {
            return this.json.getJsonArray("api_description").getString(0);
        }
    }
}
