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

import java.awt.Component;
import java.awt.Dimension;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
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.MediaEvent;
import javax.media.MediaException;
import javax.media.PlugInManager;
import javax.media.format.VideoFormat;
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.SdpMessageParser;
import net.sourceforge.jsdp.SessionDescription;

/**
 * @author 粕谷
 * 
 */
public class VideoPlayer extends AudioPlayer {

	protected int[] receivableJmfVideoFormats = new int[] {
	// sdp format // corresponding JMF Format
			SdpConstants.H263, // javax.media.format.VideoFormat.H263_RTP
			SdpConstants.JPEG, // javax.media.format.VideoFormat.JPEG_RTP
			SdpConstants.H261, // javax.media.format.VideoFormat.H261_RTP
	};

	private static int video_srcPort = 44444;
	private MediaReceiver receiver;

	MediaTransmitter transmitter;

	protected List<Integer> availableVideoFormats = new ArrayList<Integer>();

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

	public VideoPlayer(String host) throws IOException {
		this(host, video_srcPort);
	}

	public VideoPlayer(String host, int port) throws IOException {
		super(host);
		video_srcPort = port;
		init();
	}

	private void init() {
		// TODO
		// ここで設定ファイルから選べるようにしよう
		Arrays.sort(receivableJmfVideoFormats);
		for (int format : receivableJmfVideoFormats) {
			availableVideoFormats.add(format);
		}
	}

	public void addAvailableFormat(int payload) {
		availableVideoFormats.add(payload);
	}

	public int[] getReceivableFormats(String sdp_str) {
		return this.getReceivableFormats(sdp_str, false);
	}

