/*
 * ******************************************************************************
 * Copyright (C) 2015-2019 Dennis Sheirer
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>
 * *****************************************************************************
 */

package jmbe.codec.ambe;

import jmbe.codec.oscillator.Oscillator;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * Tone Generator
 */
public class ToneGenerator
{
    private static final double SAMPLE_RATE = 8000.0;
    private static final int SAMPLE_COUNT = 160;  //20ms of samples at 8000 Hz
    private static final float TWO_CHANNEL_GAIN_REDUCTION = 0.5f;

    private Oscillator mOscillator1 = new Oscillator(0.0, SAMPLE_RATE);
    private Oscillator mOscillator2 = new Oscillator(0.0, SAMPLE_RATE);

    /**
     * Constructs an instance
     */
    public ToneGenerator()
    {
    }

    /**
     * Generates 20 ms of PCM audio samples at 8000Hz sample rate using the specified frequency and gain parameters
     *
     * @param toneParameters containing frequency(s) and amplitude
     * @return pcm audio samples
     */
    public float[] generate(ToneParameters toneParameters)
    {
        if(!toneParameters.isValidTone())
        {
            throw new IllegalArgumentException("Cannot generate tone audio - INVALID tone");
        }

        Tone tone = toneParameters.getTone();
        float gain = ((float)toneParameters.getAmplitude() / 127.0f);

        if(tone.hasFrequency2())
        {
            gain *= TWO_CHANNEL_GAIN_REDUCTION;

            mOscillator1.setFrequency(tone.getFrequency1());
            mOscillator2.setFrequency(tone.getFrequency2());

            float[] samples = mOscillator1.generate(SAMPLE_COUNT, gain);
            float[] samples2 = mOscillator2.generate(SAMPLE_COUNT, gain);

            for(int x = 0; x < SAMPLE_COUNT; x++)
            {
                samples[x] += samples2[x];
            }

            return samples;
        }
        else
        {
            mOscillator1.setFrequency(tone.getFrequency1());
            return mOscillator1.generate(SAMPLE_COUNT, gain);
        }
    }

    /**
     * Test harness
     * @param args not used
     */
    public static void main(String[] args)
    {
        ToneGenerator toneGenerator = new ToneGenerator();

        AudioFormat audioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
            8000.0f, 16, 1, 2, 8000.0f, false);
        DataLine.Info datalineinfo = new DataLine.Info(SourceDataLine.class, audioFormat);

        if(AudioSystem.isLineSupported(datalineinfo))
        {
            try
            {
                SourceDataLine sourceDataLine = AudioSystem.getSourceDataLine(audioFormat);
                sourceDataLine.open(audioFormat);

                for(Tone tone: Tone.DTMF_TONES)
//                for(Tone tone: Tone.KNOX_TONES)
//                for(Tone tone: Tone.CALL_PROGRESS_TONES)
//                for(Tone tone: Tone.DISCRETE_TONES)
//                for(Tone tone: Tone.values())
                {
                    for(int x = 0; x < 128; x++) //Amplitude levels 0 - 127
                    {
                        System.out.print("\rTONE [" + tone.name() + "]: " + tone + " " + tone.getFrequency1() +
                            (tone.hasFrequency2() ? " PLUS " + tone.getFrequency2() : "") + " AMPLITUDE:" + x);

                        ToneParameters toneParameters = new ToneParameters(tone, x);

                        float[] samples = toneGenerator.generate(toneParameters);

                        ByteBuffer converted = ByteBuffer.allocate(samples.length * 2);
                        converted.order(ByteOrder.LITTLE_ENDIAN);

                        for(float sample : samples)
                        {
                            converted.putShort((short)(sample * Short.MAX_VALUE));
                        }

                        byte[] bytes = converted.array();
                        sourceDataLine.write(bytes, 0, bytes.length);

                        if(x == 0)
                        {
                            sourceDataLine.start();
                        }
                    }

                    System.out.println("\rTONE [" + tone.name() + "]: " + tone + " " + tone.getFrequency1() +
                        (tone.hasFrequency2() ? " PLUS " + tone.getFrequency2() : ""));
                }
            }
            catch(Exception e)
            {
                e.printStackTrace();
            }
        }
        else
        {
            System.out.println("Audio Format Not Supported by Host Audio System: " + audioFormat);
        }
    }
}