package turgen.generator;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import turgen.Utils;


public class WaveSignalWriter implements SignalWriter {

    private BufferedOutputStream waveStream;
    private final int bitsPerSample;
    private final int numChannels;
    private String outputFile;
    private int terminationSignalCounter;
    private byte[] terminationSignal;
    
    /*WAVE header blob*/
    private static final int[] waveHeader= {
     0x52,0x49,0x46,0x46,0x24,0x00,0x00,0x00,0x57,0x41,0x56,0x45,0x66,0x6d,0x74,0x20,0x10,0x00,0x00,0x00,
     0x01,0x00,0x02,0x00,0x44,0xac,0x00,0x00,0x10,0xb1,0x02,0x00,0x04,0x00,0x10,0x00,0x64,0x61,0x74,0x61,
     0x00,0x00,0x00,0x00
    };
    
    public WaveSignalWriter(int bitsPerSample,int numChannels) {
        
        this.bitsPerSample=bitsPerSample;
        this.numChannels=numChannels;
        terminationSignalCounter=-1;
        terminationSignal=null;
    }
    
    
    public String prepare(String outputFile) {
        
        this.outputFile=outputFile;
        
        try {
            waveStream = new BufferedOutputStream(new FileOutputStream(outputFile));

            /*WAVE HEADER*/
            for (int i = 0; i < waveHeader.length; i++) {
                waveStream.write(waveHeader[i]);
            }

            waveStream.flush();

        }
        catch (IOException e) {

            String wf = outputFile;
            if (outputFile.trim().equals("") == true) {
                wf = "|No name|";
            }
            return Utils.getTitledExceptionMessage("Unable to write to this wave file: " + wf, e);
        }
        
        return null;
    }

    public void writeInitialSignal(byte[] signal) throws Exception {
        /*Nothing to be here*/
    }

    public boolean writeTerminationSignal() throws Exception {
        if (terminationSignal==null || terminationSignalCounter<1) return false;
        waveStream.write(terminationSignal,0,terminationSignal.length);
        terminationSignalCounter--;
        return true;
    }

    public void write(byte[] signal) throws Exception {
        waveStream.write(signal, 0, signal.length);
    }

    public void prepareForClose() throws Exception {
        waveStream.flush();
        waveStream.close();
    }

    public void close() throws Exception {
        /*Create real WAVE file*/
                RandomAccessFile raf = new RandomAccessFile(outputFile, "rw");

                /*Setup WAVE chunk size*/
                raf.seek(0x4);
                int count = (int) raf.length() - (int) waveHeader.length + 36;
                int[] lb = new int[4];
                int k = (count & 0xFF000000) >> 24;
                lb[0] = k;
                k = (count & 0x00FF0000) >> 16;
                lb[1] = k;
                k = (count & 0x0000FF00) >> 8;
                lb[2] = k;
                k = (count & 0x000000FF);
                lb[3] = k;

                for (int i = 3; i > -1; i--) {
                    raf.write(lb[i]);
                }

                /*Setup size of subchunk*/
                raf.seek(0x28);
                count = (int) raf.length() - (int) waveHeader.length;

                lb = new int[4];
                k = (count & 0xFF000000) >> 24;
                lb[0] = k;
                k = (count & 0x00FF0000) >> 16;
                lb[1] = k;
                k = (count & 0x0000FF00) >> 8;
                lb[2] = k;
                k = (count & 0x000000FF);
                lb[3] = k;

                for (int i = 3; i > -1; i--) {
                    raf.write(lb[i]);
                }

                /*Sampling rate*/
                raf.seek(24L);
                raf.write((44100 & 0x000000FF));
                raf.write((44100 & 0x0000FF00) >> 8);
                raf.write((44100 & 0x00FF0000) >> 16);
                raf.write((44100 & 0xFF000000) >> 24);

                /*Bits per sample*/
                raf.seek(34L);
                raf.write((bitsPerSample & 0x000000FF));
                raf.write((bitsPerSample & 0x0000FF00) >> 8);

                /*Block alignment*/
                int ba = numChannels * bitsPerSample / 8;
                raf.seek(32L);
                raf.write((ba & 0x000000FF));
                raf.write((ba & 0x0000FF00) >> 8);

                /*Number of channels*/
                raf.seek(22L);
                raf.write((numChannels & 0x000000FF));
                raf.write((numChannels & 0x0000FF00) >> 8);

                /*Bytes per sample*/
                int br = 44100 * numChannels * bitsPerSample / 8;
                raf.seek(28L);
                raf.write((br & 0x000000FF));
                raf.write((br & 0x0000FF00) >> 8);
                raf.write((br & 0x00FF0000) >> 16);
                raf.write((br & 0xFF000000) >> 24);

                /*Done, header is correct*/
                raf.close();
        
    }

    public void flush() throws Exception {
        
    }

    public void prepareForTerminationSignal(byte[] signal) {
        if (signal==null) return;
        
        if (signal.length==0) {
            terminationSignalCounter=0;
        }
        else {
            terminationSignalCounter=((5*44100*numChannels*bitsPerSample)/8)/signal.length;
        }
        terminationSignal=signal;
    }

}
