/**
 * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
 *
 * @author Alexander Rose <alexander.rose@weirdbyte.de>
 */

import { StructureElement } from './structure'
import { Link } from './structure/structure/unit/links'
import { Shape } from './shape';
import { Sphere3D } from 'mol-math/geometry';
import { CentroidHelper } from 'mol-math/geometry/centroid-helper';
import { Vec3 } from 'mol-math/linear-algebra';
import { OrderedSet } from 'mol-data/int';
import { Structure } from './structure/structure';

/** A Loci that includes every loci */
export const EveryLoci = { kind: 'every-loci' as 'every-loci' }
export type EveryLoci = typeof EveryLoci
export function isEveryLoci(x: any): x is EveryLoci {
    return !!x && x.kind === 'every-loci';
}

/** A Loci that is empty */
export const EmptyLoci = { kind: 'empty-loci' as 'empty-loci' }
export type EmptyLoci = typeof EmptyLoci
export function isEmptyLoci(x: any): x is EmptyLoci {
    return !!x && x.kind === 'empty-loci';
}

/** A generic data loci */
export interface DataLoci {
    readonly kind: 'data-loci',
    readonly data: any,
    readonly tag: string
    readonly indices: OrderedSet<number>
}
export function isDataLoci(x: any): x is DataLoci {
    return !!x && x.kind === 'data-loci';
}
export function areDataLociEqual(a: DataLoci, b: DataLoci) {
    return a.data === b.data && a.tag === b.tag && OrderedSet.areEqual(a.indices, b.indices)
}
export function createDataLoci(data: any, tag: string, indices: OrderedSet<number>): DataLoci {
    return { kind: 'data-loci', data, tag, indices }
}

export { Loci }

type Loci = StructureElement.Loci | Structure.Loci | Link.Loci | EveryLoci | EmptyLoci | DataLoci | Shape.Loci

namespace Loci {
    export function areEqual(lociA: Loci, lociB: Loci) {
        if (isEveryLoci(lociA) && isEveryLoci(lociB)) return true
        if (isEmptyLoci(lociA) && isEmptyLoci(lociB)) return true
        if (isDataLoci(lociA) && isDataLoci(lociB)) {
            return areDataLociEqual(lociA, lociB)
        }
        if (Structure.isLoci(lociA) && Structure.isLoci(lociB)) {
            return Structure.areLociEqual(lociA, lociB)
        }
        if (StructureElement.isLoci(lociA) && StructureElement.isLoci(lociB)) {
            return StructureElement.areLociEqual(lociA, lociB)
        }
        if (Link.isLoci(lociA) && Link.isLoci(lociB)) {
            return Link.areLociEqual(lociA, lociB)
        }
        if (Shape.isLoci(lociA) && Shape.isLoci(lociB)) {
            return Shape.areLociEqual(lociA, lociB)
        }
        return false
    }

    const sphereHelper = new CentroidHelper(), tempPos = Vec3.zero();

    export function getBoundingSphere(loci: Loci, boundingSphere?: Sphere3D): Sphere3D | undefined {
        if (loci.kind === 'every-loci' || loci.kind === 'empty-loci') return void 0;

        if (!boundingSphere) boundingSphere = Sphere3D.zero()
        sphereHelper.reset();

        if (loci.kind === 'structure-loci') {
            return Sphere3D.copy(boundingSphere, loci.structure.boundary.sphere)
        } else if (loci.kind === 'element-loci') {
            return StructureElement.Loci.getBoundary(loci).sphere;
        } else if (loci.kind === 'link-loci') {
            for (const e of loci.links) {
                e.aUnit.conformation.position(e.aUnit.elements[e.aIndex], tempPos);
                sphereHelper.includeStep(tempPos);
                e.bUnit.conformation.position(e.bUnit.elements[e.bIndex], tempPos);
                sphereHelper.includeStep(tempPos);
            }
            sphereHelper.finishedIncludeStep();
            for (const e of loci.links) {
                e.aUnit.conformation.position(e.aUnit.elements[e.aIndex], tempPos);
                sphereHelper.radiusStep(tempPos);
                e.aUnit.conformation.position(e.bUnit.elements[e.bIndex], tempPos);
                sphereHelper.radiusStep(tempPos);
            }
        } else if (loci.kind === 'group-loci') {
            // TODO
            return void 0;
        } else if (loci.kind === 'data-loci') {
            // TODO maybe add loci.getBoundingSphere()???
            return void 0;
        }

        Vec3.copy(boundingSphere.center, sphereHelper.center)
        boundingSphere.radius = Math.sqrt(sphereHelper.radiusSq)
        return boundingSphere
    }

    const tmpSphere3D = Sphere3D.zero()
    export function getCenter(loci: Loci, center?: Vec3): Vec3 | undefined {
        const boundingSphere = getBoundingSphere(loci, tmpSphere3D)
        return boundingSphere ? Vec3.copy(center || Vec3.zero(), boundingSphere.center) : undefined
    }
}