type index = {
    x: number,
    y: number
}

/**
 * AStar类用于实现A*寻路算法。
 */
export class AStar {
    private map: any[][] = []; // 地图数组
    private weightMap: number[][] = null; // 权重地图数组
    private openlist: index[] = []; // 开放列表，存储待评估的节点
    private closelist: index[] = []; // 封闭列表，存储已评估的节点
    private parentMap: Map<index, index>; // 父节点映射，用于回溯路径
    private gcostMap: Map<index, number>; // g成本映射，记录从起点到当前节点的成本
    private hcostMap: Map<index, number>; // h成本映射，记录从当前节点到终点的估算成本
    private startValue: number = 0;
    private endValue: number = 0;
    private pathValue: number = 0;
    private isEightDirections: boolean = false;
    private neighborMap: Map<index, index[]>;

    constructor() {
        this.openlist = [];
        this.closelist = [];
        this.parentMap = new Map();
        this.gcostMap = new Map();
        this.hcostMap = new Map();
    }

    /**
     * 设置地图。
     * @param map 二维数组表示的地图
     */
    setMap(map: number[][]) {
        this.map = map;
    }

    /**
     * 设置权重地图。
     * @param weightMap 二维数组表示的权重地图
     */
    setWeightMap(weightMap: number[][]) {
        this.weightMap = weightMap;
    }

    setValue(startValue: number, endValue: number, pathValue: number) {
        this.startValue = startValue;
        this.endValue = endValue;
        this.pathValue = pathValue;
    }

    setEightDirections(isEightDirections: boolean) {
        this.isEightDirections = isEightDirections;
    }

    setNeighborMap(neighborMap: Map<index, index[]>) {
        this.neighborMap = neighborMap;
    }

    /**
     * 在给定范围内根据指定类型搜索从起点到终点的路径。
     * @param start 起点索引
     * @param end 终点索引
     * @param type 搜索类型（可能影响启发式函数的选择）
     * @returns 返回找到的路径数组，如果无路径则返回空数组
     */
    search(start: index, end: index, type: number) {
        this.init(); // 初始化搜索环境

        if (this.map[start.y][start.x] !== this.startValue ||
            this.map[end.y][end.x] !== this.endValue) {
            console.log("Invalid start or end point");
            return [];
        }

        // 初始化起点信息，并将其加入开放列表
        this.openlist.push(start);
        this.gcostMap.set(start, 0); // 起点到起点的距离为0
        this.hcostMap.set(start, this.getHeuristicCost(start, end, type)); // 起点到终点的启发式成本
        this.parentMap.set(start, null); // 起点的父节点设置为null

        // 循环直到开放列表为空，即所有可能的路径都被考虑
        while (this.openlist.length > 0) {
            const current = this.getMinCostNode(this.openlist); // 从开放列表中选择成本最小的节点

            if (!current) {
                this.openlist.splice(this.openlist.indexOf(current), 1);
                continue;
            }

            // 判断是否到达终点
            if (this.isArrived(current, end)) {
                return this.reconstructPath(end); // 如果到达终点，返回路径重构的结果
            } else {
                // 从开放列表移除当前节点，将其加入封闭列表
                this.openlist.splice(this.openlist.indexOf(current), 1);
                this.closelist.push(current);

                // 遍历当前节点的所有邻居节点
                const neighbors = this.getNeighbors(current);
                for (const neighbor of neighbors) {
                    const hCost = this.getHeuristicCost(neighbor, end, type); // 计算邻居节点到终点的启发式成本
                    const gCost = this.gcostMap.get(current) + this.getGCost(current, neighbor, type); // 计算当前节点到邻居节点的实际成本
                    const totalCost = gCost + hCost; // 计算总成本

                    // 根据邻居节点的状态进行处理
                    if (!this.isInClosedList(neighbor)) {
                        if (!this.isInOpenList(neighbor)) {
                            this.openlist.push(neighbor); // 如果邻居未在开放列表中，将其加入
                            this.updateCost(neighbor, gCost, hCost); // 更新邻居节点的成本信息
                            this.updateParent(neighbor, current); // 更新邻居节点的父节点信息
                        } else {
                            // 如果邻居已在开放列表中，但通过当前节点发现更优路径
                            if (totalCost < this.getCost(neighbor)) {
                                this.updateCost(neighbor, gCost, hCost); // 更新成本信息
                                this.updateParent(neighbor, current); // 更新父节点信息
                            }
                        }
                    }
                }
            }
        }

        console.log("No path found.");
        return []; // 如果无法找到路径，返回空数组
    }

    /**
     * 初始化算法状态。
     */
    private init() {
        // 重置所有列表和映射
        this.openlist = [];
        this.closelist = [];
        this.parentMap = new Map();
        this.gcostMap = new Map();
        this.hcostMap = new Map();
    }

