/*
 * Copyright (C) 2013 Darth Affe <http://wyrez.org> and contributors
 *
 * 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 org.wyrez.shootingstars.data;

import org.wyrez.audio.AudioProcessor;
import org.wyrez.audio.decoder.DecoderFactory;
import org.wyrez.audio.util.Band;
import org.wyrez.audio.util.SampleBuffer;
import org.wyrez.audio.util.SampleHelper;
import org.wyrez.shootingstars.game.GameSettings;
import uk.co.caprica.vlcj.player.MediaPlayer;

/**
 *
 * @author Darth Affe
 */
public class AudioDataManager {

    private static final int MIN_PEAK_DIFF = 13; //~198.8bpm
    private static final Band LOW_BAND = new Band(1f, 140);
    private static final Band MID_BAND = new Band(400f, 1200f);
    private static final Band HIGH_BAND = new Band(2000f, 16000f);
    /**/
    private AudioProcessor audioProcessorLowBand;
    private AudioProcessor audioProcessorMidBand;
    private AudioProcessor audioProcessorHighBand;
    private MediaPlayer player;
    private int bufferSize;
    private float samplingRate;
    private float[] speedData;
//    private float[] movementData;
    private float[] flashData;
    private float[] groundMoveData;
    private float peakAverageFlash;
    private float indexFactor;
    private float bpm;
    private float currentTime;
    private float lastTimeSync;

    public void AudioDataManager() {
    }

    public boolean initialize(GameSettings settings, MediaPlayer player) {
        try {
            this.player = player;
            SampleBuffer samples = SampleHelper.createSampleBuffer(
                    DecoderFactory.create(settings.getAudioFile()));
            this.samplingRate = samples.samplingRate;
            this.audioProcessorLowBand = new AudioProcessor(samples, LOW_BAND);
            this.audioProcessorMidBand = new AudioProcessor(samples, MID_BAND);
            this.audioProcessorHighBand = new AudioProcessor(samples, HIGH_BAND);
            return true;
        } catch (Exception ex) {
            return false;
        }
    }

    public void update(float tpf) {
        if (lastTimeSync != player.getTime()) {
            currentTime = player.getTime();
            lastTimeSync = currentTime;
        } else {
            currentTime += tpf * 1000f;
        }
    }

    public void analyseLowBand() {
        audioProcessorLowBand.calculate();
        bufferSize = audioProcessorLowBand.getBufferSize();
        indexFactor = (samplingRate / bufferSize) / 1000f;

        speedData = calcWave(audioProcessorLowBand.getSpectrum(), 4, 24, 3);
        SampleHelper.normalize(speedData, 0f, 1f);

        //TODO if there is some time left...
//        movementData = calcWave(audioProcessorLowBand.getPeaks(), 2, 2, 48);
//        SampleHelper.normalize(movementData, 0f, 1f);

        audioProcessorLowBand.cutFastPeaks(MIN_PEAK_DIFF);

        groundMoveData = audioProcessorLowBand.getPeaks();
        SampleHelper.normalize(groundMoveData, 0f, 1f);

        bpm = audioProcessorLowBand.getBpm();
        while (bpm > 200f) {
            bpm /= 2f;
        }

        audioProcessorLowBand.clean();
        audioProcessorLowBand = null;
    }

    private float calculateAverage(float[] data) {
        int counter = 0;
        float sum = 0f;
        for (int i = 0; i < data.length; i++) {
            if (data[i] > 0f) {
                sum += data[i];
                counter++;
            }
        }
        return sum / counter;
    }

    public void analyseMidBand() {
        audioProcessorMidBand.calculate();
        audioProcessorMidBand.cutFastPeaks(MIN_PEAK_DIFF);

        audioProcessorMidBand.clean();
        audioProcessorMidBand = null;
    }

    public void analyseHighBand() {
        audioProcessorHighBand.calculate();

        flashData = audioProcessorHighBand.getPeaks();
        float maxValue = audioProcessorHighBand.getPeakAverage() * 2f;
        for (int i = 0; i < flashData.length; i++) {
            if (flashData[i] > maxValue) {
                flashData[i] = maxValue;
            }
        }
        SampleHelper.normalize(flashData, 0f, 1f);
        peakAverageFlash = calculateAverage(audioProcessorHighBand.getPeaks());

        audioProcessorHighBand.clean();
        audioProcessorHighBand = null;
    }

    private float[] calcWave(float[] data, int actualWeight, int calcSize, int smooth) {
        float[] result = new float[data.length];
        float average;
        int count;
        for (int s = 0; s < smooth; s++) {
            for (int i = 0; i < data.length; i++) {
                count = actualWeight;
                average = data[i] * actualWeight;

                for (int j = 0; j < calcSize; j++) {
                    if (i > j) {
                        average += data[i - j];
                        count++;
                    }
                    if (i < data.length - j - 1) {
                        average += data[i + j];
                        count++;
                    }
                }
                average /= count;
                result[i] = average;
            }
        }
        return result;
    }

    public float getBPM() {
        return bpm;
    }

    private int calcIndex(int length) {
        int index = (int) (currentTime * indexFactor);
        if (index >= length) {
            index = length - 1;
        }
        return index;
    }

    public boolean isFlash() {
        return flashData[calcIndex(flashData.length)] > peakAverageFlash * 0.5f;
    }

    public float getFlashData() {
        return flashData[calcIndex(flashData.length)];
    }

    public boolean isGroundMove() {
        return groundMoveData[calcIndex(groundMoveData.length)] > 0f;
    }

    public float getGroundMoveData() {
        return groundMoveData[calcIndex(flashData.length)];
    }

    public float getMovementData() {
        return 0.25f;
//        System.out.println(currentTime + " * " + indexFactor + " = " + calcIndex() + " --> " + movementData[calcIndex()]);
//        return movementData[calcIndex()];
    }

    public float getSpeedData() {
        return speedData[calcIndex(flashData.length)];
    }

    public float getMidData() {
        return 0f; //TODO implement
    }

    public float getHighData() {
        return 0f; //TODO implement
    }
}
