/***************************************************************************
 *   Copyright (C) 2009 by Paul Lutus                                      *
 *   lutusp@arachnoid.com                                                  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
package fftexplorer;

/*
 * DataProcessor.java
 *
 * Created on November 10, 2004, 3:11 PM
 */
/**
 *
 * @author  lutusp
 */
import java.io.*;
import java.util.*;
import javax.swing.*;


import javax.sound.sampled.*;

public final class SignalProcessor {

    FFTExplorer parent;
    FileInputStream fis;
    FFT fft;
    boolean inverse = false;
    boolean audioBusy = false;
    boolean playAudio;
    Thread graphicThread;
    boolean suspendSynth;
    boolean graphicThreadRunning = false;
    final double pi2 = Math.PI * 2.0;
    //double[] re,im,mag;
    Complex[] inData = null, outData = null;
    //long t = 0, tl = 0;
    int mode;
    final int AM_MODE = 0;
    final int FM_MODE = 1;
    final int SW_MODE = 2;
    final int TW_MODE = 3;
    final int ST_MODE = 4;
    double audioLevel = 8192.0;
    double audioFactor = 1.0 / audioLevel;
    SourceDataLine sourceLine = null;
    AudioFormat audioFormat;
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    byte[] bdata = new byte[4096];
    double tt = 0;
    double dt;
    double fm_integral = 0;
    int arraySize;

    /** Creates a new instance of DataProcessor */
    public SignalProcessor(FFTExplorer p) {
        parent = p;
        fft = new FFT();
    }

    public float getSampleRate() {
        return parent.arraySize;
    }

    public void initialize(boolean warm) {
        if (!warm) {
            playAudio = false;
        }
        if (warm && playAudio) {
            endPlayAudio();
            setupPlayAudio();
        }
        boolean wasRunning = graphicThreadRunning;
        if (graphicThreadRunning && inData.length != parent.arraySize) {
            stopSynthesis();
        }
        // an isolation measure for experiments
        // with fractional seconds
        arraySize = parent.arraySize;
        if (inData == null || inData.length != arraySize) {
            fft.initialize(arraySize, inverse);
            inData = new Complex[arraySize];
            outData = new Complex[arraySize];
            for (int i = 0; i < arraySize; i++) {
                inData[i] = new Complex();
                outData[i] = new Complex();
            }
        }
        dt = 1.0 / parent.sampleRate;
        setMode();
        if (wasRunning) {
            startSynthesis();
        }
    }

    public void setMode() {
        if (parent.sv_amRadioButton.isSelected()) {
            mode = AM_MODE;
        } else if (parent.sv_fmRadioButton.isSelected()) {
            mode = FM_MODE;
        } else if (parent.sv_sqwRadioButton.isSelected()) {
            mode = SW_MODE;
        } else if (parent.sv_twRadioButton.isSelected()) {
            mode = TW_MODE;
        } else if (parent.sv_stRadioButton.isSelected()) {
            mode = ST_MODE;
        }
    }

    public void suspend(boolean susp) {
        suspendSynth = susp;
    }

    public void startSynthesis() {
        parent.audioCapture(false);
        parent.streamMP3(false);
        parent.setSampleRate();
        stopSynthesis();
        initialize(false);
        playAudio = parent.sv_soundCheckBox.isSelected();
        setupPlayAudio();
        synthesisThread();
    }

    public void synthDisplay(boolean display) {
        if (display) {
            startSynthesis();
        } else {
            stopSynthesis();
        }
        parent.startSynthButton.setEnabled(!graphicThreadRunning);
        parent.stopSynthButton.setEnabled(graphicThreadRunning);
    }

    private void synthesisThread() {
        suspendSynth = false;
        graphicThreadRunning = true;
        graphicThread = new Thread() {

            @Override
            public void run() {
                while (graphicThreadRunning) {
                    if (suspendSynth) {
                        try {
                            Thread.sleep(250);
                        } catch (Exception e) {
                        }
                    } else {
                        process();
                        parent.repaint();
                    }
                }
            }
        };
        graphicThread.start();
    }

