/**
 * 
 */
package jp.ac.naka.ec.media;

import java.awt.Component;
import java.awt.Frame;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import javax.media.Format;
import javax.media.MediaException;
import javax.media.PlugInManager;
import javax.media.format.AudioFormat;
import javax.sdp.SdpConstants;

import net.sourceforge.jsdp.Attribute;
import net.sourceforge.jsdp.Connection;
import net.sourceforge.jsdp.Media;
import net.sourceforge.jsdp.MediaDescription;
import net.sourceforge.jsdp.Origin;
import net.sourceforge.jsdp.SdpException;
import net.sourceforge.jsdp.SdpMessageParser;
import net.sourceforge.jsdp.SessionDescription;
import net.sourceforge.jsdp.SessionName;
import net.sourceforge.jsdp.Time;
import net.sourceforge.jsdp.TimeDescription;
import net.sourceforge.jsdp.Version;

/**
 * JMediaSenderのラッパー
 * 
 * @author 粕谷
 * 
 */
public class AudioPlayer implements MediaPlayer {

	private InetAddress localhost;

	// デフォルトポート
	protected static int srcPort = 20023;
	
	// Payloadの最大数
	protected int limit = 3;
	
	private AudioReceiver receiver;
	private AudioTransmitter transmiter;
	protected List<Integer> availableAudioFormats = new ArrayList<Integer>();

	protected int[] receivableJmfAudioFormats = new int[] {
	// sdp format // corresponding JMF Format
			SdpConstants.G723, // javax.media.format.AudioFormat.G723_RTP
			SdpConstants.GSM, // javax.media.format.AudioFormat.GSM_RTP;
			SdpConstants.PCMU, // javax.media.format.AudioFormat.ULAW_RTP;
			SdpConstants.DVI4_8000, // javax.media.format.AudioFormat.DVI_RTP;
			SdpConstants.DVI4_16000, // javax.media.format.AudioFormat.DVI_RTP;
			SdpConstants.PCMA, // javax.media.format.AudioFormat.ALAW;
			SdpConstants.G728 // , //

	};

	public AudioPlayer() throws IOException {
		this("localhost");
	}

	public AudioPlayer(String host) throws IOException {
		this(host, srcPort);
	}
	
	public AudioPlayer(String host, int port) throws IOException {
		init(host);
		this.srcPort = port;
	}

	private void init(String host) throws IOException {
		this.localhost = InetAddress.getByName(host);
		// TODO
		// ここで設定ファイルから選べるようにしよう
		Arrays.sort(receivableJmfAudioFormats);
		for (int format:receivableJmfAudioFormats) {
			availableAudioFormats.add(format);
		}
	}

	public Component getVisualComponent() {
		return null;
	}

	/**
	 * メディア記述を除いたSDPの生成
	 * 
	 * @param version
	 * @param userName
	 * @param sessionName
	 * @return
	 */
	private SessionDescription generateBasicSessionDescription(int version,
			String userName, String sessionName) {

		SessionDescription sdp = null;
		try {
			Version v = new Version(version);
			/*
			 * String addrType = localhost instanceof Inet6Address ?
			 * Connection.IP6 : Connection.IP4;
			 */
			Date date = new Date();
			long sessionVersion = date.getTime();

			Origin o = new Origin(userName, sessionVersion, localhost
					.getHostAddress());
			o.setSessionVersion(o.getSessionID());
			SessionName s = new SessionName(sessionName);
			TimeDescription t = new TimeDescription(new Time());

			Connection c = new Connection(localhost.getHostAddress());
			sdp = new SessionDescription(v, o, s, t);
			// sdp.addMediaDescription(am);
			sdp.setConnection(c);
			sdp.setSessionName(s);

		} catch (SdpException e) {
			System.err
					.println("An SDP exception occurred while generating local sdp description \n"
							+ e.getMessage());
		}

		return sdp;
	}

