package ucesoft.cbm.peripheral.sid.resid4;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

class SIDfilter {
    private boolean V3OFF;
    private boolean enabled;
    private int fc;
    private int res;
    private int filt;
    private boolean voice3off;
    private int hp_bp_lp;
    int vol;
    private int mixer_DC;
    private int Vhp;
    private int Vbp;
    private int Vlp;
    private int Vnf;
    private int DLthreshold;
    private int DLsteepness;
    private int DHthreshold;
    private int DHsteepness;
    private int DLlp;
    private int DLbp;
    private int DLhp;
    private int DHlp;
    private int DHbp;
    private int DHhp;
    private int w0;
    private int w0_ceil_1;
    private int _1024_div_Q;
    private final int[] f0_6581;
    private final int[] f0_8580;
    private int[] f0;
    static final int[][] f0_points_6581;
    static final int[][] f0_points_8580;
    private static final int[][] _1024_div_Q_table;
    private int sid_model;

    public void clock(final int voice1, final int voice2, final int voice3) {
        clock(voice1, voice2, voice3, 0);
    }

    public void clock(final int voice1, final int voice2, int voice3, final int ext_in) {
        if (voice3off) {
            voice3 = 0;
        }
        if (!enabled) {
            Vnf = voice1 + voice2 + voice3 + ext_in;
            final int vhp = 0;
            Vlp = vhp;
            Vbp = vhp;
            Vhp = vhp;
            return;
        }
        Vnf = 0;
        int Vi = 0;
        switch (filt) {
            default: {
                Vi = 0;
                Vnf = voice1 + voice2 + voice3 + ext_in;
                break;
            }
            case 1: {
                Vi = voice1;
                Vnf = voice2 + voice3 + ext_in;
                break;
            }
            case 2: {
                Vi = voice2;
                Vnf = voice1 + voice3 + ext_in;
                break;
            }
            case 3: {
                Vi = voice1 + voice2;
                Vnf = voice3 + ext_in;
                break;
            }
            case 4: {
                Vi = voice3;
                Vnf = voice1 + voice2 + ext_in;
                break;
            }
            case 5: {
                Vi = voice1 + voice3;
                Vnf = voice2 + ext_in;
                break;
            }
            case 6: {
                Vi = voice2 + voice3;
                Vnf = voice1 + ext_in;
                break;
            }
            case 7: {
                Vi = voice1 + voice2 + voice3;
                Vnf = ext_in;
                break;
            }
            case 8: {
                Vi = ext_in;
                Vnf = voice1 + voice2 + voice3;
                break;
            }
            case 9: {
                Vi = voice1 + ext_in;
                Vnf = voice2 + voice3;
                break;
            }
            case 10: {
                Vi = voice2 + ext_in;
                Vnf = voice1 + voice3;
                break;
            }
            case 11: {
                Vi = voice1 + voice2 + ext_in;
                Vnf = voice3;
                break;
            }
            case 12: {
                Vi = voice3 + ext_in;
                Vnf = voice1 + voice2;
                break;
            }
            case 13: {
                Vi = voice1 + voice3 + ext_in;
                Vnf = voice2;
                break;
            }
            case 14: {
                Vi = voice2 + voice3 + ext_in;
                Vnf = voice1;
                break;
            }
            case 15: {
                Vi = voice1 + voice2 + voice3 + ext_in;
                Vnf = 0;
                break;
            }
        }
        Vi >>= 7;
        Vnf >>= 7;
        int Vi_peak_bp = (Vlp * DHlp + Vbp * DHbp + Vhp * DHhp >> 8) + Vi;
        if (Vi_peak_bp < DHthreshold) {
            Vi_peak_bp = DHthreshold;
        }
        int Vi_peak_lp = (Vlp * DLlp + Vbp * DLbp + Vhp * DLhp >> 8) + Vi;
        if (Vi_peak_lp < DLthreshold) {
            Vi_peak_lp = DLthreshold;
        }
        int w0_eff_bp = w0 + w0 * (Vi_peak_bp - DHthreshold >> 4) / DHsteepness;
        int w0_eff_lp = w0 + w0 * (Vi_peak_lp - DLthreshold >> 4) / DLsteepness;
        if (w0_eff_bp > w0_ceil_1) {
            w0_eff_bp = w0_ceil_1;
        }
        if (w0_eff_lp > w0_ceil_1) {
            w0_eff_lp = w0_ceil_1;
        }
        Vhp = (Vbp * _1024_div_Q >> 10) - Vlp - Vi;
        Vlp -= w0_eff_lp * Vbp >> 20;
        Vbp -= w0_eff_bp * Vhp >> 20;
    }