    public void stopSynthesis() {
        endPlayAudio();
        try {
            if (graphicThreadRunning) {
                graphicThreadRunning = false;
                graphicThread.join();
            }
        } catch (Exception e) {
        }
        parent.setSampleRate();
    }

    public boolean processing() {
        return graphicThreadRunning;
    }

    public void process() {
        createData();
        process2();
    }

    private void process2() {
        parent.timeControl.setData(inData);
        playAudio(inData);
        processData();
        parent.freqControl.setData(outData);
    /*try {
    Thread.sleep(10);
    } catch (Exception e) {
    }*/
    //parent.p("proocess " + new Date());
    }

    public void readAudio(ByteArrayOutputStream stream) {
        if (!audioBusy) {
            audioBusy = true;
            int blen = arraySize * 2;
            int top = stream.size();
            if (top > blen) {
                Complex[] ta = inData;
                Complex[] tb = fft.inputArray();
                byte[] array = stream.toByteArray();
                int v;
                double dv;
                int n = 0;
                for (int i = 0; i < blen; i += 2) {
                    v = array[i + 1] << 8;
                    v |= array[i] & 0xff;
                    dv = v * audioFactor;
                    ta[n].re = dv;
                    tb[n].re = dv;
                    n++;
                }
                stream.reset();
                readAudio2();
            } else {
                audioBusy = false;
            }
        }
    }

    public void readAudio(Vector<Float> vf) {
        if (!audioBusy) {
            audioBusy = true;
            Float[] data = (Float[]) vf.toArray(new Float[]{});
            int blen = arraySize;
            int top = data.length;
            if (top > blen) {
                Complex[] ta = inData;
                Complex[] tb = fft.inputArray();
                double v;
                int n = 0;
                for (int i = 0; i < blen; i++) {
                    v = data[i] * audioFactor;
                    ta[n].re = v;
                    tb[n].re = v;
                    n++;
                }
                vf.clear();
                readAudio2();
            } else {
                audioBusy = false;
            }
        }
    }

    private void readAudio2() {
        SwingUtilities.invokeLater(
                new Runnable() {

                    public void run() {
                        process2();
                        audioBusy = false;
                    }
                });
    }

    private AudioFormat createAudioFormat() {
        //float sampleRate = 4000.0F;
        //sampleRate = 44100.0F;
        //8000,11025,16000,22050,44100
        int sampleSizeInBits = 16;
        //8,16
        int channels = 1;
        //1,2
        boolean signed = true;
        //true,false
        boolean bigEndian = false;
        //true,false
        return new AudioFormat(
                AudioFormat.Encoding.PCM_SIGNED,
                getSampleRate(),
                sampleSizeInBits,
                channels,
                2 * channels,
                //signed,
                getSampleRate(),
                bigEndian);
    }

    private void setupPlayAudio() {
        if (playAudio) {
            try {
                audioFormat = createAudioFormat();
                DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
                sourceLine = (SourceDataLine) AudioSystem.getLine(info);
                sourceLine.open(audioFormat);
                sourceLine.start();
                parent.setArraySizeLabelColor(true);
            } catch (Exception e) {
                System.out.println("setupPlayAudio: " + e);
                parent.beep();
                parent.setArraySizeLabelColor(false);
            }
        } else {
            sourceLine = null;
        }
    }

    public void flushAudio() {
        if (sourceLine != null) {
            sourceLine.flush();
        }
    }

    private void endPlayAudio() {
        if (sourceLine != null) {
            sourceLine.flush();
            sourceLine.stop();
            sourceLine.close();
            sourceLine =
                    null;
            bos.reset();
        }
        parent.setArraySizeLabelColor(true);
    }

