/**
 * A class representing coordinates in a hexagonal grid with 2 dimensions.
 * Offset system is "odd-r".
 */
export class Hex {
    x: number;
    y: number;

    constructor(x: number | { x: number, y: number }, y?: number) {
        if (typeof x === "number") {
            this.x = x as number;
            this.y = y || 0;
        }
        else {
            this.x = x.x;
            this.y = x.y;
        }
    }

    toCube() {
        const q = this.x - (this.y - (this.y&1)) / 2;
        const r = this.y;
        return new Cube(q, r, -q - r);
    }
}


/**
 * A class representing coordinates in a hexagonal grid with 3 dimensions.
 * Mostly used as a transient value for easier algorithms.
 */
class Cube {
    q: number;
    r: number;
    s: number;

    constructor(q: number, r: number, s: number) {
        this.q = q;
        this.r = r;
        this.s = s;
    }

    toHex() {
        return new Hex(
            this.q + (this.r - (this.r&1)) / 2,
            this.r
        );
    }

    add(cube: Cube) {
        return new Cube(this.q + cube.q, this.r + cube.r, this.s + cube.s);
    }

    subtract(cube: Cube) {
        return new Cube(this.q - cube.q, this.r - cube.r, this.s - cube.s);
    }
}


export function getCornersAtDistance(distance: number) {
    const results: Hex[] = [];
    for (let q = -distance; q <= distance; q++) {
        for (let r = Math.max(-distance, -q - distance); r <= Math.min(distance, -q + distance); r++) {
            const s = -q - r;
            if (
                (q === 0 || r === 0 || s === 0)
                && Math.max(Math.abs(q), Math.abs(r), Math.abs(s)) === distance
            ) {
                const cube = new Cube(q, r, s);
                results.push(cube.toHex());
            }
        }
    }
    return results;
}


export function getTilesAtDistance(distance: number) {
    const results: Hex[] = [];
    for (let q = -distance; q <= distance; q++) {
        for (let r = Math.max(-distance, -q - distance); r <= Math.min(distance, -q + distance); r++) {
            const s = -q - r;
            if (Math.max(Math.abs(q), Math.abs(r), Math.abs(s)) === distance) {
                const cube = new Cube(q, r, s);
                results.push(cube.toHex());
            }
        }
    }
    return results;
}


const adjacentVectors = [
    new Cube(+1, 0, -1), new Cube(+1, -1, 0), new Cube(0, -1, +1),
    new Cube(-1, 0, +1), new Cube(-1, +1, 0), new Cube(0, +1, -1),
];


/**
 * Return all coordinates adjacent to a given one.
 * @param tile Hex - 2D coordinates in a hexagonal grid.
 * @returns Array of 6 2D coordinates.
 */
export function getAdjacentCoords(tile: Hex): Hex[] {
    const origin = tile.toCube();
    return adjacentVectors.map(vec => origin.add(vec).toHex());
}


/**
 * Return a position on the 3D board based on coordinates of a tile on our grid.
 * @param x The x coordinate in the grid.
 * @param y The y coordinate in the grid.
 * @returns An array containing the 3 coordinates (x, y, z) of that tile on a 3D board.
 */
export function getPositionOnBoard(x: number, y: number): [number, number, number] {
    const posX = x * 2 + (y % 2 ? 1 : 0);
    const posY = 0;
    const posZ = y * 1.72;
    return [ posX, posY, posZ ];
}


/**
 * Return the distance between two cells.
 * @param a Hex - 2D coordinates in a hexagonal grid.
 * @param b Hex - 2D coordinates in a hexagonal grid.
 * @returns Distance between the two coordinates.
 */
export function distanceBetween(a: Hex, b: Hex): number {
    const aCube = a.toCube();
    const bCube = b.toCube();
    const vector = aCube.subtract(bCube);
    return (Math.abs(vector.q) + Math.abs(vector.r) + Math.abs(vector.s)) / 2;
}