    /**
     * 获取当前节点的邻居。
     * @param current 当前节点坐标
     * @returns 邻居节点数组
     */
    private getNeighbors(current: index) {
        if (this.neighborMap.size === 0) {
            const neighbors: index[] = [];

            // 尝试获取四个方向的邻居节点
            const upindex = { x: current.x, y: current.y + 1 };
            const downindex = { x: current.x, y: current.y - 1 };
            const leftindex = { x: current.x - 1, y: current.y };
            const rightindex = { x: current.x + 1, y: current.y };

            const upleftindex = { x: current.x - 1, y: current.y + 1 };
            const uprightindex = { x: current.x + 1, y: current.y + 1 };
            const downleftindex = { x: current.x - 1, y: current.y - 1 };
            const downrightindex = { x: current.x + 1, y: current.y - 1 };

            // 如果合法则加入邻居列表
            if (this.isValidIndex(upindex) && (this.map[upindex.y][upindex.x] == this.pathValue ||
                this.map[upindex.y][upindex.x] == this.endValue)) {
                neighbors.push(upindex);
            }
            if (this.isValidIndex(downindex) && (this.map[downindex.y][downindex.x] == this.pathValue ||
                this.map[downindex.y][downindex.x] == this.endValue)) {
                neighbors.push(downindex);
            }
            if (this.isValidIndex(leftindex) && (this.map[leftindex.y][leftindex.x] == this.pathValue ||
                this.map[leftindex.y][leftindex.x] == this.endValue)) {
                neighbors.push(leftindex);
            }
            if (this.isValidIndex(rightindex) && (this.map[rightindex.y][rightindex.x] == this.pathValue ||
                this.map[rightindex.y][rightindex.x] == this.endValue)) {
                neighbors.push(rightindex);
            }

            if (this.isEightDirections) {
                if (this.isValidIndex(upleftindex) && (this.map[upleftindex.y][upleftindex.x] == this.pathValue ||
                    this.map[upleftindex.y][upleftindex.x] == this.endValue)) {
                    neighbors.push(upleftindex);
                }
                if (this.isValidIndex(downleftindex) && (this.map[downleftindex.y][downleftindex.x] == this.pathValue ||
                    this.map[downleftindex.y][downleftindex.x] == this.endValue)) {
                    neighbors.push(downleftindex);
                }
                if (this.isValidIndex(downrightindex) && (this.map[downrightindex.y][downrightindex.x] == this.pathValue ||
                    this.map[downrightindex.y][downrightindex.x] == this.endValue)) {
                    neighbors.push(downrightindex);
                }
                if (this.isValidIndex(uprightindex) && (this.map[uprightindex.y][uprightindex.x] == this.pathValue ||
                    this.map[uprightindex.y][uprightindex.x] == this.endValue)) {
                    neighbors.push(uprightindex);
                }
            }

            return neighbors;
        } else {
            const _neighbors = this.neighborMap.get(current);
            const neighbors: index[] = [];
            for (const neighbor of _neighbors) {
                if (this.map[neighbor.y][neighbor.x] == this.pathValue ||
                    this.map[neighbor.y][neighbor.x] == this.endValue) {
                    neighbors.push(neighbor);
                }
            }
            return neighbors;
        }
    }

    /**
     * 计算从当前节点到邻居节点的实际成本
     * @param current 当前节点
     * @param neighbor 邻居节点
     * @param type 距离计算类型
     * @returns 返回从当前节点到邻居节点的成本
     */
    private getGCost(current: index, neighbor: index, type: number = 0) {
        let baseCost = 0;

        // 计算基础距离成本
        switch (type) {
            case 0: {
                // 计算曼哈顿距离
                baseCost = Math.abs(current.x - neighbor.x) + Math.abs(current.y - neighbor.y);
                break;
            }
            case 1: {
                // 计算欧几里得距离
                baseCost = Math.sqrt(Math.pow(current.x - neighbor.x, 2) + Math.pow(current.y - neighbor.y, 2));
                break;
            }
            case 2: {
                // 计算切比雪夫距离
                const x = Math.abs(current.x - neighbor.x);
                const y = Math.abs(current.y - neighbor.y);
                const minxy = Math.min(x, y);
                baseCost = x + y + (Math.sqrt(2) - 2) * minxy;
                break;
            }
        }

        // 如果存在权重地图，应用权重
        if (this.weightMap && this.isValidIndex(neighbor)) {
            const weight = this.weightMap[neighbor.y][neighbor.x];
            return baseCost * weight;
        }

        return baseCost;
    }