    private void audioWrite() {
        int canwrite = inData.length * 2;
        if (sourceLine != null && bos.size() == canwrite) {
            byte[] data = bos.toByteArray();
            try {
                sourceLine.write(data, 0, data.length);
            } catch (Exception e) {
                System.out.println("audioWrite: " + e);
            }

            bos.reset();
        }

    }

    private void playAudio(Complex[] data) {
        if (sourceLine != null) {
            int iv;
            double dv;
            double lvl = audioLevel * 0.5;
            try {
                for (int i = 0; i <
                        data.length; i++) {
                    dv = data[i].re * lvl;
                    dv = Math.min(audioLevel, dv);
                    dv = Math.max(-audioLevel, dv);
                    iv = (int) dv;
                    bos.write((iv & 0xff));
                    bos.write((iv >> 8));
                }
                audioWrite();
            } catch (Exception e) {
                System.out.println("playAudio " + e);
            }
        }
    }

    private double randomNoiseSignal() {
        return (Math.random() - 0.5) * parent.noiseLevel;
    }

// create AM test signal
    private void createAMSignal(Complex[] ta, Complex[] tb) {
        double v;
        for (int t = 0; t < arraySize; t++) {
            tt += dt;
            v = Math.sin(pi2 * parent.carrier * tt) * (1.0 + (Math.sin(pi2 * parent.modFreq * tt) * parent.modLevel));
            v += randomNoiseSignal();
            ta[t].re = v;
            tb[t].re = v;
        }

    }

    private void createFMSignal(Complex[] ta, Complex[] tb) {
        double v;
        for (int t = 0; t < arraySize; t++) {
            tt += dt;
            fm_integral += Math.sin(pi2 * parent.modFreq * tt);
            v = Math.sin(2 * Math.PI * parent.carrier * (tt + fm_integral * dt * parent.modLevel));
            v += randomNoiseSignal();
            ta[t].re = v;
            tb[t].re = v;
        }

    }

    // create squarewave test signal
    private void createSQSignal(Complex[] ta, Complex[] tb) {
        double v;
        for (int t = 0; t < arraySize; t++) {
            tt += dt;
            v = Math.sin(pi2 * parent.carrier * tt);
            v = (v < 0.0) ? -1.0 : (v > 0.0) ? 1.0 : 0.0;
            v += randomNoiseSignal();
            ta[t].re = v;
            tb[t].re = v;
        }

    }

    // create squarewave test signal
    private void createTWSignal(Complex[] ta, Complex[] tb) {
        double v;
        for (int t = 0; t < arraySize; t++) {
            tt += dt;
            v = ((4.0 * parent.carrier * tt) % 4.0);
            if (v >= 1.0 && v <= 3) {
                v = 2 - v;
            } else if (v > 3) {
                v = v - 4;
            }
            v += randomNoiseSignal();
            ta[t].re = v;
            tb[t].re = v;
        }

    }

    // create squarewave test signal
    private void createSTSignal(Complex[] ta, Complex[] tb) {
        double v;
        for (int t = 0; t < arraySize; t++) {
            tt += dt;
            v = ((2.0 * parent.carrier * tt) % 2.0) - 1.0;
            v += randomNoiseSignal();
            ta[t].re = v;
            tb[t].re = v;
        }

    }

    private void createData() {
        switch (mode) {
            case AM_MODE:
                createAMSignal(inData, fft.inputArray());
                break;

            case FM_MODE:
                createFMSignal(inData, fft.inputArray());
                break;

            case SW_MODE:
                createSQSignal(inData, fft.inputArray());
                break;

            case TW_MODE:
                createTWSignal(inData, fft.inputArray());
                break;

            case ST_MODE:
                createSTSignal(inData, fft.inputArray());
                break;
        }
    }

    private void processData() {
        fft.fft1();
        Complex[] fftData = fft.outputArray();
        for (int i = 0; i < outData.length; i++) {
            outData[i].assign(fftData[i]);
        }
    }

    public Complex[] getInputData() {
        return inData;
    }

    public Complex[] getOutputData() {
        return fft.outputArray();
    }
}