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

import { ValueCell } from 'mol-util/value-cell'
import { createPointRenderObject, RenderObject, PointRenderObject } from 'mol-gl/render-object'
import { Unit, Element } from 'mol-model/structure';
import { Task } from 'mol-task'
import { fillSerial } from 'mol-gl/renderable/util';

import { UnitsRepresentation, DefaultStructureProps } from './index';
import VertexMap from '../../shape/vertex-map';
import { SizeTheme } from '../../theme';
import { createTransforms, createColors, createSizes, markElement } from './utils';
import { deepEqual, defaults } from 'mol-util';
import { SortedArray, OrderedSet } from 'mol-data/int';
import { RenderableState, PointValues } from 'mol-gl/renderable';
import { PickingId } from '../../util/picking';
import { Loci, EmptyLoci } from 'mol-model/loci';
import { MarkerAction, createMarkers } from '../../util/marker-data';

export const DefaultPointProps = {
    ...DefaultStructureProps,
    sizeTheme: { name: 'vdw' } as SizeTheme
}
export type PointProps = Partial<typeof DefaultPointProps>

export function createPointVertices(unit: Unit) {
    const elements = unit.elements
    const elementCount = elements.length
    const vertices = new Float32Array(elementCount * 3)

    const { x, y, z } = unit.conformation
    const l = Element.Location()
    l.unit = unit

    for (let i = 0; i < elementCount; i++) {
        l.element = elements[i];
        const i3 = i * 3
        vertices[i3] = x(l.element)
        vertices[i3 + 1] = y(l.element)
        vertices[i3 + 2] = z(l.element)
    }
    return vertices
}

export default function PointUnitsRepresentation(): UnitsRepresentation<PointProps> {
    const renderObjects: RenderObject[] = []
    let points: PointRenderObject
    let currentProps = DefaultPointProps
    let currentGroup: Unit.SymmetryGroup

    let _units: ReadonlyArray<Unit>
    let _elements: SortedArray

    return {
        renderObjects,
        create(group: Unit.SymmetryGroup, props: PointProps = {}) {
            currentProps = Object.assign({}, DefaultPointProps, props)

            return Task.create('Point.create', async ctx => {
                renderObjects.length = 0 // clear
                currentGroup = group

                _units = group.units
                _elements = group.elements;

                const { colorTheme, sizeTheme } = currentProps
                const elementCount = _elements.length
                const instanceCount = group.units.length

                const vertexMap = VertexMap.create(
                    elementCount,
                    elementCount + 1,
                    fillSerial(new Uint32Array(elementCount)),
                    fillSerial(new Uint32Array(elementCount + 1))
                )

                await ctx.update('Computing point vertices');
                const vertices = createPointVertices(_units[0])

                await ctx.update('Computing point transforms');
                const transforms = createTransforms(group)

                await ctx.update('Computing point colors');
                const color = createColors(group, vertexMap, colorTheme)

                await ctx.update('Computing point sizes');
                const size = createSizes(group, vertexMap, sizeTheme)

                await ctx.update('Computing spacefill marks');
                const marker = createMarkers(instanceCount * elementCount)

                const values: PointValues = {
                    aPosition: ValueCell.create(vertices),
                    aElementId: ValueCell.create(fillSerial(new Float32Array(elementCount))),
                    aTransform: transforms,
                    aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))),
                    ...color,
                    ...marker,
                    ...size,

                    uAlpha: ValueCell.create(defaults(props.alpha, 1.0)),
                    uInstanceCount: ValueCell.create(instanceCount),
                    uElementCount: ValueCell.create(group.elements.length),

                    drawCount: ValueCell.create(vertices.length / 3),
                    instanceCount: ValueCell.create(instanceCount),

                    dPointSizeAttenuation: ValueCell.create(true),
                    dUseFog: ValueCell.create(defaults(props.useFog, true)),
                }
                const state: RenderableState = {
                    depthMask: defaults(props.depthMask, true),
                    visible: defaults(props.visible, true)
                }

                points = createPointRenderObject(values, state)
                renderObjects.push(points)
            })
        },
        update(props: PointProps) {
            return Task.create('Point.update', async ctx => {
                if (!points || !_units || !_elements) return false

                const newProps = { ...currentProps, ...props }
                if (deepEqual(currentProps, newProps)) {
                    console.log('props identical, nothing to change')
                    return true
                }

                // const elementCount = OrderedSet.size(_elementGroup.elements)
                // const unitCount = _units.length

                // const vertexMap = VertexMap.create(
                //     elementCount,
                //     elementCount + 1,
                //     fillSerial(new Uint32Array(elementCount)),
                //     fillSerial(new Uint32Array(elementCount + 1))
                // )

                if (!deepEqual(currentProps.colorTheme, newProps.colorTheme)) {
                    console.log('colorTheme changed', currentProps.colorTheme, newProps.colorTheme)
                    // await ctx.update('Computing point colors');
                    // const color = createColors(_units, _elementGroup, vertexMap, newProps.colorTheme)
                    // ValueCell.update(points.props.color, color)
                }

                if (!deepEqual(currentProps.sizeTheme, newProps.sizeTheme)) {
                    console.log('sizeTheme changed', currentProps.sizeTheme, newProps.sizeTheme)
                }

                currentProps = newProps
                return false
            })
        },
        getLoci(pickingId: PickingId) {
            const { objectId, instanceId, elementId } = pickingId
            if (points.id === objectId) {
                const unit = currentGroup.units[instanceId]
                const indices = OrderedSet.ofSingleton(elementId)
                return Element.Loci([{ unit, indices }])
            }
            return EmptyLoci
        },
        mark(loci: Loci, action: MarkerAction) {
            markElement(points.values.tMarker, currentGroup, loci, action)
        }
    }
}