/*
 * 
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * 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 version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

package com.sun.mmedia;

import com.sun.mmedia.DirectPlayer;

import javax.microedition.media.*;
import javax.microedition.media.control.*;

/**
 * Implement all of MIDI related controls like :
 * 	MIDIControl, RateControl, PitchControl, TempoControl
 */
class DirectMIDIControl implements 
    MIDIControl, RateControl, PitchControl, TempoControl, DirectControls {

    // MIDIControl
    private native void nSetChannelVolume(int handle, int channel, int volume);
    private native int nGetChannelVolume(int handle, int channel);
    private native void nSetProgram(int handle, int channel, int bank, int program);
    private native void nShortMidiEvent(int handle, int type, int data1, int data2);
    private native int nLongMidiEvent(int handle, byte[] data, int offset, int length);
                  
    // RateControl
    private native int nGetMaxRate(int handle);
    private native int nGetMinRate(int handle);
    private native int nSetRate(int handle, int rate);
    private native int nGetRate(int handle); 
    
    // PitchControl
    private native int nGetMaxPitch(int handle);
    private native int nGetMinPitch(int handle);
    private native int nSetPitch(int handle, int pitch);
    private native int nGetPitch(int handle);
      
    // TempoControl    
    private native int nGetTempo(int handle);
    private native int nSetTempo(int handle, int tempo);

    // Bank Query
    private native boolean nIsBankQuerySupported(int handle);
    private native int nGetBankList(int handle, boolean custom, int[] list); 
    private native int nGetKeyName(int handle, int bank, int prog, int key, byte[] keyname);
    private native int nGetProgramName(int handle, int bank, int prog, byte[] progname);
    private native int nGetProgramList(int handle, int bank, int[] proglist);    
    private native int nGetProgram(int handle, int channel, int[] program);     
    
	DirectMIDIControl(DirectPlayer p) {
		_player = p;
	}

	// Private //////////////////////////////////////////////////////////

	private void checkState() {
        if (_player.state < Player.PREFETCHED) {
            throw new IllegalStateException("Not prefetched");
        }
    }

    private void checkChannel(int channel) {
        if (channel < 0 || channel > 15) {
            throw new IllegalArgumentException("Channel is out of range");
        }
    }

    private void checkVolume(int volume) {
        if (volume < 0 || volume > 127) {
            throw new IllegalArgumentException("Volume is out of range");    
        }
    }

    private void checkType(int type) {
        if (type < 0x80 || type > 0xFF || type == 0xF0 || type == 0xF7) {
            throw new IllegalArgumentException("Type is out of range");
        }
    }

    private void checkData(int data) {
        if (data < 0 || data > 127) {
            throw new IllegalArgumentException("Data is out of range");
        }
    }

    private void checkLongMidiEvent(byte[] data, int offset, int length) {
        if (data == null) {
            throw new IllegalArgumentException("Data is null for long MIDI event ");
        } else if ((offset >= data.length) || (offset < 0)) {
            if (data.length != 0) {
                throw new IllegalArgumentException("Invalid offset for long MIDI event");
            }
        } else if ((length > data.length) || (length < 0)) {
            throw new IllegalArgumentException("Length is out of range for long MIDI event");
        }
    }

    private void checkProgram(int program) {
        if (program < 0 || program > 127) {
            throw new IllegalArgumentException("Program is out of range");
        }
    }
    private void checkBank(int bank) {
        if (bank < 0 || bank > 16383) {
            throw new IllegalArgumentException("Bank is out of range");
        }
    }
	// DirectControls ////////////////////////////////////////////////////

	public synchronized void playerStarted() {
		if (_cachedRate != -1) {
			// NOTE - MMAPI SPEC is not clear about this
			// set cached rate at enter playing state
			nSetRate(_player.hNative, _cachedRate);
			_cachedRate = -1;
		}
	}

    public synchronized void playerStopped() {
    }

	public synchronized void playerClosed() {
        _player = null;
	}

	// MIDIControl ///////////////////////////////////////////////////////

    public synchronized boolean isBankQuerySupported() {
	return nIsBankQuerySupported(_player.hNative);        
    }

    public synchronized int[] getProgram(int channel) throws MediaException {

        checkState();
        checkChannel(channel);
        
        int[] p = new int[2];
        
        int len = nGetProgram(_player.hNative, channel, p);
        if(len < 0) throw new MediaException("GetProgram failure");
        
        return p;        
    }

    public synchronized int[] getBankList(boolean custom) throws MediaException {

        checkState();
        
        int[] bl1 = new int[20];
        
        int len = nGetBankList(_player.hNative, custom, bl1);
        if(len < 0) throw new MediaException("BankList failure");
        
        int[] bl2 = new int[len];
        
        for(int i=0; i<len; i++)
            bl2[i] = bl1[i];
        
        return bl2;
    }

    public synchronized String getKeyName(int bank, int prog, int key) throws MediaException {
 
        checkState();
        checkBank(bank);
        checkProgram(prog);
        
        if((key < 0) || (key > 127))
            throw new IllegalArgumentException("key out of range");
        
        byte[] str = new byte[64];
        
        int len = nGetKeyName(_player.hNative, bank, prog, key, str);
        if(len < 0) throw new MediaException("KeyName failure");
        
        return new String(str, 0, len);
    }

    public synchronized String getProgramName(int bank, int prog) throws MediaException {

        checkState();
        checkBank(bank);
        checkProgram(prog);
        
        byte[] str = new byte[64];
        
        int len = nGetProgramName(_player.hNative, bank, prog, str);
        if(len < 0) throw new MediaException("ProgramName failure");
        
        return new String(str, 0, len);    
    }

    public synchronized int[] getProgramList(int bank) throws MediaException {

        checkState();
        checkBank(bank);
                
        int[] pl1 = new int[200];
        
        int len = nGetProgramList(_player.hNative, bank, pl1);        
        if(len < 0) throw new MediaException("ProgramList failure");
        
        int[] pl2 = new int[len];
        
        for(int i=0; i<len; i++)
            pl2[i] = pl1[i];
        
        return pl2;
    }

    public synchronized void setChannelVolume(int channel, int volume) {
		checkState();
                checkChannel(channel);
                checkVolume(volume);
		
		if (_player != null && _player.hNative != 0) {
			nSetChannelVolume(_player.hNative, channel, volume);
		}
	}

    public synchronized int getChannelVolume(int channel) {
        checkState();
        checkChannel(channel);

        if (_player != null && _player.hNative != 0) {
            return nGetChannelVolume(_player.hNative, channel);
        } else {
            return -1;
        }
    }

    public synchronized void setProgram(int channel, int bank, int program) {
        checkState();
        checkChannel(channel);
        
        if(bank != -1) checkBank(bank);
        checkProgram(program);

        if (_player != null && _player.hNative != 0) {
            nSetProgram(_player.hNative, channel, bank, program);
        }
    }

    public synchronized void shortMidiEvent(int type, int data1, int data2) {
	    checkState();
        checkType(type);
        checkData(data1);
        checkData(data2);
    
        if (_player != null && _player.hNative != 0) {
            nShortMidiEvent(_player.hNative, type, data1, data2);
        }
	}

    public synchronized int longMidiEvent(byte[] data, int offset, int length) {
        checkState();
        checkLongMidiEvent(data, offset, length);

        if (_player != null && _player.hNative != 0) {
            return nLongMidiEvent(_player.hNative, data, offset, length);
        } else {
            return -1;
        }
    }

	// RateControl ///////////////////////////////////////////////////////
    
	//
	// SPEC:
	// 	If the Player is already started, setRate will immediately take effect.
	//	Q - How about the other case?
	//
    public synchronized int setRate(int millirate) {
        if (_player == null) 
            return 0;

        int max = getMaxRate();
        int min = getMinRate();

        if (millirate > max) {
                millirate = max;
        } else if (millirate < min) {
                millirate = min;
        }

        if (_player.state >= Player.STARTED) {
                _cachedRate = -1;
                return nSetRate(_player.hNative, millirate);
        } else {
                _cachedRate = millirate;
                return millirate;
        }
	}

    public synchronized int getRate() {
		if (_player != null && _player.hNative != 0) {
			return _cachedRate == -1 ? nGetRate(_player.hNative) : _cachedRate;
		} else {
			return 0;
		}
	}

    public synchronized int getMaxRate() {
		if (_player != null && _player.hNative != 0) {
			return nGetMaxRate(_player.hNative);
		} else {
			return 0;
		}
    }

    public synchronized int getMinRate() {
		if (_player != null && _player.hNative != 0) {
			return nGetMinRate(_player.hNative);
		} else {
			return 0;
		}
	}

	// PitchControl //////////////////////////////////////////////////////

    public synchronized int setPitch(int millisemitones) {
		int max = getMaxPitch();
		int min = getMinPitch();
		
		if (millisemitones > max) {
			millisemitones = max;
		} else if (millisemitones < min) {
			millisemitones = min;
		}

		if (_player != null && _player.hNative != 0) {
			return nSetPitch(_player.hNative, millisemitones);
		} else {
			return 0;
		}
	}

    public synchronized int getPitch() {
		if (_player != null && _player.hNative != 0) {
			return nGetPitch(_player.hNative);
		} else {
			return 0;
		}
	}

    public synchronized int getMaxPitch() {
		if (_player != null && _player.hNative != 0) {
			return nGetMaxPitch(_player.hNative);
		} else {
			return 0;
		}
	}

    public synchronized int getMinPitch() {
		if (_player != null && _player.hNative != 0) {
			return nGetMinPitch(_player.hNative);
		} else {
			return 0;
		}
	}

	// TempoControl //////////////////////////////////////////////////////

    public synchronized int setTempo(int millitempo) {
		if (millitempo < 0) {
			millitempo = 0;
		}

		if (_player != null && _player.hNative != 0) {
			return nSetTempo(_player.hNative, millitempo);
		} else {
			return 0;
		}
	}

    public synchronized int getTempo() {
    	if (_player != null &&  _player.hNative != 0) {
			return nGetTempo(_player.hNative);
		} else {
			return 0;
		}
	}

	//////////////////////////////////////////////////////////////////////

	private DirectPlayer _player;
	private int _cachedRate = -1;
};
