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

// adapted from three.js, MIT License Copyright 2010-2018 three.js authors

import { Vec3 } from 'mol-math/linear-algebra'
import { Primitive } from './primitive';

export const DefaultCylinderProps = {
    radiusTop: 1,
    radiusBottom: 1,
    height: 1,
    radialSegments: 8,
    heightSegments: 1,
    topCap: false,
    bottomCap: false,
    thetaStart: 0.0,
    thetaLength: Math.PI * 2
}
export type CylinderProps = Partial<typeof DefaultCylinderProps>

export function Cylinder(props?: CylinderProps): Primitive {
    const { radiusTop, radiusBottom, height, radialSegments, heightSegments, topCap, bottomCap, thetaStart, thetaLength } = { ...DefaultCylinderProps, ...props };

    // buffers
    const indices: number[] = [];
    const vertices: number[] = [];
    const normals: number[] = [];

    // helper variables
    let index = 0;
    const indexArray: number[][] = [];
    const halfHeight = height / 2;

    // generate geometry
    generateTorso();

    if (topCap && radiusTop > 0) generateCap(true);
    if (bottomCap && radiusBottom > 0) generateCap(false);

    return {
        vertices: new Float32Array(vertices),
        normals: new Float32Array(normals),
        indices: new Uint32Array(indices)
    }

    function generateTorso() {
        const normal = Vec3.zero()

        // this will be used to calculate the normal
        const slope = (radiusBottom - radiusTop) / height;

        // generate vertices, normals and uvs

        for (let y = 0; y <= heightSegments; ++y) {
            const indexRow: number[] = [];
            const v = y / heightSegments;

            // calculate the radius of the current row
            const radius = v * (radiusBottom - radiusTop) + radiusTop;

            for (let x = 0; x <= radialSegments; ++x) {
                const u = x / radialSegments;
                const theta = u * thetaLength + thetaStart;
                const sinTheta = Math.sin(theta);
                const cosTheta = Math.cos(theta);

                // vertex
                vertices.push(radius * sinTheta, -v * height + halfHeight, radius * cosTheta);

                // normal
                Vec3.normalize(normal, Vec3.set(normal, sinTheta, slope, cosTheta));
                normals.push(...normal);

                // save index of vertex in respective row
                indexRow.push(index++);
            }

            // now save vertices of the row in our index array
            indexArray.push(indexRow);
        }

        // generate indices

        for (let x = 0; x < radialSegments; ++x) {

            for (let y = 0; y < heightSegments; ++y) {
                // we use the index array to access the correct indices
                const a = indexArray[ y ][ x ];
                const b = indexArray[ y + 1 ][ x ];
                const c = indexArray[ y + 1 ][ x + 1 ];
                const d = indexArray[ y ][ x + 1 ];

                // faces
                indices.push(a, b, d);
                indices.push(b, c, d);
            }

        }
    }

    function generateCap(top: boolean) {
        const radius = (top === true) ? radiusTop : radiusBottom;
        const sign = (top === true) ? 1 : - 1;

        // save the index of the first center vertex
        let centerIndexStart = index;

        // first we generate the center vertex data of the cap.
        // because the geometry needs one set of uvs per face,
        // we must generate a center vertex per face/segment

        for (let x = 1; x <= radialSegments; ++x) {
            // vertex
            vertices.push(0, halfHeight * sign, 0);

            // normal
            normals.push(0, sign, 0);

            // increase index
            ++index;
        }

        // save the index of the last center vertex
        let centerIndexEnd = index;

        // now we generate the surrounding vertices, normals and uvs
        for (let x = 0; x <= radialSegments; ++x) {
            const u = x / radialSegments;
            const theta = u * thetaLength + thetaStart;

            const cosTheta = Math.cos(theta);
            const sinTheta = Math.sin(theta);

            // vertex
            vertices.push(radius * sinTheta, halfHeight * sign, radius * cosTheta);

            // normal
            normals.push(0, sign, 0);

            // increase index
            ++index;
        }

        // generate indices
        for (let x = 0; x < radialSegments; ++x) {
            const c = centerIndexStart + x;
            const i = centerIndexEnd + x;

            if (top === true) {
                indices.push(i, i + 1, c); // face top
            } else {
                indices.push(i + 1, i, c); // face bottom
            }
        }
    }
}