	/* (non-Javadoc)
	 * @see jp.ac.naka.ec.media.MediaPlayer#getRequestSessionDescription(java.lang.String, java.lang.String)
	 */
	public SessionDescription getRequestSessionDescription(String user_name, String session_name) throws MediaException {
		String sdp_str = getRequestSessionDescription(0, user_name, session_name);
		return getSessionDescription(sdp_str);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.iobb.ibaraki.media.MediaPlayer#getRequestSessionDescription(int,
	 *      java.lang.String, java.lang.String)
	 */
	public String getRequestSessionDescription(int version, String userName,
			String sessionName) {
		SessionDescription sdp = generateBasicSessionDescription(version,
				userName, sessionName);
		MediaDescription[] md = null;
		String sdp_str = null;
		if (!availableAudioFormats.isEmpty()) {
			md = new MediaDescription[1];
			String name = "rtpmap";
			String value;
			Iterator<Integer> iter = availableAudioFormats.iterator();
			int payload = iter.next();
			Media media = new Media("audio", srcPort, "RTP/AVP", payload + "");

			value = payload + " " + getAttributeValue(payload);
			Attribute attr = new Attribute(name, value);
			md[0] = new MediaDescription(media);

			md[0].addAttribute(attr);
			
			while (iter.hasNext()) {
				payload = iter.next();
				media.addMediaFormat(payload + "");
				value = payload + " " + getAttributeValue(payload);
				attr = new Attribute(name, value);
				md[0].addAttribute(attr);
				limit--;
				if (limit == 1)  
					break;
				
			}
			limit = 3;
			if (md != null) {
				// バグがあるっぽい
				// sdp.setMediaDescriptions(md);
			}

			sdp_str = sdp.toString();
			if (md != null) {
				for (MediaDescription mediaDescriptor : md)
					sdp_str += mediaDescriptor.encode();
			}
			//System.out.println("Generated SDP - \n" + sdp_str);
		}else {
			//throw new MediaException ("There is no available AudioFormat");
			return "";
		}
		return sdp_str;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.iobb.ibaraki.media.MediaPlayer#getAcceptableFormats(java.lang.String)
	 */
	public int[] getReceivableFormats(String sdp_str) {
		int[] supports = getSupportedFormats(sdp_str);
		Vector codecs = PlugInManager.getPlugInList(null, null,
				PlugInManager.CODEC);
		Set<Integer> vec = new HashSet<Integer>();
		// 8bit固定
		int size = 8;
		for (Iterator iter = codecs.iterator(); iter.hasNext();) {
			String plugin = (String) iter.next();
			Format[] formats = PlugInManager.getSupportedInputFormats(plugin,
					PlugInManager.CODEC);
			for (Format format : formats) {

				if (format instanceof AudioFormat) {
					AudioFormat f = (AudioFormat) format;
					for (int i = 0; i < supports.length; i++) {
						// 比較
						AudioJmfFormats jmfFormat = findCorrespondingJmfFormat(supports[i]);
						if (jmfFormat == null) {
							// System.err.println("Can't resolve payload type:"
							// + supports[i]);
							continue;
						}
						Format target = new AudioFormat(jmfFormat.encoding,
								jmfFormat.sampleRate, size, jmfFormat.channel);
						AudioFormat audio = (AudioFormat) target;

						// とりあえずエンコーディングがあっているのを探す
						if (audio.isSameEncoding(f)) {
							vec.add(supports[i]);
						}
					}
				}
			}
		}
		int[] fmts = new int[vec.size()];
		int fmt = 0, i=0;
		for (Iterator<Integer> iter=vec.iterator(); iter.hasNext();fmt = iter.next(), i++) {
			fmts[i]=fmt;
		}
		Arrays.sort(fmts);
		return fmts;
	}

	/**
	 * Gets Media Formats of Media Description information sent in SDP.
	 * 
	 * @param inviteRequest
	 * @return supportedFormats
	 */
	private int[] getSupportedFormats(String sdp_str) {
		int[] supportedFormats = null;
		String[] temp = null;
		MediaDescription[] mediaDescriptions = getSessionDescription(sdp_str)
				.getMediaDescriptions();
		for (MediaDescription mediaDescription : mediaDescriptions) {
			Media media = mediaDescription.getMedia();
			// 音声メディアのみ
			if (media.getMediaType().equals("audio")) {
				temp = media.getMediaFormats();
				break;
			}
		}
		supportedFormats = new int[temp.length];
		int i = 0;
		for (String str : temp) {
			supportedFormats[i++] = Integer.parseInt(str);
		}
		return supportedFormats;
	}

	/* (non-Javadoc)
	 * @see jp.ac.naka.ec.media.MediaPlayer#getResponseSessionDescription(net.sourceforge.jsdp.SessionDescription)
	 */
	public SessionDescription getResponseSessionDescription(SessionDescription sdp) {
		int version = 0;
		String name = sdp.getSessionName().toString();
		String userName = sdp.getOrigin().getUser();
		String sdp_str = getResponseSessionDescription(sdp.toString(), version, userName, name);
		return getSessionDescription(sdp_str);
	}

	protected SessionDescription getSessionDescription(String sdp_str) {
		SessionDescription sdp = SdpMessageParser
				.getSessionDescription(sdp_str);
		return sdp;
	}

	/**
	 * ファイルからSDPのStringを生成
	 * 
	 * @param sdp_file
	 * @return
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public static String getSessionDescriptionString(String sdp_file)
			throws FileNotFoundException, IOException {
		FileReader fr = new FileReader(sdp_file);
		BufferedReader br = new BufferedReader(fr);
		StringBuilder builder = new StringBuilder();
		int i = 0;
		while ((i = br.read()) != -1) {
			char c = (char) i;
			builder.append(c);
		}
		return builder.toString();
	}

	/**
	 * ペイロードタイプに対応したフォーマットを返す
	 * 
	 * @param payload
	 * @return
	 */
	private String getAttributeValue(int payload) {
		switch (payload) {
		case SdpConstants.PCMU:
			return "PCMU/8000";
		case SdpConstants.GSM:
			return "gsm/8000";
		case SdpConstants.G723:
			return "g723/8000";
		case SdpConstants.DVI4_8000:
			return "dvi4/8000";
		case SdpConstants.DVI4_16000:
			return "dvi4/16000";
		case SdpConstants.PCMA:
			return "PCMA/8000";
		case SdpConstants.G728:
			return "g728/8000";
		case SdpConstants.DVI4_11025:
			return "dvi4/11025";
		case SdpConstants.DVI4_22050:
			return "dvi4/22050";
		case SdpConstants.G729:
			return "g729/8000";
		default:
			return null;
		}
	}

	private AudioJmfFormats findCorrespondingJmfFormat(int sdpFormat) {

		switch (sdpFormat) {
		case SdpConstants.PCMU:
			return new AudioJmfFormats(AudioFormat.ULAW_RTP, 8000.0);
		case SdpConstants.GSM:
			return new AudioJmfFormats(AudioFormat.GSM_RTP, 8000.0);
		case SdpConstants.G723:
			return new AudioJmfFormats(AudioFormat.G723_RTP, 8000.0);
		case SdpConstants.DVI4_8000:
			return new AudioJmfFormats(AudioFormat.DVI_RTP, 8000.0);
		case SdpConstants.DVI4_16000:
			return new AudioJmfFormats(AudioFormat.DVI_RTP, 16000.0);
		case SdpConstants.PCMA:
			return new AudioJmfFormats(AudioFormat.ALAW, 8000.0);
		case SdpConstants.G728:
			return new AudioJmfFormats(AudioFormat.G728_RTP, 8000.0);
		case SdpConstants.DVI4_11025:
			return new AudioJmfFormats(AudioFormat.DVI_RTP, 11025.0);
		case SdpConstants.DVI4_22050:
			return new AudioJmfFormats(AudioFormat.DVI_RTP, 22050.0);
		case SdpConstants.G729:
			return new AudioJmfFormats(AudioFormat.G729_RTP, 8000.0);

		default:
			return null;
		}
	}
	
	AudioFormat findCorrespondingAudioFormat(int sdpFormat) {

		switch (sdpFormat) {
		case SdpConstants.PCMU:
			return new AudioFormat(AudioFormat.ULAW_RTP, 8000.0d, 8, 1);
		case SdpConstants.GSM:
			return new AudioFormat(AudioFormat.GSM_RTP, 8000.0d, 8, 1);
		case SdpConstants.G723:
			return new AudioFormat(AudioFormat.G723_RTP, 8000.0d, 8, 1);
		case SdpConstants.DVI4_8000:
			return new AudioFormat(AudioFormat.DVI_RTP, 8000.0d, 8, 1);
		case SdpConstants.DVI4_16000:
			return new AudioFormat(AudioFormat.DVI_RTP, 16000.0d, 8, 1);
		case SdpConstants.PCMA:
			return new AudioFormat(AudioFormat.ALAW, 8000.0d, 8, 1);
		case SdpConstants.G728:
			return new AudioFormat(AudioFormat.G728_RTP, 8000.0d, 8, 1);
		case SdpConstants.DVI4_11025:
			return new AudioFormat(AudioFormat.DVI_RTP, 11025d, 8, 1);
		case SdpConstants.DVI4_22050:
			return new AudioFormat(AudioFormat.DVI_RTP, 22050.0, 8, 1);
		case SdpConstants.G729:
			return new AudioFormat(AudioFormat.G729_RTP, 8000.0, 8 ,1);

		default:
			return null;
		}
	}

	class AudioJmfFormats {
		String encoding;

		double sampleRate;

		int channel = 1;

		private AudioJmfFormats(String encoding, double sampleRate) {
			this.encoding = encoding;
			this.sampleRate = sampleRate;
		}

		private AudioJmfFormats(String encoding, double sampleRate, int channel) {
			this.encoding = encoding;
			this.sampleRate = sampleRate;
			this.channel = 1;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.iobb.ibaraki.media.MediaPlayer#addAvailableFormat(int)
	 */
	public void addAvailableFormat(int payload) {
		availableAudioFormats.add(payload);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.iobb.ibaraki.media.MediaPlayer#getResponseSessionDescription(java.lang.String,
	 *      int, java.lang.String, java.lang.String)
	 */
	public String getResponseSessionDescription(String sdp_string, int version,
			String userName, String sessionName) {
		int[] supports = getReceivableFormats(sdp_string);
		int payload = supports[0];
		String name = "rtpmap";
		String value, attr_str;
		// オーディオのみなので１つ
		MediaDescription[] md = new MediaDescription[1];
		SessionDescription sdp = generateBasicSessionDescription(version,
				userName, sessionName);
		// メディア記述の追加
		Media media = new Media("audio", srcPort, "RTP/AVP", payload + "");
		value = payload + " " + getAttributeValue(payload);
		Attribute attr = new Attribute(name, value);
		md[0] = new MediaDescription(media);
		md[0].addAttribute(attr);
		for (int i = 1; i < supports.length; i++) {
			payload = supports[i];
			attr_str = getAttributeValue(payload);
			if (attr_str == null) {
				continue;
			}
			media.addMediaFormat(payload + "");
			value = payload + " " + attr_str;
			attr = new Attribute(name, value);
			md[0].addAttribute(attr);
		}
		StringBuilder builder = new StringBuilder();
		builder.append(sdp.toString());
		builder.append(md[0].encode());
		return builder.toString();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.iobb.ibaraki.media.MediaPlayer#isAvailable(java.lang.String)
	 */
	public boolean isAvailable(String sdp_string) {
		int[] formats = getReceivableFormats(sdp_string);
		if (formats != null || formats.length != 0) {
			return true;
		}
		return false;
	}

	/**
	 * RTPオーディオストリームの受信
	 * 
	 * @param sdp_string
	 */
	public void receiveMediaStream(String sdp_string) throws MediaException {
		if (receiver == null) {
			receiver = new AudioReceiver();
		}
		SessionDescription sdp = SdpMessageParser
				.getSessionDescription(sdp_string);
		// 検証
		int[] formats = getReceivableFormats(sdp_string);
		if (formats.length > 0) {
			receiver.receiveMedia(sdp);
		} else {
			throw new MediaException("Invalid MediaFormats: "
					+ sdp.getMediaDescriptions().toString());
		}

	}

	/**
	 * キャプチャで取得した音声のRTPによる送信。
	 * 
	 * @param sdp_string
	 * @throws MediaException
	 */

	public void sendMediaStream(String path, int port) throws MediaException {
		if (transmiter == null) {
			transmiter = new AudioTransmitter(path, port);
		}

	}

	/**
	 * キャプチャで取得した音声のRTPによる送信。
	 * 
	 * @param sdp_string
	 * @throws MediaException
	 * @throws IOException
	 */
	public void sendMediaStream(String sdp_string) throws MediaException,
			IOException {
		int[] formats = getReceivableFormats(sdp_string);
		AudioJmfFormats f = findCorrespondingJmfFormat(formats[0]);
		AudioFormat format = new AudioFormat(f.encoding, f.sampleRate,
				AudioTransmitter.rtp_bit, f.channel);
		SessionDescription sdp = SdpMessageParser
				.getSessionDescription(sdp_string);
		Connection c = sdp.getConnection();
		String url = c.getAddress();
		int port = 0;
		MediaDescription[] mls = sdp.getMediaDescriptions();
		for (MediaDescription md : mls) {
			Media media = md.getMedia();
			if (media.getMediaType().equals("audio")) {
				port = media.getPort();
			}
		}
		if (transmiter == null) {
			transmiter = new AudioTransmitter(url, port, format);
		}
		transmiter.play();
	}

	/**
	 * 音声ファイルからの音声のRTPによる送信。
	 * 
	 * @param sdp_String
	 * @param path
	 * @throws MediaException
	 */
	public void sendMediaStream(String path, int port, String media)
			throws MediaException {
		if (transmiter == null) {
			transmiter = new AudioTransmitter(media, path, port);
		}
		// SessionDescription sdp =
		// SdpMessageParser.getSessionDescription(sdp_string);
		// 検証
		// TODO

	}

	/**
	 * オーディオの受信を止める。
	 * 
	 */
	public void stopReceivingMediaStream() {
		receiver.stop();
	}

	/**
	 * オーディオの送信をやめる。
	 * 
	 */
	public void stopSendingMediaStream() {
		transmiter.close();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.iobb.ibaraki.media.MediaPlayer#isPlaying()
	 */
	public boolean isPlaying() {
		return receiver.isPlaying();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.iobb.ibaraki.media.MediaPlayer#getTransmitterController()
	 */
	public Component getTransmitterController() {
		return transmiter.getControlPanelComponent();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.iobb.ibaraki.media.MediaPlayer#getReceiverController()
	 */
	public Component getReceiverController() {
		return receiver.getControlPanelComponent();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.iobb.ibaraki.media.MediaPlayer#isSending()
	 */
	public boolean isSending() {
		return transmiter.isPlaying();
	}

	public static void main(String[] args) {
		MediaPlayer receiver = null;
		try {
			receiver = new AudioPlayer();
		} catch (IOException e1) {
			return;
		}
		String sdp_string = "";
		try {
			FileReader fr = new FileReader("03.sdp");
			BufferedReader br = new BufferedReader(fr);
			StringBuilder builder = new StringBuilder();
			int i = 0;
			while ((i = br.read()) != -1) {
				char c = (char) i;
				builder.append(c);
			}

			sdp_string = builder.toString();

		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			// receiver.receiveMedia(sdp_string);
			Frame frame = new Frame("test");
			frame.pack();
			frame.setVisible(true);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
