//////////////////////////////////////////////////////////////////////////////
//	Copyright 2011 Alex Leffelman
//	
//	Licensed under the Apache License, Version 2.0 (the "License");
//	you may not use this file except in compliance with the License.
//	You may obtain a copy of the License at
//	
//	http://www.apache.org/licenses/LICENSE-2.0
//	
//	Unless required by applicable law or agreed to in writing, software
//	distributed under the License is distributed on an "AS IS" BASIS,
//	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//	See the License for the specific language governing permissions and
//	limitations under the License.
//////////////////////////////////////////////////////////////////////////////

package com.leff.midi.event;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;

public class ChannelEvent extends MidiEvent
{
    protected int mType;
    protected int mChannel;
    protected int mValue1;
    protected int mValue2;

    private static HashMap<Integer, Integer> mOrderMap;

    protected ChannelEvent(long tick, int type, int channel, int param1, int param2)
    {
        this(tick, 0, type, channel, param1, param2);
    }

    protected ChannelEvent(long tick, long delta, int type, int channel, int param1, int param2)
    {
        super(tick, delta);

        mType = type & 0x0F;
        mChannel = channel & 0x0F;
        mValue1 = param1 & 0xFF;
        mValue2 = param2 & 0xFF;
    }

    public int getType()
    {
        return mType;
    }

    public void setChannel(int c)
    {
        if(c < 0)
        {
            c = 0;
        }
        else if(c > 15)
        {
            c = 15;
        }
        mChannel = c;
    }

    public int getChannel()
    {
        return mChannel;
    }

    protected int getEventSize()
    {
        switch(mType)
        {
            case PROGRAM_CHANGE:
            case CHANNEL_AFTERTOUCH:
                return 2;
            default:
                return 3;
        }
    }

    @Override
    public int compareTo(MidiEvent other)
    {
        if(mTick != other.getTick())
        {
            return mTick < other.getTick() ? -1 : 1;
        }
        if(mDelta.getValue() != other.mDelta.getValue())
        {
            return mDelta.getValue() < other.mDelta.getValue() ? 1 : -1;
        }

        if(!(other instanceof ChannelEvent))
        {
            return 1;
        }

        ChannelEvent o = (ChannelEvent) other;
        if(mType != o.getType())
        {
            if(mOrderMap == null)
            {
                buildOrderMap();
            }

            int order1 = mOrderMap.get(mType);
            int order2 = mOrderMap.get(o.getType());

            return order1 < order2 ? -1 : 1;
        }
        if(mValue1 != o.mValue1)
        {
            return mValue1 < o.mValue1 ? -1 : 1;
        }
        if(mValue2 != o.mValue2)
        {
            return mValue2 < o.mValue2 ? -1 : 1;
        }
        if(mChannel != o.getChannel())
        {
            return mChannel < o.getChannel() ? -1 : 1;
        }
        return 0;
    }

    @Override
    public boolean requiresStatusByte(MidiEvent prevEvent)
    {
    	return true;
    }

    @Override
    public void writeToFile(OutputStream out, boolean writeType) throws IOException
    {
        super.writeToFile(out, writeType);

        if(writeType)
        {
            int typeChannel = (mType << 4) + mChannel;
            out.write(typeChannel);
        }

        out.write(mValue1);
        if(mType != PROGRAM_CHANGE && mType != CHANNEL_AFTERTOUCH)
        {
            out.write(mValue2);
        }
    }

    public static ChannelEvent parseChannelEvent(long tick, long delta, int type, int channel, InputStream in) throws IOException
    {
        int val1 = in.read();
        int val2 = 0;
        if(type != PROGRAM_CHANGE && type != CHANNEL_AFTERTOUCH)
        {
            val2 = in.read();
        }

        switch(type)
        {
            case NOTE_OFF:
                return new NoteOff(tick, delta, channel, val1, val2);
            case NOTE_ON:
                return new NoteOn(tick, delta, channel, val1, val2);
            case NOTE_AFTERTOUCH:
                return new NoteAftertouch(tick, delta, channel, val1, val2);
            case CONTROLLER:
                return new Controller(tick, delta, channel, val1, val2);
            case PROGRAM_CHANGE:
                return new ProgramChange(tick, delta, channel, val1);
            case CHANNEL_AFTERTOUCH:
                return new ChannelAftertouch(tick, delta, channel, val1);
            case PITCH_BEND:
                return new PitchBend(tick, delta, channel, val1, val2);
            default:
                return new ChannelEvent(tick, delta, type, channel, val1, val2);
        }
    }

    private static void buildOrderMap()
    {

        mOrderMap = new HashMap<Integer, Integer>();

        mOrderMap.put(PROGRAM_CHANGE, 0);
        mOrderMap.put(CONTROLLER, 1);
        mOrderMap.put(NOTE_ON, 2);
        mOrderMap.put(NOTE_OFF, 3);
        mOrderMap.put(NOTE_AFTERTOUCH, 4);
        mOrderMap.put(CHANNEL_AFTERTOUCH, 5);
        mOrderMap.put(PITCH_BEND, 6);
    }

    public static final int NOTE_OFF = 0x8;
    public static final int NOTE_ON = 0x9;
    public static final int NOTE_AFTERTOUCH = 0xA;
    public static final int CONTROLLER = 0xB;
    public static final int PROGRAM_CHANGE = 0xC;
    public static final int CHANNEL_AFTERTOUCH = 0xD;
    public static final int PITCH_BEND = 0xE;
}