    /**
     * 根据给定的起始点和目标点，以及类型参数，计算启发式成本。
     * @param start 起始点的索引，包含 x 和 y 坐标。
     * @param end 目标点的索引，包含 x 和 y 坐标。
     * @param type 计算成本的类型，0 表示曼哈顿距离，1 表示欧几里得距离，2 表示切比雪夫距离，默认为 0。
     * @returns 返回根据指定类型计算出的启发式成本。
     */
    private getHeuristicCost(start: index, end: index, type: number = 0) {
        switch (type) {
            case 0: {
                // 计算曼哈顿距离
                return Math.abs(start.x - end.x) + Math.abs(start.y - end.y);
            }
            case 1: {
                // 计算欧几里得距离
                return Math.sqrt(Math.pow(start.x - end.x, 2) + Math.pow(start.y - end.y, 2));
            }
            case 2: {
                // 计算切比雪夫距离
                const x = Math.abs(start.x - end.x);
                const y = Math.abs(start.y - end.y);
                const minxy = Math.min(x, y);
                return x + y + (Math.sqrt(2) - 2) * minxy;
            }
        }
    }

    /**
     * 检查节点是否在封闭列表中。
     * @param node 节点坐标
     * @returns {boolean} 如果节点在封闭列表中返回true，否则返回false
     */
    private isInClosedList(node: index): boolean {
        // 遍历封闭列表查找节点
        let _index = -1;
        for (let i = 0; i < this.closelist.length; i++) {
            if (this.closelist[i].x == node.x && this.closelist[i].y == node.y) {
                _index = i;
                break;
            }
        }
        return _index != -1;
    }

    /**
     * 检查节点是否在开放列表中。
     * @param node 节点坐标
     * @returns {boolean} 如果节点在开放列表中返回true，否则返回false
     */
    private isInOpenList(node: index): boolean {
        // 遍历开放列表查找节点
        let _index = -1;
        for (let i = 0; i < this.openlist.length; i++) {
            if (this.openlist[i].x == node.x && this.openlist[i].y == node.y) {
                _index = i;
                break;
            }
        }
        return _index != -1;
    }

    /**
     * 获取节点的成本。
     * @param node 节点坐标
     * @returns 节点的总成本
     */
    private getCost(node: index): number {
        let gcost = 0;
        let hcost = 0;
        this.gcostMap.forEach((value, key) => {
            if (key.x == node.x && key.y == node.y) {
                gcost = value;
            }
        });
        this.hcostMap.forEach((value, key) => {
            if (key.x == node.x && key.y == node.y) {
                hcost = value;
            }
        });
        return gcost + hcost;
    }

    /**
     * 获取开放列表中成本最小的节点。
     * @param list 开放列表
     * @returns 成本最小的节点
     */
    private getMinCostNode(list: index[]): index {
        let _minindex = -1;
        let _mincost = Number.MAX_SAFE_INTEGER;
        // 寻找成本最小的节点
        for (let i = 0; i < list.length; i++) {
            const cost = this.getCost(list[i]);
            if (cost < _mincost) {
                _minindex = i;
                _mincost = cost;
            }
        }
        return list[_minindex];
    }

    /**
     * 更新节点的成本和父节点信息。
     * @param node 节点坐标
     * @param gcost 从起点到当前节点的实际成本
     * @param hcost 从当前节点到终点的估算成本
     */
    private updateCost(node: index, gcost: number, hcost: number) {
        this.gcostMap.set(node, gcost);
        this.hcostMap.set(node, hcost);
    }

    /**
     * 更新节点的父节点。
     * @param node 节点坐标
     * @param parent 父节点坐标
     */
    private updateParent(node: index, parent: index) {
        const existingparent = this.parentMap.get(node);
        // 仅当新的父节点成本更低时更新
        if (!existingparent || this.getCost(parent) < this.getCost(existingparent)) {
            this.parentMap.set(node, parent);
        }
    }

    /**
     * 根据父节点映射重构路径。
     * @param endPoint 终点坐标
     * @returns 从起点到终点的路径
     */
    private reconstructPath(endPoint: index) {
        const path = [endPoint];
        let currentnode = endPoint;

        // 逆向回溯路径
        while (currentnode) {
            let parent = null;
            this.parentMap.forEach((value, key) => {
                if (currentnode.x == key.x && currentnode.y == key.y) {
                    parent = value;
                }
            })
            if (parent) {
                path.unshift(parent);
                currentnode = parent;
            } else {
                currentnode = null;
            }
        }

        return path;
    }


    /**
     * 检查给定的索引是否为有效索引。
     * @param index 一个包含x和y属性的对象，代表要检查的索引位置。
     * @returns 返回一个布尔值，如果索引位置有效，则为true；否则为false。
     */
    private isValidIndex(index: index): boolean {
        return index.x >= 0 && index.x < this.map[0].length && index.y >= 0 && index.y < this.map.length;
    }

    /**
     * 判断当前节点是否到达了目标节点。
     * @param node 一个包含x和y属性的对象，代表当前节点的位置。
     * @param end 一个包含x和y属性的对象，代表目标节点的位置。
     * @returns 返回一个布尔值，如果当前节点已到达目标节点，则为true；否则为false。
     */
    private isArrived(node: index, end: index) {
        // 比较当前节点和目标节点的坐标，判断是否已经到达目标位置
        return node.x == end.x && node.y == end.y;
    }
}

