/*
 * HalfNES by Andrew Hoffman
 * Licensed under the GNU GPL Version 3. See LICENSE file
 */
package com.grapeshot.halfnes.video;

import java.awt.image.BufferedImage;
import com.grapeshot.halfnes.utils;
import com.grapeshot.halfnes.utils;

/**
 *
 * @author Andrew
 */
//Direct port of Bisqwit's code on the wiki. (probably just as slow, we'll see.)
//yep, it's WORSE
//the expensive part is the Math function calls of course
public class AltNTSCRenderer extends Renderer {

    public AltNTSCRenderer() {
        frame_width = 604;
        init_images();
//        for (int i = 0; i < 12; ++i) {
//            System.err.println(inColorPhase( 3, i));
//        };
    }
    private final int[] frame = new int[width * 240];
    private int frame_ptr = 0;
    private int frame_ctr;
    private int phase;
    private final static double attenuation = 0.746;
    private final static double[] levels = {
        -0.117f, 0.000f, 0.308f, 0.715f,
        0.397f, 0.681f, 1.0f, 1.0f
    //0x00    0x10    0x20    0x30
    };
    private final static int SAMPLESPERPIXEL = 8;

    @Override
    public BufferedImage render(int[] nespixels, int[] bgcolors, boolean dotcrawl) {
        ++frame_ctr;
        if ((frame_ctr & 1) == 0) {
            phase = 0;
        } else {
            phase = 6;
        }
        for (int i = 0; i < 240; ++i) {
            double phi = (phase + 3.9 - 0.3) % 12;
            for (int j = 0; j < 256; ++j) {
                ntsc_render(nespixels[i * 256 + j]);
            }
            ntsc_decode(phi);
            ntsc_buf_ptr = 0;
        }
        frame_ptr = 0;
        return getBufferedImage(frame);
    }

    private void ntsc_render(int pixel) {
        int color = pixel & 0xf;
        int level = (pixel >> 4) & 3;
        int emphasis = (pixel >> 6);
        if (color > 13) {
            level = 1;
        }
        double low = levels[level];
        double high = levels[4 + level];
        if (color == 0) {
            low = high;
        } else if (color > 12) {
            high = low;
        }
        for (int i = 0; i < SAMPLESPERPIXEL; ++i, ++phase) {
            double signal = inColorPhase(color, phase) ? high : low;
            if (emphasis != 0) {
                if ((((emphasis & (utils.BIT0)) != 0) && inColorPhase(0, phase))
                        || (((emphasis & (utils.BIT1)) != 0) && inColorPhase(4, phase))
                        || (((emphasis & (utils.BIT2)) != 0) && inColorPhase(8, phase))) {
                    signal *= attenuation;
                }
            }
            signal_levels[ntsc_buf_ptr++] = signal;
        }
    }

    private static boolean inColorPhase(final int color, final int phase) {
        return (color + phase) % 12 < 6;
    }
    private final double[] signal_levels = new double[256 * SAMPLESPERPIXEL];
    private int ntsc_buf_ptr = 0;
    private final static int width = 604;

    private void ntsc_decode(final double phase) {
        for (int x = 0; x < width; ++x) {
            int center = x * (256 * SAMPLESPERPIXEL) / width + 0;
            int begin = center - 6;
            if (begin < 0) {
                begin = 0;
            }
            int end = center + 6;
            if (end > 256 * SAMPLESPERPIXEL) {
                end = (256 * SAMPLESPERPIXEL);
            }
            double y = 0, i = 0, q = 0;
            for (int p = begin; p < end; ++p) {
                double level = signal_levels[p] / 12.;
                y += level;
                i += level * Math.cos(Math.PI * (phase + p) / 6.);
                q += level * Math.sin(Math.PI * (phase + p) / 6.);
            }
            render_pixel(y, i, q);
        }
    }

    private void render_pixel(final double y, final double i, final double q) {

        final int rgb = 0xff000000
                | 0x10000 * clamp(255.95 * gammafix(y + 0.946882f * i + 0.623557f * q))
                + 0x00100 * clamp(255.95 * gammafix(y + -0.274788f * i + -0.635691f * q))
                + 0x00001 * clamp(255.95 * gammafix(y + -1.108545f * i + 1.709007f * q));
        frame[frame_ptr++] = rgb;
    }

    public static int clamp(final double a) {
        return (int) ((a < 0) ? 0 : ((a > 255) ? 255 : a));
    }

    public static double gammafix(double luma) {
        final float gamma = 2.0f; // Assumed display gamma
        return luma <= 0.f ? 0.f : Math.pow(luma, 2.2f / gamma);
    }
}
