// Copyright (C) 2010  Paul Marks  http://www.pmarks.net/
//
// This file is part of Chroma Doze.
//
// Chroma Doze 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.
//
// Chroma Doze 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 Chroma Doze.  If not, see <http://www.gnu.org/licenses/>.

package net.pmarks.chromadoze;

import android.os.Parcel;
import android.os.Parcelable;

// SpectrumData is a Phonon translated into "machine readable" form.
//
// In other words, the values here are suitable for generating noise,
// and not for storage or rendering UI elements.

public class SpectrumData implements Parcelable {
    public static final Parcelable.Creator<SpectrumData> CREATOR
            = new Parcelable.Creator<SpectrumData>() {
        @Override
        public SpectrumData createFromParcel(Parcel in) {
            return new SpectrumData(in);
        }

        @Override
        public SpectrumData[] newArray(int size) {
            return new SpectrumData[size];
        }
    };

    private static final float MIN_FREQ = 100;
    private static final float MAX_FREQ = 20000;
    public static final int BAND_COUNT = 32;

    // The frequency of the edges between each bar.
    private static final int[] EDGE_FREQS = calculateEdgeFreqs();

    private final float[] mData;

    private static int[] calculateEdgeFreqs() {
        int[] edgeFreqs = new int[BAND_COUNT + 1];
        float range = MAX_FREQ / MIN_FREQ;
        for (int i = 0; i <= BAND_COUNT; i++) {
            edgeFreqs[i] = (int) (MIN_FREQ * Math.pow(range, (float) i / BAND_COUNT));
        }
        return edgeFreqs;
    }

    public SpectrumData(float[] donateBars) {
        if (donateBars.length != BAND_COUNT) {
            throw new RuntimeException("Incorrect number of bands");
        }
        mData = donateBars;
        for (int i = 0; i < BAND_COUNT; i++) {
            if (mData[i] <= 0f) {
                mData[i] = 0f;
            } else {
                mData[i] = 0.001f * (float) Math.pow(1000, mData[i]);
            }
        }
    }

    private SpectrumData(Parcel in) {
        mData = new float[BAND_COUNT];
        in.readFloatArray(mData);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeFloatArray(mData);
    }

    public void fill(float[] out, int sampleRate) {
        int maxFreq = sampleRate / 2;
        subFill(out, 0f, 0, EDGE_FREQS[0], maxFreq);
        for (int i = 0; i < BAND_COUNT; i++) {
            subFill(out, mData[i], EDGE_FREQS[i], EDGE_FREQS[i + 1], maxFreq);
        }
        subFill(out, 0f, EDGE_FREQS[BAND_COUNT], maxFreq, maxFreq);
    }

    private void subFill(float[] out, float setValue, int startFreq, int limitFreq, int maxFreq) {
        // This min() applies if the sample rate is below 40kHz.
        int limitIndex = Math.min(out.length, limitFreq * out.length / maxFreq);
        for (int i = startFreq * out.length / maxFreq; i < limitIndex; i++) {
            out[i] = setValue;
        }
    }

    public boolean sameSpectrum(SpectrumData other) {
        if (other == null) {
            return false;
        }
        for (int i = 0; i < BAND_COUNT; i++) {
            if (mData[i] != other.mData[i]) {
                return false;
            }
        }
        return true;
    }
}