package vermilion;

/**
 * Defines a base for audio processing plugins. This includes buffers,
 * parameters, and a mixing routine that the programmer can override.
 */
public abstract class MachineBase extends PluginClassVermilion
{
    /**
     * The output buffer, where the plugin places audio.
     */
    public float[][] output = new float[0][0];

    /**
     * The 'A' input buffer of the machine, generally the main input.
     */
    public float[][] aInput = new float[0][0];

    /**
     * The 'B' input buffer of the machine, generally an auxilliary input for
     * things like a vocoder.
     */
    public float[][] bInput = new float[0][0];

    /**
     * The 'C' input buffer of the machine, in case a need ever arises.
     */
    public float[][] cInput = new float[0][0];

    /**
     * A list of wires that feed this machine. Each one is premixed
     * to one of the buffers before the sub-class fillBuffer is called.
     */
    public SimpleWire[] inputs = new SimpleWire[0];

    /**
     * This array will hold the list of controls that the machine
     * exposes.
     */
    public ParameterBase[] parameters = new ParameterBase[0];

    /**
     * Identifies the machine within a composition, since more than
     * one instance of a given machine may be used.
     */
    public String machineID = "[BASE]";

    /**
     * Specifies a machine's x location on the wiring panel.
     */
    public int xLoc = 0;

    /**
     * Specifies a machine's y location on the wiring panel.
     */
    public int yLoc = 0;

    /**
     * Indicates that this machine is the master machine.
     */
    public boolean isMaster = false;

    /**
     * This is used to prevent overflow when a loop is connected and stop
     * double generation when multiple outputs are present.
     */
    protected boolean hasWorkedThisCycle = false;

    // The sample bank is kept here so machines can access it.
    protected static SystemSampleBank sampleBank;

    public MachineBase()
    {
        if (Main.audio != null)
        {
            output = new float[Main.audio.NUM_CHANNELS][Main.audio.internalBufferSize];
            aInput = new float[Main.audio.NUM_CHANNELS][Main.audio.internalBufferSize];
            bInput = new float[Main.audio.NUM_CHANNELS][Main.audio.internalBufferSize];
            cInput = new float[Main.audio.NUM_CHANNELS][Main.audio.internalBufferSize];
        }
    }

    /**
     * This is the routine that sub-classes override to implement the algorithm
     * for their machine.
     * @param numSamples The number of samples that are to be processed at this time.
     */
    public abstract void fillBuffer(int numSamples);

    /**
     * This is called by the router when a machine is created to list the
     * controls on the machine. The result will be placed in the parameters
     * class variable and manipulated directly. Machines can also keep their
     * own class variables to name parameters and access them during events.
     */
    public abstract void setupMachine();

    /**
     * This is called when a machine's parameter changes, so the machine
     * may respond in whatever way is appropriate. The system makes no guarantee
     * on the order of parameter changes, and simply copies the new value onto
     * the parameter before calling this method.
     * @param param The parameter which has been changed.
     */
    public abstract void parameterChange(PlaybackEvent param);

    /**
     * Prints the value for a parameter, so it can be shown on e.g. machine
     * parameter windows.
     * @param p The parameter we are interested in.
     * @param value The value of the parameter we wish to print.
     * @return A string representing the meaning of the value to this parameter.
     */
    public abstract String printParameter(ParameterBase p);

    /**
     * This method is called internally to mix the input buffers before
     * processing.
     * @param numSamples The number of samples that are to be processed at this time.
     */
    public void fillBufferInternal(int numSamples)
    {
        //Set a flag indicating this machine is active (for loops).
        if (hasWorkedThisCycle == false)
        {
            hasWorkedThisCycle = true;

            try
            {
                boolean aClear = false;
                boolean bClear = false;
                boolean cClear = false;

                for (int x = 0; x < inputs.length; x++)
                {
                    // Mixing of multiple inputs (of the same type) here.
                    SimpleWire wire = inputs[x];

                    wire.input.fillBufferInternal(numSamples);

                    if (wire.channel == 0)
                    {
                        mixInput(wire.input.output, aInput, numSamples, wire.amplitudeL, wire.amplitudeR, aClear);
                        aClear = true;
                    }
                    else if (wire.channel == 1)
                    {
                        mixInput(wire.input.output, bInput, numSamples, wire.amplitudeL, wire.amplitudeR, bClear);
                        bClear = true;
                    }
                    else if (wire.channel == 2)
                    {
                        mixInput(wire.input.output, cInput, numSamples, wire.amplitudeL, wire.amplitudeR, cClear);
                        cClear = true;
                    }
                }

                if (aClear == false) clearBuffer(aInput, numSamples);
                if (bClear == false) clearBuffer(bInput, numSamples);
                if (cClear == false) clearBuffer(cInput, numSamples);

                fillBuffer(numSamples);
            }
            catch (Exception e) {}
        }
    }

    // Mixes the data provided by one of the wires to the buffer that the wire targets.
    private void mixInput(float[][] input, float[][] output, int numSamples, float amplitudeL, float amplitudeR, boolean mix)
    {
        // NOTE: Hard coded for 2 channels.
        if (mix)
        {
            for (int x = 0; x < numSamples; x++)
            {
                output[0][x] += input[0][x] * amplitudeL;
                output[1][x] += input[1][x] * amplitudeR;
            }
        }
        else
        {
            for (int x = 0; x < numSamples; x++)
            {
                output[0][x] = input[0][x] * amplitudeL;
                output[1][x] = input[1][x] * amplitudeR;
            }
        }
    }

    // Clears a buffer to zero in the event that no audio data was placed in it.
    private void clearBuffer(float[][] input, int numSamples)
    {
        for (int x = 0; x < numSamples * Main.audio.NUM_CHANNELS; x++)
        {
            // NOTE: Hardcoded for two channels.
            input[0][x] = 0.0f;
            input[1][x] = 0.0f;
        }
    }

    /**
     * Machine plugins do not need notification of document loading/saving.
     */
    @Override
    public void documentLoaded() {}

    /**
     * Machine plugins do not need notification of document loading/saving.
     */
    @Override
    public void documentSaving() {}

    /**
     * Machine plugins do not require initialisation and do nothing.
     */
    @Override
    public void initialise() {}

    /**
     * Machine plugins do not require deinitialisation and do nothing.
     */
    @Override
    public void deinitialise() {}

    /**
     * Machine plugins do not pass messages via the system.
     */
    @Override
    public void processMessage(String message, Object msgVal) {}

    public ParameterBase findParameter(String parameterName)
    {
        for (ParameterBase pb : parameters)
        {
            if (pb.parameterName.equals(parameterName)) return pb;
        }

        return null;
    }
}