    public int output() {
        if (!enabled) {
            return (Vnf + mixer_DC) * vol;
        }
        int Vf = 0;
        switch (hp_bp_lp) {
            default: {
                Vf = 0;
                break;
            }
            case 1: {
                Vf = Vlp;
                break;
            }
            case 2: {
                Vf = Vbp;
                break;
            }
            case 3: {
                Vf = Vlp + Vbp;
                break;
            }
            case 4: {
                Vf = Vhp;
                break;
            }
            case 5: {
                Vf = Vlp + Vhp;
                break;
            }
            case 6: {
                Vf = Vbp + Vhp;
                break;
            }
            case 7: {
                Vf = Vlp + Vbp + Vhp;
                break;
            }
        }
        return (Vnf + Vf + mixer_DC) * vol;
    }

    public SIDfilter() {
        V3OFF = false;
        f0_6581 = new int[2048];
        f0_8580 = new int[2048];
        fc = 0;
        res = 0;
        filt = 0;
        voice3off = false;
        hp_bp_lp = 0;
        vol = 0;
        Vhp = 0;
        Vbp = 0;
        Vlp = 0;
        Vnf = 0;
        enable_filter(true);
        DAC.interpolate(SIDfilter.f0_points_6581, 0, SIDfilter.f0_points_6581.length - 1, f0_6581, 1.0);
        DAC.interpolate(SIDfilter.f0_points_8580, 0, SIDfilter.f0_points_8580.length - 1, f0_8580, 1.0);
        set_chip_model(0);
        set_distortion_properties(999999, 999999, 0, 0, 0, 999999, 999999, 0, 0, 0);
    }

    private void enable_filter(final boolean enable) {
        enabled = enable;
    }

    public void set_chip_model(final int model) {
        sid_model = model;
        if (model == 0) {
            mixer_DC = -454;
            f0 = f0_6581;
        } else {
            mixer_DC = 0;
            f0 = f0_8580;
        }
        set_w0();
        set_Q();
    }

    private void set_distortion_properties(final int Lthreshold, int Lsteepness, final int Llp, final int Lbp, final int Lhp, final int Hthreshold, int Hsteepness, final int Hlp, final int Hbp, final int Hhp) {
        DLthreshold = Lthreshold;
        if (Lsteepness < 16) {
            Lsteepness = 16;
        }
        DLsteepness = Lsteepness >> 4;
        DLlp = Llp;
        DLbp = Lbp;
        DLhp = Lhp;
        DHthreshold = Hthreshold;
        if (Hsteepness < 16) {
            Hsteepness = 16;
        }
        DHsteepness = Hsteepness >> 4;
        DHlp = Hlp;
        DHbp = Hbp;
        DHhp = Hhp;
    }

    public void reset() {
        fc = 0;
        res = 0;
        filt = 0;
        voice3off = false;
        hp_bp_lp = 0;
        vol = 0;
        Vhp = 0;
        Vbp = 0;
        Vlp = 0;
        Vnf = 0;
        set_w0();
        set_Q();
    }

    public void writeFC_LO(final int fc_lo) {
        fc = ((fc & 0x7F8) | (fc_lo & 0x7));
        set_w0();
    }

    public void writeFC_HI(final int fc_hi) {
        fc = ((fc_hi << 3 & 0x7F8) | (fc & 0x7));
        set_w0();
    }

    public void writeRES_FILT(final int res_filt) {
        res = (res_filt >> 4 & 0xF);
        set_Q();
        filt = (res_filt & 0xF);
        voice3off = (V3OFF && (filt & 0x4) == 0x0);
    }

