import { Box3, Geometry, Line, Matrix4, Mesh, Object3D, Vector2, Vector3, type Vector } from 'three';
import { Matrix2 } from './Matrix2';


export const cZeroVec = new Vector3();
export const cXAxis = new Vector3(1, 0, 0);
export const cYAxis = new Vector3(0, 1, 0);
export const cZAxis = new Vector3(0, 0, 1);

/**
 * 旋转一个点,旋转中心在原点
 *
 * @export
 * @param {Vector3} pt 点
 * @param {number} ang 角度.
 * @returns {Vector3} 返回pt不拷贝.
 */
export function rotatePoint(pt: Vector3, ang: number): Vector3
{
    new Matrix2().setRotate(ang).applyVector(pt);
    return pt;
}

export function equaln(v1: number, v2: number, fuzz = 1e-3)
{
    return Math.abs(v1 - v2) < fuzz;
}

export function equalv3(v1: Vector3, v2: Vector3, fuzz = 1e-8)
{
    return equaln(v1.x, v2.x, fuzz) && equaln(v1.y, v2.y, fuzz) && equaln(v1.z, v2.z, fuzz);
}

export function equal<T extends Vector>(v1: T, v2: T)
{
    return v1.distanceToSquared(v2) < 1e-8;
}

export function fixAngle(an: number, fixAngle: number, fuzz: number = 0.1)
{
    if (an < 0)
        an += Math.PI * 2;
    an += fuzz;
    let rem = an % fixAngle;
    if (rem < fuzz * 2)
    {
        an -= rem;
    }
    else
    {
        an -= fuzz;
    }
    return an;
}

/**
 * 按照极坐标的方式移动一个点
 *
 * @export
 * @template
 * @param {T} v 向量(2d,3d)
 * @param {number} an 角度
 * @param {number} dis 距离
 * @returns {T}
 */
export function polar<T extends Vector2 | Vector3>(v: T, an: number, dis: number): T
{
    v.x += Math.cos(an) * dis;
    v.y += Math.sin(an) * dis;
    return v;
}

export function angle(v: Vector3 | Vector2)
{
    if (equaln(v.y, 0) && v.x > 0)
        return 0;
    let angle = Math.atan2(v.y, v.x);
    if (angle < 0) angle += Math.PI * 2;
    return angle;
}

/**
 * 求两个向量的夹角,顺时针为负,逆时针为正
 *
 * @param {Vector3} v1
 * @param {Vector3} v2
 * @param {Vector3} [ref] 参考向量,如果为世界坐标系则为0,0,1
 * @returns
 */
export function angleTo(v1: Vector3, v2: Vector3, ref: Vector3 = new Vector3(0, 0, 1))
{
    if (!ref.equals(new Vector3(0, 0, 1)))
    {
        //任意轴坐标系. 使用相机的构造矩阵.
        ref.multiplyScalar(-1);
        let up = getLoocAtUpVec(ref);
        let refOcs = new Matrix4();
        refOcs.lookAt(cZeroVec, ref, up);
        let refOcsInv = new Matrix4().getInverse(refOcs);
        v1.applyMatrix4(refOcsInv);
        v2.applyMatrix4(refOcsInv);
        v1.z = 0;
        v2.z = 0;
    }
    if (v1.equals(cZeroVec) || v2.equals(cZeroVec))
        return 0;
    let cv = new Vector3().crossVectors(v1, v2).normalize();
    return cv.z === 0 ? v1.angleTo(v2) : v1.angleTo(v2) * cv.z;
}

export function getLoocAtUpVec(dir: Vector3): Vector3
{
    if (dir.equals(cZeroVec))
    {
        throw ("zero vector");
    }
    let norm = dir.clone().normalize();
    if (norm.equals(cZAxis))
    {
        return new Vector3(0, 1, 0);
    }
    else if (norm.equals(cZAxis.clone().negate()))
    {
        return new Vector3(0, -1, 0);
    }
    else
    {
        let xv: Vector3 = new Vector3();
        xv.crossVectors(cZAxis, norm);

        let up = new Vector3();
        up.crossVectors(norm, xv);
        return up;
    }
}

export function createLookAtMat4(dir: Vector3): Matrix4
{
    let up = getLoocAtUpVec(dir);
    let mat = new Matrix4();
    mat.lookAt(cZeroVec, dir, up);
    return mat;
}

