package org.sunflow.core.renderer;

import org.sunflow.core.Display;
import org.sunflow.core.ImageSampler;
import org.sunflow.core.IntersectionState;
import org.sunflow.core.Options;
import org.sunflow.core.Scene;
import org.sunflow.core.ShadingState;
import org.sunflow.image.Color;
import org.sunflow.system.Timer;
import org.sunflow.system.UI;
import org.sunflow.system.UI.Module;

public class SimpleRenderer implements ImageSampler {
    private Scene scene;
    private Display display;
    private int imageWidth, imageHeight;
    private int numBucketsX, numBucketsY;
    private int bucketCounter, numBuckets;

    public boolean prepare(Options options, Scene scene, int w, int h) {
        this.scene = scene;
        imageWidth = w;
        imageHeight = h;
        numBucketsX = (imageWidth + 31) >>> 5;
        numBucketsY = (imageHeight + 31) >>> 5;
        numBuckets = numBucketsX * numBucketsY;
        return true;
    }

    public void render(Display display) {
        this.display = display;
        display.imageBegin(imageWidth, imageHeight, 32);
        // set members variables
        bucketCounter = 0;
        // start task
        Timer timer = new Timer();
        timer.start();
        BucketThread[] renderThreads = new BucketThread[scene.getThreads()];
        for (int i = 0; i < renderThreads.length; i++) {
            renderThreads[i] = new BucketThread();
            renderThreads[i].start();
        }
        for (int i = 0; i < renderThreads.length; i++) {
            try {
                renderThreads[i].join();
            } catch (InterruptedException e) {
                UI.printError(Module.BCKT, "Bucket processing thread %d of %d was interrupted", i + 1, renderThreads.length);
            } finally {
                renderThreads[i].updateStats();
            }
        }
        timer.end();
        UI.printInfo(Module.BCKT, "Render time: %s", timer.toString());
        display.imageEnd();
    }

    private class BucketThread extends Thread {
        private final IntersectionState istate = new IntersectionState();

        @Override
        public void run() {
            while (true) {
                int bx, by;
                synchronized (SimpleRenderer.this) {
                    if (bucketCounter >= numBuckets)
                        return;
                    by = bucketCounter / numBucketsX;
                    bx = bucketCounter % numBucketsX;
                    bucketCounter++;
                }
                renderBucket(bx, by, istate);
            }
        }

        void updateStats() {
            scene.accumulateStats(istate);
        }
    }

    public void renderBucket(int bx, int by, IntersectionState istate) {
        // pixel sized extents
        int x0 = bx * 32;
        int y0 = by * 32;
        int bw = Math.min(32, imageWidth - x0);
        int bh = Math.min(32, imageHeight - y0);

        Color[] bucketRGB = new Color[bw * bh];
        float[] bucketAlpha = new float[bw * bh];

        for (int y = 0, i = 0; y < bh; y++) {
            for (int x = 0; x < bw; x++, i++) {
                ShadingState state = scene.getRadiance(istate, x0 + x, imageHeight - 1 - (y0 + y), 0.0, 0.0, 0.0, 0, 0, null);
                bucketRGB[i] = (state != null) ? state.getResult() : Color.BLACK;
                bucketAlpha[i] = (state != null) ? 1 : 0;
            }
        }
        // update pixels
        display.imageUpdate(x0, y0, bw, bh, bucketRGB, bucketAlpha);
    }
}