import { N8AOPostPass } from "n8ao";
import { Color, PerspectiveCamera } from "three";

import { serializable } from "../../../engine/engine_serialization.js";
import { validate } from "../../../engine/engine_util_decorator.js";
import { type EffectProviderResult, PostProcessingEffect } from "../PostProcessingEffect.js";
import { VolumeParameter } from "../VolumeParameter.js";
import { registerCustomEffectType } from "../VolumeProfile.js";

// https://github.com/N8python/n8ao

/**See (N8AO documentation)[https://github.com/N8python/n8ao] */
export enum ScreenSpaceAmbientOcclusionN8QualityMode {
    Performance = 0,
    Low = 1,
    Medium = 2,
    High = 3,
    Ultra = 4,
}

/** Screen Space Ambient Occlusion (SSAO) effect.  
 * @category Effects
 * @group Components
 * @link [N8AO documentation](https://github.com/N8python/n8ao)
 */
export class ScreenSpaceAmbientOcclusionN8 extends PostProcessingEffect {

    get typeName() {
        return "ScreenSpaceAmbientOcclusionN8";
    }

    @validate()
    @serializable()
    gammaCorrection: boolean = true;

    /** The most important parameter for your ambient occlusion effect. 
     * Controls the radius/size of the ambient occlusion in world units. 
     * Should be set to how far you want the occlusion to extend from a given object. 
     * Set it too low, and AO becomes an edge detector. 
     * Too high, and the AO becomes "soft" and might not highlight the details you want. 
     * The radius should be one or two magnitudes less than scene scale: 
     * if your scene is 10 units across, the radius should be between 0.1 and 1. If its 100, 1 to 10.  
     * @default 1
     */
    @serializable(VolumeParameter)
    aoRadius: VolumeParameter = new VolumeParameter(1);

    /** The second most important parameter for your ambient occlusion effect. 
     * Controls how fast the ambient occlusion fades away with distance in proportion to its radius. 
     * Defaults to 1, and behind-the-scenes, is a calculated as a ratio of your radius (0.2 * distanceFalloff is the size used for attenuation). 
     * Decreasing it reduces "haloing" artifacts and improves the accuracy of your occlusion, 
     * but making it too small makes the ambient occlusion disappear entirely.
     * @default 1
     */
    @serializable(VolumeParameter)
    falloff: VolumeParameter = new VolumeParameter(1);

    /** A purely artistic control for the intensity of the AO - runs the ao through the function pow(ao, intensity), 
     * which has the effect of darkening areas with more ambient occlusion. 
     * Useful to make the effect more pronounced. 
     * An intensity of 2 generally produces soft ambient occlusion that isn't too noticeable, 
     * whereas one of 5 produces heavily prominent ambient occlusion.
     * @default 1
     */
    @serializable(VolumeParameter)
    intensity: VolumeParameter = new VolumeParameter(1);

    /** The color of the ambient occlusion. By default, it is black, but it can be changed to any color 
     * to offer a crude approximation of global illumination. 
     * Recommended in scenes where bounced light has a uniform "color", 
     * for instance a scene that is predominantly lit by a blue sky. 
     * The color is expected to be in the sRGB color space, and is automatically converted to linear space for you. 
     * Keep the color pretty dark for sensible results.
     * @default new Color(0, 0, 0)
     */
    @serializable(VolumeParameter)
    color: VolumeParameter = new VolumeParameter(new Color(0, 0, 0));

    /** If you want the AO to calculate the radius based on screen space, you can do so by setting configuration.screenSpaceRadius to true. 
     * This is useful for scenes where the camera is moving across different scales a lot, 
     * or for scenes where the camera is very close to the objects.
     * @default false
     */
    @validate()
    @serializable()
    screenspaceRadius: boolean = false;

    /**
     * The quality of the ambient occlusion effect.
     * @default ScreenSpaceAmbientOcclusionN8QualityMode.Medium
     */
    @validate()
    @serializable()
    quality: ScreenSpaceAmbientOcclusionN8QualityMode = ScreenSpaceAmbientOcclusionN8QualityMode.Medium;

    private _ssao?: N8AOPostPass;

    onValidate(): void {
        if (this._ssao) {
            this._ssao.setQualityMode(ScreenSpaceAmbientOcclusionN8QualityMode[this.quality]);
            this._ssao.configuration.gammaCorrection = this.gammaCorrection;
            this._ssao.configuration.screenSpaceRadius = this.screenspaceRadius;

        }
    }

    onCreateEffect(): EffectProviderResult {

        const cam = this.context.mainCamera! as PerspectiveCamera;

        const ssao = this._ssao = new N8AOPostPass(
            this.context.scene,
            cam,
            this.context.domWidth,
            this.context.domHeight
        );
        const mode = ScreenSpaceAmbientOcclusionN8QualityMode[this.quality];
        ssao.setQualityMode(mode);

        ssao.configuration.gammaCorrection = this.gammaCorrection;

        ssao.configuration.screenSpaceRadius = this.screenspaceRadius;

        this.intensity.onValueChanged = newValue => {
            ssao.configuration.intensity = newValue;
        }
        this.falloff.onValueChanged = newValue => {
            ssao.configuration.distanceFalloff = newValue;
        }
        this.aoRadius.onValueChanged = newValue => {
            ssao.configuration.aoRadius = newValue;
        }
        this.color.onValueChanged = newValue => {
            if (!ssao.color) ssao.color = new Color();
            ssao.configuration.color.copy(newValue);
        }

        const arr = new Array();
        arr.push(ssao);
        return arr;
    }

}
registerCustomEffectType("ScreenSpaceAmbientOcclusionN8", ScreenSpaceAmbientOcclusionN8);