export function isParallelTo(v1: Vector3, v2: Vector3)
{
    return v1.clone().cross(v2).lengthSq() < 1e-9;
}

export function ptToString(v: Vector3, fractionDigits: number = 3): string
{
    return v.toArray().map(o =>
    {
        return o.toFixed(fractionDigits);
    }).join(",");
}

export function midPoint(v1: Vector3, v2: Vector3): Vector3
{
    return v1.clone().add(v2).multiplyScalar(0.5);
}
export function midPoint2(v1: Vector2, v2: Vector2): Vector2
{
    return v1.clone().add(v2).multiplyScalar(0.5);
}

export function midPtCir(v1: Vector3, v2: Vector3)
{
    let baseline = new Vector3(1, 0, 0);
    let outLine = v2.clone().sub(v1);
    let ang = angleTo(baseline, outLine) / 2;
    let midLine = rotatePoint(outLine, -ang);
    return v1.clone().add(midLine);
}

/**
 * 获得Three对象的包围盒.
 * @param obj
 * @param [updateMatrix] 是否应该更新对象矩阵
 * @returns box
 */
export function GetBox(obj: Object3D, updateMatrix?: boolean): Box3
{
    let box = new Box3();
    if (updateMatrix) obj.updateMatrixWorld(false);
    if (!obj.visible) return box;

    obj.traverse(o =>
    {
        //因为实体Erase时,实体仍然保存在Scene中.
        if (o.visible === false)
            return;

        //@ts-ignore
        let geo = o.geometry as BufferGeometry;
        if (geo)
        {
            if (!geo.boundingBox)
                geo.computeBoundingBox();
            box.union(geo.boundingBox.clone().applyMatrix4(o.matrixWorld));
        }
    });

    return box;
}

export function GetBoxArr(arr: Array<Object3D>): Box3
{
    let box = new Box3();
    for (let o of arr)
    {
        let b = GetBox(o);
        if (!b.isEmpty())
            box.union(b);
    }
    return box;
}

export function MoveMatrix(v: Vector3): Matrix4
{
    let mat = new Matrix4();
    mat.makeTranslation(v.x, v.y, v.z);
    return mat;
}

export function getProjectDist(v1: Vector3, v2: Vector3)
{
    let ang = v1.angleTo(v2);
    let dist = v1.length();
    return {
        h: dist * Math.cos(ang),
        v: dist * Math.sin(ang)
    };
}
//获得输入点在2线组成的4个区间的位置
export function getPtPostion(sp: Vector3, ep: Vector3, c: Vector3, inPt: Vector3)
{
    let l1 = sp.clone().sub(c);
    let l2 = ep.clone().sub(c);
    let l3 = l1.clone().negate();
    let l4 = l2.clone().negate();
    let inputLine = inPt.clone().sub(c);
    let ang1 = angleTo(l1, l2);
    let ang2 = Math.PI;
    let ang3 = ang2 + Math.abs(ang1);
    let inputAng = angleTo(l1, inputLine);
    if (ang1 * inputAng < 0)
    {
        inputAng = (Math.PI * 2 - Math.abs(inputAng));
    }
    ang1 = Math.abs(ang1);
    inputAng = Math.abs(inputAng);
    if (inputAng <= ang1)
    {
        return { sp, ep };
    } else if (inputAng > ang1 && inputAng <= ang2)
    {
        return { sp: c.clone().add(l3), ep };
    } else if (inputAng > ang2 && inputAng <= ang3)
    {
        return { sp: c.clone().add(l3), ep: c.clone().add(l4) };
    } else
    {
        return { sp, ep: c.clone().add(l4) };
    }
}
export function angleAndX(v: Vector3 | Vector2)
{
    return v.x ? Math.atan(v.y / v.x) : Math.PI / 2;
}
/**
 * 将角度调整为0-2pi之间
 *
 * @export
 * @param {number} an
 */
export function angleTo2Pi(an: number)
{
    an = an % (Math.PI * 2);
    if (an < 0) an += Math.PI * 2;
    return an;
}
export function updateGeometry(l: Line | Mesh, geometry: Geometry)
{
    let geo = l.geometry as Geometry;
    geo.dispose();
    l.geometry = geometry;
    geometry.verticesNeedUpdate = true;
    geometry.computeBoundingSphere();
}