	private int[] getReceivableFormats(String sdp_str, boolean only_video) {
		// 音声メディアフォーマットの取得
		int[] audio_formats = super.getReceivableFormats(sdp_str);

		// 動画メディアフォーマットの取得
		int[] supports = getSupportedFormats(sdp_str);
		Vector codecs = PlugInManager.getPlugInList(null, null,
				PlugInManager.CODEC);
		Set<Integer> vec = new HashSet<Integer>();
		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 VideoFormat) {
					VideoFormat f = (VideoFormat) format;
					for (int i = 0; i < supports.length; i++) {
						// 比較
						VideoFormat target = findCorrespondingVideoFormat(supports[i]);
						if (target == null) {
							// System.err.println("Can't resolve payload type:"
							// + supports[i]);
							continue;
						}
						// とりあえずエンコーディングがあっているのを探す
						if (target.isSameEncoding(f)) {
							vec.add(supports[i]);
						}
					}
				}
			}
		}
		int[] video_formats = null;
		int i = 0;
		if (!only_video) {
			video_formats = new int[audio_formats.length + vec.size()];
			for (i = 0; i < audio_formats.length; i++) {
				video_formats[i] = audio_formats[i];
			}
			for (Iterator iter = vec.iterator(); iter.hasNext(); i++) {
				int next = (Integer) iter.next();
				video_formats[i] = next;
			}
		} else {
			video_formats = new int[vec.size()];
			for (Iterator iter = vec.iterator(); iter.hasNext();) {
				int next = (Integer) iter.next();
				video_formats[i++] = next;
			}
		}
		Arrays.sort(video_formats);
		return video_formats;
	}

	private int[] getSupportedFormats(String sdp_str) {
		int[] supportedFormats = null;
		String[] temp = null;
		MediaDescription[] mediaDescriptions = SdpMessageParser
				.getSessionDescription(sdp_str).getMediaDescriptions();
		for (MediaDescription mediaDescription : mediaDescriptions) {
			Media media = mediaDescription.getMedia();
			// 動画メディアのみ
			if (media.getMediaType().equals("video")) {
				temp = media.getMediaFormats();
				break;
			}
		}
		supportedFormats = new int[temp.length];
		int i = 0;
		for (String str : temp) {
			supportedFormats[i++] = Integer.parseInt(str);
		}
		return supportedFormats;
	}

	protected VideoFormat findCorrespondingVideoFormat(int sdpFormat) {
		String format = null;
		switch (sdpFormat) {
		case SdpConstants.H263:
			format = VideoFormat.H263_RTP;
			break;
		case SdpConstants.JPEG:
			format = VideoFormat.JPEG_RTP;
			break;
		case SdpConstants.H261:
			format = VideoFormat.H261_RTP;
			break;
		case SdpConstants.MPV:
			format = VideoFormat.MPEG_RTP;
			break;
		default:
			return null;
		}
		return new VideoFormat(format);
	}

	public Component getTransmitterController() {
		if (transmitter == null) {
			return null;
		}
		return transmitter.getControlPanelComponent();
	}

	public Component getReceiverController() {
		if (receiver == null) {
			return null;
		}
		return receiver.getControlPanelComponent();
	}

	public Component getVisualComponent() {
		if (receiver == null) {
			return null;
		}
		return receiver.getVisualComponent();
	}

	public String getRequestSessionDescription(int version, String userName,
			String sessionName) {
		String sdp_str = super.getRequestSessionDescription(version, userName,
				sessionName);

		if (!availableVideoFormats.isEmpty()) {
			MediaDescription[] md = new MediaDescription[1];
			String name = "rtpmap";
			String value;
			Iterator<Integer> iter = availableVideoFormats.iterator();
			int payload = iter.next();
			Media media = new Media("video", video_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);
			}
			if (md != null) {
				for (MediaDescription mediaDescriptor : md)
					sdp_str += mediaDescriptor.encode();
			}
		}
		return sdp_str;
	}

	private String getAttributeValue(int payload) {
		switch (payload) {
		case SdpConstants.H261:
			return "H261/90000";
		case SdpConstants.H263:
			return "H263/90000";
		case SdpConstants.MPV:
			return "MPV/90000";
		case SdpConstants.JPEG:
			return "JPEG/90000";
		default:
			return null;
		}
	}

	public String getResponseSessionDescription(String sdp_string, int version,
			String userName, String sessionName) {
		String sdp = super.getResponseSessionDescription(sdp_string, version,
				userName, sessionName);
		int[] supports = getReceivableFormats(sdp_string, true);
		int payload = supports[0];
		String name = "rtpmap";
		String value;
		MediaDescription[] md = new MediaDescription[1];
		// メディア記述の追加
		Media media = new Media("video", video_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];
			media.addMediaFormat(payload + "");
			value = payload + " " + getAttributeValue(payload);
			attr = new Attribute(name, value);
			md[0].addAttribute(attr);
		}
		StringBuilder builder = new StringBuilder();
		builder.append(sdp);
		builder.append(md[0].encode());

		return builder.toString();
	}

	public boolean isAvailable(String sdp_string) {
		boolean a = super.isAvailable(sdp_string);
		int[] formats = getReceivableFormats(sdp_string, true);
		boolean v = (formats != null && formats.length != 0) ? true : false;
		return a & v;
	}

	public boolean isPlaying() {
		if (this.receiver == null)
			return false;
		return this.receiver.isPlaying();
	}

	public boolean isSending() {
		if (transmitter == null)
			return false;
		return transmitter.isPlaying();
	}

	public void receiveMediaStream(String sdp_string) throws MediaException {
		SessionDescription sdp = SdpMessageParser
				.getSessionDescription(sdp_string);
		try {
			if (receiver == null) {
				this.receiver = new MediaReceiver(sdp);
			} else {
				this.receiver.setNewSessionDescription(sdp);
			}
		} catch (IOException e) {
			throw new MediaException(e.getMessage());
		}
		receiver.play();
	}

	public void sendMediaStream(String sdp_string) throws MediaException,
			IOException {
		SessionDescription sdp = SdpMessageParser
				.getSessionDescription(sdp_string);
		Connection c = sdp.getConnection();
		InetAddress addr = InetAddress.getByName(c.getAddress());
		MediaDescription[] mds = sdp.getMediaDescriptions();
		SessionInformation[] info = new SessionInformation[mds.length];
		int i = 0;
		for (MediaDescription md : mds) {
			Media media = md.getMedia();
			String[] formats = media.getMediaFormats();
			// 一番目のフォーマットを使う
			int payload = Integer.parseInt(formats[0]);
			int port = media.getPort();
			info[i] = new SessionInformation(addr.getHostAddress(), port);
			Format format = findCorrespondingVideoFormat(payload);
			if (format == null) {
				format = findCorrespondingAudioFormat(payload);
			}
			info[i].format = format;
			i++;
		}
		if (this.transmitter == null) {
		//	transmitter = new MediaTransmitter(info);
		} else {
			transmitter.setNewSessionDescription(sdp);
		}
		transmitter.play();
	}

	public void stopReceivingMediaStream() {
		if (receiver != null) {
			receiver.stop();
			receiver.close();
		} else {
			throw new NullPointerException("Receiver is null.");
		}
	}

	public void stopSendingMediaStream() {
		if (this.transmitter == null) {
			return;
		}
		transmitter.close();
	}

	
	
	
	class VideoJmfFormats {
		String encoding;

		float frameRate;

		Dimension dim;

		int maxDataLength;

		Class type;

		private VideoJmfFormats(String encoding, Dimension size,
				int maxDataLength, Class dataType, float frameRate) {
			this.encoding = encoding;
			this.dim = size;
			this.maxDataLength = maxDataLength;
			this.type = dataType;
			this.frameRate = frameRate;
		}

		private VideoJmfFormats(String encoding) {
			this.encoding = encoding;

		}
	}

	/**
	 * @param args
	 * @throws IOException
	 */
	public static void main(String[] args) {
		VideoPlayer vp;
		try {
			vp = new VideoPlayer();

			SessionDescription sdp = vp.getRequestSessionDescription("name",
					"-");
			//System.out.println(sdp);
			//vp.getResponseSessionDescription(sdp);
			//vp.sendMediaStream(sdp.toString());
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}