    public void writeMODE_VOL(final int mode_vol) {
        V3OFF = ((mode_vol & 0x80) == 0x80);
        voice3off = (V3OFF && (filt & 0x4) == 0x0);
        hp_bp_lp = (mode_vol >> 4 & 0x7);
        vol = (mode_vol & 0xF);
    }

    private void set_w0() {
        w0 = (int) (6.283185307179586 * f0[fc] * 1.048576);
        final int w0_max_1 = 118591;
        w0_ceil_1 = ((w0 <= w0_max_1) ? w0 : w0_max_1);
    }

    private void set_Q() {
        _1024_div_Q = _1024_div_Q_table[sid_model][res];
    }

    void saveState(ObjectOutputStream out) throws IOException {
        out.writeBoolean(V3OFF);
        out.writeBoolean(enabled);
        out.writeInt(fc);
        out.writeInt(res);
        out.writeInt(filt);
        out.writeBoolean(voice3off);
        out.writeInt(hp_bp_lp);
        out.writeInt(vol);
        out.writeInt(mixer_DC);
        out.writeInt(Vhp);
        out.writeInt(Vbp);
        out.writeInt(Vnf);
        out.writeInt(DLthreshold);
        out.writeInt(DLsteepness);
        out.writeInt(DHthreshold);
        out.writeInt(DHsteepness);
        out.writeInt(DLlp);
        out.writeInt(DLbp);
        out.writeInt(DLhp);
        out.writeInt(DHlp);
        out.writeInt(DHbp);
        out.writeInt(DHhp);
        out.writeInt(w0);
        out.writeInt(w0_ceil_1);
        out.writeInt(_1024_div_Q);
        out.writeInt(sid_model);
    }

    void loadState(ObjectInputStream in) throws IOException {
        V3OFF = in.readBoolean();
        enabled = in.readBoolean();
        fc = in.readInt();
        res = in.readInt();
        filt = in.readInt();
        voice3off = in.readBoolean();
        hp_bp_lp = in.readInt();
        vol = in.readInt();
        mixer_DC = in.readInt();
        Vhp = in.readInt();
        Vbp = in.readInt();
        Vnf = in.readInt();
        DLthreshold = in.readInt();
        DLsteepness = in.readInt();
        DHthreshold = in.readInt();
        DHsteepness = in.readInt();
        DLlp = in.readInt();
        DLbp = in.readInt();
        DLhp = in.readInt();
        DHlp = in.readInt();
        DHbp = in.readInt();
        DHhp = in.readInt();
        w0 = in.readInt();
        w0_ceil_1 = in.readInt();
        _1024_div_Q = in.readInt();
        sid_model = in.readInt();
    }

    static {
        f0_points_6581 = new int[][]{{0, 220}, {0, 220}, {128, 230}, {256, 250}, {384, 300}, {512, 420}, {640, 780}, {768, 1600}, {832, 2300}, {896, 3200}, {960, 4300}, {992, 5000}, {1008, 5400}, {1016, 5700}, {1023, 6000}, {1023, 6000}, {1024, 4600}, {1024, 4600}, {1032, 4800}, {1056, 5300}, {1088, 6000}, {1120, 6600}, {1152, 7200}, {1280, 9500}, {1408, 12000}, {1536, 14500}, {1664, 16000}, {1792, 17100}, {1920, 17700}, {2047, 18000}, {2047, 18000}};
        f0_points_8580 = new int[][]{{0, 0}, {0, 0}, {128, 800}, {256, 1600}, {384, 2500}, {512, 3300}, {640, 4100}, {768, 4800}, {896, 5600}, {1024, 6500}, {1152, 7500}, {1280, 8400}, {1408, 9200}, {1536, 9800}, {1664, 10500}, {1792, 11000}, {1920, 11700}, {2047, 12500}, {2047, 12500}};
        _1024_div_Q_table = new int[2][16];
        for (int res = 0; res < 16; ++res) {
            SIDfilter._1024_div_Q_table[0][res] = (int) (1024.0 / (0.707 + 1.0 * res / 15.0));
            SIDfilter._1024_div_Q_table[1][res] = (int) (1024.0 * Math.pow(2.0, (4 - res) / 8.0) + 0.5);
        }
    }
}
