package org.sunflow.core.light;

import org.sunflow.SunflowAPI;
import org.sunflow.core.Instance;
import org.sunflow.core.LightSample;
import org.sunflow.core.LightSource;
import org.sunflow.core.ParameterList;
import org.sunflow.core.Ray;
import org.sunflow.core.ShadingState;
import org.sunflow.image.Color;
import org.sunflow.math.OrthoNormalBasis;
import org.sunflow.math.Point3;
import org.sunflow.math.Vector3;

public class DirectionalSpotlight implements LightSource {
    private Point3 src;
    private Vector3 dir;
    private OrthoNormalBasis basis;
    private float r, r2;
    private Color radiance;

    public DirectionalSpotlight() {
        src = new Point3(0, 0, 0);
        dir = new Vector3(0, 0, -1);
        dir.normalize();
        basis = OrthoNormalBasis.makeFromW(dir);
        r = 1;
        r2 = r * r;
        radiance = Color.WHITE;
    }

    public boolean update(ParameterList pl, SunflowAPI api) {
        src = pl.getPoint("source", src);
        dir = pl.getVector("dir", dir);
        dir.normalize();
        r = pl.getFloat("radius", r);
        basis = OrthoNormalBasis.makeFromW(dir);
        r2 = r * r;
        radiance = pl.getColor("radiance", radiance);
        return true;
    }

    public int getNumSamples() {
        return 1;
    }

    public int getLowSamples() {
        return 1;
    }

    public void getSamples(ShadingState state) {
        if (Vector3.dot(dir, state.getGeoNormal()) < 0 && Vector3.dot(dir, state.getNormal()) < 0) {
            // project point onto source plane
            float x = state.getPoint().x - src.x;
            float y = state.getPoint().y - src.y;
            float z = state.getPoint().z - src.z;
            float t = ((x * dir.x) + (y * dir.y) + (z * dir.z));
            if (t >= 0.0) {
                x -= (t * dir.x);
                y -= (t * dir.y);
                z -= (t * dir.z);
                if (((x * x) + (y * y) + (z * z)) <= r2) {
                    Point3 p = new Point3();
                    p.x = src.x + x;
                    p.y = src.y + y;
                    p.z = src.z + z;
                    LightSample dest = new LightSample();
                    dest.setShadowRay(new Ray(state.getPoint(), p));
                    dest.setRadiance(radiance, radiance);
                    dest.traceShadow(state);
                    state.addSample(dest);
                }
            }
        }
    }

    public void getPhoton(double randX1, double randY1, double randX2, double randY2, Point3 p, Vector3 dir, Color power) {
        float phi = (float) (2 * Math.PI * randX1);
        float s = (float) Math.sqrt(1.0f - randY1);
        dir.x = r * (float) Math.cos(phi) * s;
        dir.y = r * (float) Math.sin(phi) * s;
        dir.z = 0;
        basis.transform(dir);
        Point3.add(src, dir, p);
        dir.set(this.dir);
        power.set(radiance).mul((float) Math.PI * r2);
    }

    public float getPower() {
        return radiance.copy().mul((float) Math.PI * r2).getLuminance();
    }

    public Instance createInstance() {
        return null;
    }
}