/*
 *
 * Copyright (C) 2013 www.yaacc.de 
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package de.yaacc.upnp.server;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.http.ConnectionClosedException;
import org.apache.http.HttpException;
import org.apache.http.HttpServerConnection;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpService;
import org.apache.http.protocol.ResponseConnControl;
import org.apache.http.protocol.ResponseContent;
import org.apache.http.protocol.ResponseDate;
import org.apache.http.protocol.ResponseServer;

import org.fourthline.cling.binding.annotations.AnnotationLocalServiceBinder;
import org.fourthline.cling.model.DefaultServiceManager;
import org.fourthline.cling.model.ValidationError;
import org.fourthline.cling.model.ValidationException;
import org.fourthline.cling.model.meta.DeviceDetails;
import org.fourthline.cling.model.meta.DeviceIdentity;
import org.fourthline.cling.model.meta.Icon;
import org.fourthline.cling.model.meta.LocalDevice;
import org.fourthline.cling.model.meta.LocalService;
import org.fourthline.cling.model.meta.ManufacturerDetails;
import org.fourthline.cling.model.meta.ModelDetails;
import org.fourthline.cling.model.meta.UDAVersion;
import org.fourthline.cling.model.types.DLNACaps;
import org.fourthline.cling.model.types.DLNADoc;
import org.fourthline.cling.model.types.UDADeviceType;
import org.fourthline.cling.model.types.UDN;
import org.fourthline.cling.protocol.async.SendingNotificationAlive;
import org.fourthline.cling.support.avtransport.AbstractAVTransportService;
import org.fourthline.cling.support.connectionmanager.ConnectionManagerService;
import org.fourthline.cling.support.model.Protocol;
import org.fourthline.cling.support.model.ProtocolInfo;
import org.fourthline.cling.support.model.ProtocolInfos;
import org.fourthline.cling.support.renderingcontrol.AbstractAudioRenderingControl;
import org.fourthline.cling.support.xmicrosoft.AbstractMediaReceiverRegistrarService;

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import de.yaacc.R;
import de.yaacc.upnp.UpnpClient;
import de.yaacc.upnp.server.avtransport.YaaccAVTransportService;
import de.yaacc.upnp.server.contentdirectory.YaaccContentDirectory;
import de.yaacc.util.NotificationId;

/**
 * A simple local upnp server implementation. This class encapsulate the creation
 * and registration of local upnp services. it is implemented as a android
 * service in order to run in background
 * 
 * @author Tobias Schoene (openbit)
 */
public class YaaccUpnpServerService extends Service {

	private static final String UDN_ID = "35"
			+ // we make this look like a valid IMEI
			Build.BOARD.length() % 10 + Build.BRAND.length() % 10 + Build.CPU_ABI.length() % 10 + Build.DEVICE.length() % 10 + Build.DISPLAY.length()
			% 10 + Build.HOST.length() % 10 + Build.ID.length() % 10 + Build.MANUFACTURER.length() % 10 + Build.MODEL.length() % 10
			+ Build.PRODUCT.length() % 10 + Build.TAGS.length() % 10 + Build.TYPE.length() % 10 + Build.USER.length() % 10;

	public static int PORT = 4711;

	private LocalDevice localServer;
	private LocalDevice localRenderer;

	// make preferences available for the whole service, since there might be
	// more things to configure in the future
	SharedPreferences preferences;

	public static final String MEDIA_SERVER_UDN_ID = UDN_ID;

	public static final String MEDIA_RENDERER_UDN_ID = UDN_ID + "-1";

	private UpnpClient upnpClient;

	private boolean watchdog;

	private RequestListenerThread httpServer;
	
	private boolean initialized=false;

	/*
	 * (non-Javadoc)
	 * 
	 * @see android.app.Service#onBind(android.content.Intent)
	 */
	@Override
	public IBinder onBind(Intent intent) {
		Log.d(this.getClass().getName(), "On Bind");
		// do nothing
		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see android.app.Service#onStart(android.content.Intent, int)
	 */
	@Override
	public void onStart(Intent intent, int startid) {

		// when the service starts, the preferences are initialized
		preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());

		if (getUpnpClient() == null) {
			setUpnpClient(new UpnpClient());
		}
		// the footprint of the onStart() method must be small
		// otherwise android will kill the service
		// in order of this circumstance we have to initialize the service
		// asynchronous
		Thread initializationThread = new Thread(new Runnable() {

			@Override
			public void run() {
				initialize();
			}
		});
		initializationThread.start();
		showNotification();
		Log.d(this.getClass().getName(), "End On Start");

	}

	@Override
	public void onDestroy() {
		Log.d(this.getClass().getName(), "Destroying the service");
		if (getUpnpClient() != null) {
			if (localServer != null) {
				getUpnpClient().localDeviceRemoved(getUpnpClient().getRegistry(), localServer);
				localServer = null;
			}
			if (localRenderer != null) {
				getUpnpClient().localDeviceRemoved(getUpnpClient().getRegistry(), localRenderer);
				localRenderer = null;
			}

		}
		if (httpServer != null) {
			try {
				httpServer.serversocket.close();
			} catch (IOException e) {
				Log.e(this.getClass().getName(), "Error while closing http request thread", e);
			}
		}
		cancleNotification();
		super.onDestroy();
	}

	/**
	 * Displays the notification.
	 */
	private void showNotification() {
		Intent notificationIntent = new Intent(this, YaaccUpnpServerControlActivity.class);
		PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
		NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this).setOngoing(true).setSmallIcon(R.drawable.ic_launcher)
				.setContentTitle("Yaacc Upnp Server")
				.setContentText(preferences.getString(getApplicationContext().getString(R.string.settings_local_server_name_key), ""));
		mBuilder.setContentIntent(contentIntent);
		NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
		// mId allows you to update the notification later on.
		mNotificationManager.notify(NotificationId.UPNP_SERVER.getId(), mBuilder.build());
	}

	/**
	 * Cancels the notification.
	 */
	private void cancleNotification() {
		NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
		// mId allows you to update the notification later on.
		mNotificationManager.cancel(NotificationId.UPNP_SERVER.getId());

	}

	/**
	 * 
	 */
	private void initialize() {
		this.initialized = false;
		if (!getUpnpClient().isInitialized()) {
			getUpnpClient().initialize(getApplicationContext());
			watchdog = false;
			new Timer().schedule(new TimerTask() {

				@Override
				public void run() {
					watchdog = true;
				}
			}, 30000L); // 30 sec. watchdog

			while (!getUpnpClient().isInitialized() && !watchdog) {
				// wait for upnpClient initialization
			}
		}
		if (getUpnpClient().isInitialized()) {
			if (preferences.getBoolean(getApplicationContext().getString(R.string.settings_local_server_provider_chkbx), false)) {
				if (localServer == null) {
					localServer = createMediaServerDevice();
				}
				getUpnpClient().getRegistry().addDevice(localServer);

				createHttpServer();
			}

			if (preferences.getBoolean(getApplicationContext().getString(R.string.settings_local_server_receiver_chkbx), false)) {
				if (localRenderer == null) {
					localRenderer = createMediaRendererDevice();
				}
				getUpnpClient().getRegistry().addDevice(localRenderer);
			}
			this.initialized = true;
		} else {
			throw new IllegalStateException("UpnpClient is not initialized!");
		}

		startUpnpAliveNotifications();
	}

	/**
	 * creates a http request thread
	 */
	private void createHttpServer() {
		// Create a HttpService for providing content in the network.
		try {

			httpServer = new RequestListenerThread(getApplicationContext());
			httpServer.start();

		} catch (BindException e) {
			Log.w(this.getClass().getName(), "Server already running");
		} catch (IOException e) {
			// FIXME Ignored right error handling on rebind needed
			Log.w(this.getClass().getName(), "ContentProvider can not be initialized!", e);
			// throw new
			// IllegalStateException("ContentProvider can not be initialized!",
			// e);
		}
	}

	/**
	 * start sending periodical upnp alive notifications.
	 */
	private void startUpnpAliveNotifications() {
		int upnpNotificationFrequency = getUpnpNotificationFrequency();
		if (upnpNotificationFrequency != -1 && preferences.getBoolean(getString(R.string.settings_local_server_chkbx), false)) {
			new Timer().schedule(new TimerTask() {
				@Override
				public void run() {
					Log.d(YaaccUpnpServerService.this.getClass().getName(), "Sending upnp alive notivication");
					SendingNotificationAlive sendingNotificationAlive = null;
					if (localServer != null) {
						sendingNotificationAlive = new SendingNotificationAlive(getUpnpClient().getRegistry().getUpnpService(), localServer);
						sendingNotificationAlive.run();
					}
					if (localRenderer != null) {
						sendingNotificationAlive = new SendingNotificationAlive(getUpnpClient().getRegistry().getUpnpService(), localRenderer);
                        sendingNotificationAlive.run();
					}
                    startUpnpAliveNotifications();
				}
			}, upnpNotificationFrequency);

		}
	}

	/**
	 * the time between two upnp alive notifications. -1 if never send a
	 * notification
	 * 
	 * @return the time
	 */
	private int getUpnpNotificationFrequency() {
		return Integer.parseInt(preferences.getString(getUpnpClient().getContext().getString(R.string.settings_sending_upnp_alive_interval_key), "5000"));
	}

	/**
	 * Create a local upnp renderer device
	 * 
	 * @return the device
	 */
	private LocalDevice createMediaRendererDevice() {
		LocalDevice device;
		String versionName;
		Log.d(this.getClass().getName(), "Create MediaRenderer with ID: " + MEDIA_SERVER_UDN_ID);
		try {
			versionName = getApplicationContext().getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), 0).versionName;
		} catch (NameNotFoundException ex) {
			Log.e(this.getClass().getName(), "Error while creating device", ex);
			versionName = "??";
		}
		try {
			device = new LocalDevice(new DeviceIdentity(new UDN(MEDIA_RENDERER_UDN_ID)), new UDADeviceType("MediaRenderer",3),
			// Used for shown name: first part of ManufactDet, first
			// part of ModelDet and version number
					new DeviceDetails("YAACC - MediaRenderer (" + getLocalServerName() + ")",
							new ManufacturerDetails("yaacc", "http://www.yaacc.de"), 
							new ModelDetails(getLocalServerName() + "-Renderer", "Free Android UPnP AV MediaRender, GNU GPL", versionName), 
							new DLNADoc[]{
                        		new DLNADoc("DMS", DLNADoc.Version.V1_5),
                        		new DLNADoc("M-DMS", DLNADoc.Version.V1_5)
							},
							new DLNACaps(new String[] {"av-upload", "image-upload", "audio-upload"})), createDeviceIcons(), createMediaRendererServices(),null);

			return device;
		} catch (ValidationException e) {
            for (ValidationError validationError : e.getErrors()) {
                Log.d(getClass().getCanonicalName(), validationError.toString());
            }
                throw new IllegalStateException("Exception during device creation", e);
		}

	}

	/**
	 * Create a local upnp renderer device
	 * 
	 * @return the device
	 */
	private LocalDevice createMediaServerDevice() {
		// https://bitbucket.org/longkerdandy/chii2/src/
		LocalDevice device;
		String versionName;
		Log.d(this.getClass().getName(), "Create MediaServer whith ID: " + MEDIA_SERVER_UDN_ID);
		try {
			versionName = getApplicationContext().getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), 0).versionName;
		} catch (NameNotFoundException ex) {
			Log.e(this.getClass().getName(), "Error while creating device", ex);
			versionName = "??";
		}
		try {

			// Yaacc Details
			// Used for shown name: first part of ManufactDet, first
			// part of ModelDet and version number
			DeviceDetails yaaccDetails = new DeviceDetails(
                    "YAACC - MediaServer(" + getLocalServerName() + ")", new ManufacturerDetails("yaacc.de",
					"http://www.yaacc.de"), new ModelDetails(getLocalServerName() + "-MediaServer", "Free Android UPnP AV MediaServer, GNU GPL",
					versionName));

			DeviceIdentity identity = new DeviceIdentity(new UDN(MEDIA_SERVER_UDN_ID));

			device = new LocalDevice(identity, new UDADeviceType("MediaServer"), yaaccDetails, createDeviceIcons(), createMediaServerServices());

			return device;
		} catch (ValidationException e) {
			Log.e(this.getClass().getName(), "Exception during device creation", e);
			Log.e(this.getClass().getName(), "Exception during device creation Errors:" + e.getErrors());
			throw new IllegalStateException("Exception during device creation", e);
		}

	}

	private Icon[] createDeviceIcons() {
		Drawable drawable = getResources().getDrawable(R.drawable.yaacc120_jpg);
		Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
		ByteArrayOutputStream stream = new ByteArrayOutputStream();
		bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
		//
		// return new Icon("image/png", 48, 48, 24,
		// URI.create("/icon.png"),stream.toByteArray());
		ArrayList<Icon> icons = new ArrayList<Icon>();

		icons.add(new Icon("image/jpeg", 120, 120, 24, "yaacc120.jpg", getIconAsByteArray(R.drawable.yaacc120_jpg)));
		icons.add(new Icon("image/jpeg", 48, 48, 24, "yaacc48.jpg", getIconAsByteArray(R.drawable.yaacc48_jpg)));
		icons.add(new Icon("image/jpeg", 32, 32, 24, "yaacc32.jpg", getIconAsByteArray(R.drawable.yaacc32_jpg)));
		icons.add(new Icon("image/bmp", 120, 120, 24, "yaacc120_24.bmp", getIconAsByteArray(R.drawable.yaacc120_24_bmp)));
		icons.add(new Icon("image/png", 120, 120, 24, "yaacc120_24.png", getIconAsByteArray(R.drawable.yaacc120_24_png)));
		icons.add(new Icon("image/bmp", 120, 120, 8, "yaacc120_8.bmp", getIconAsByteArray(R.drawable.yaacc120_8_bmp)));
		icons.add(new Icon("image/png", 120, 120, 8, "yaacc120_8.png", getIconAsByteArray(R.drawable.yaacc120_8_png)));
		icons.add(new Icon("image/png", 48, 48, 24, "yaacc48_24.bmp", getIconAsByteArray(R.drawable.yaacc48_24_bmp)));
		icons.add(new Icon("image/png", 48, 48, 24, "yaacc48_24.png", getIconAsByteArray(R.drawable.yaacc48_24_png)));
		icons.add(new Icon("image/bmp", 48, 48, 8, "yaacc48_8.bmp", getIconAsByteArray(R.drawable.yaacc48_8_bmp)));
		icons.add(new Icon("image/png", 48, 48, 8, "yaacc48_8.png", getIconAsByteArray(R.drawable.yaacc48_8_png)));
		icons.add(new Icon("image/bmp", 32, 32, 24, "yaacc32_24.bmp", getIconAsByteArray(R.drawable.yaacc32_24_bmp)));
		icons.add(new Icon("image/png", 32, 32, 24, "yaacc32_24.png", getIconAsByteArray(R.drawable.yaacc32_24_png)));
		icons.add(new Icon("image/bmp", 32, 32, 8, "yaacc32_8.bmp", getIconAsByteArray(R.drawable.yaacc32_8_bmp)));
		icons.add(new Icon("image/png", 32, 32, 8, "yaacc32_8.png", getIconAsByteArray(R.drawable.yaacc32_8_png)));
		return icons.toArray(new Icon[icons.size()]);
	}

	private String getLocalServerName() {
		return preferences.getString(getApplicationContext().getString(R.string.settings_local_server_name_key), "Yaacc");
	}

	/**
	 * Create the services provided by the server device
	 * 
	 * @return the services
	 */
	private LocalService<?>[] createMediaServerServices() {
		List<LocalService<?>> services = new ArrayList<LocalService<?>>();
		services.add(createContentDirectoryService());
		services.add(createServerConnectionManagerService());
		services.add(createMediaReceiverRegistrarService());
		return services.toArray(new LocalService[] {});
	}

	/**
	 * Create the renderer services provided by the device
	 * 
	 * @return the services
	 */
	private LocalService<?>[] createMediaRendererServices() {
		List<LocalService<?>> services = new ArrayList<LocalService<?>>();
		services.add(createAVTransportService());
		services.add(createRendererConnectionManagerService());
		services.add(createRenderingControl());
		return services.toArray(new LocalService[] {});
	}

	/**
	 * Creates an ContentDirectoryService. The content directory includes all
	 * Files of the MediaStore.
	 * 
	 * @return The ContenDiractoryService.
	 */
	@SuppressWarnings("unchecked")
	private LocalService<YaaccContentDirectory> createContentDirectoryService() {
		LocalService<YaaccContentDirectory> contentDirectoryService = new AnnotationLocalServiceBinder().read(YaaccContentDirectory.class);
		contentDirectoryService.setManager(new DefaultServiceManager<YaaccContentDirectory>(contentDirectoryService, null) {

            @Override
            protected int getLockTimeoutMillis() {
                return 1000;
            }

			@Override
			protected YaaccContentDirectory createServiceInstance() throws Exception {
				return new YaaccContentDirectory(getApplicationContext());
			}
		});
		return contentDirectoryService;
	}

	/**
	 * creates an AVTransportService
	 * 
	 * @return the service
	 */
	@SuppressWarnings("unchecked")
	private LocalService<YaaccAVTransportService> createAVTransportService() {
		LocalService<YaaccAVTransportService> avTransportService = new AnnotationLocalServiceBinder().read(YaaccAVTransportService.class);
 		avTransportService.setManager(new DefaultServiceManager<YaaccAVTransportService>(avTransportService, null) {
            @Override
            protected int getLockTimeoutMillis() {
                return 1000;
            }

            @Override
			protected YaaccAVTransportService createServiceInstance() throws Exception {
				return new YaaccAVTransportService(getUpnpClient());
			}
		});
		return avTransportService;
	}

	private LocalService<AbstractAudioRenderingControl> createRenderingControl() {
		LocalService<AbstractAudioRenderingControl> renderingControlService = new AnnotationLocalServiceBinder()
				.read(AbstractAudioRenderingControl.class);
		renderingControlService.setManager(new DefaultServiceManager<AbstractAudioRenderingControl>(renderingControlService, null) {
            @Override
            protected int getLockTimeoutMillis() {
                return 1000;
            }

			@Override
			protected AbstractAudioRenderingControl createServiceInstance() throws Exception {
				return new YaaccAudioRenderingControlService(getUpnpClient());
			}
		});
		return renderingControlService;
	}

	private LocalService<AbstractMediaReceiverRegistrarService> createMediaReceiverRegistrarService() {
		LocalService<AbstractMediaReceiverRegistrarService> service = new AnnotationLocalServiceBinder()
				.read(AbstractMediaReceiverRegistrarService.class);
		service.setManager(new DefaultServiceManager<AbstractMediaReceiverRegistrarService>(service, null) {

            @Override
            protected int getLockTimeoutMillis() {
                return 1000;
            }

			@Override
			protected AbstractMediaReceiverRegistrarService createServiceInstance() throws Exception {
				return new YaaccMediaReceiverRegistrarService(getUpnpClient());
			}
		});
		return service;
	}

	/**
	 * creates a ConnectionManagerService.
	 * 
	 * @return the service
	 */
	@SuppressWarnings("unchecked")
	private LocalService<ConnectionManagerService> createServerConnectionManagerService() {
		LocalService<ConnectionManagerService> service = new AnnotationLocalServiceBinder().read(ConnectionManagerService.class);
		final ProtocolInfos sourceProtocols = getSourceProtocolInfos();	
		
		service.setManager(new DefaultServiceManager<ConnectionManagerService>(service, ConnectionManagerService.class) {

            @Override
            protected int getLockTimeoutMillis() {
                return 1000;
            }

			@Override
			protected ConnectionManagerService createServiceInstance() throws Exception {
				return new ConnectionManagerService(sourceProtocols, null);
			}
		});

		return service;
	}

	/**
	 * creates a ConnectionManagerService.
	 * 
	 * @return the service
	 */
	@SuppressWarnings("unchecked")
	private LocalService<ConnectionManagerService> createRendererConnectionManagerService() {
		LocalService<ConnectionManagerService> service = new AnnotationLocalServiceBinder().read(ConnectionManagerService.class);		
		final ProtocolInfos sinkProtocols = getSinkProtocolInfos();	
		service.setManager(new DefaultServiceManager<ConnectionManagerService>(service, ConnectionManagerService.class) {

            @Override
            protected int getLockTimeoutMillis() {
                return 1000;
            }

			@Override
			protected ConnectionManagerService createServiceInstance() throws Exception {
				return new ConnectionManagerService(null, sinkProtocols);
			}
		});

		return service;
	}
	
	/**
	 * @return
	 */
	private ProtocolInfos getSourceProtocolInfos() {
		return new ProtocolInfos(
				new ProtocolInfo("http-get:*:audio:*"),
				new ProtocolInfo("http-get:*:audio/mpeg:*"),
				new ProtocolInfo("http-get:*:audio/x-mpegurl:*"),
				new ProtocolInfo("http-get:*:audio/x-wav:*"),
				new ProtocolInfo("http-get:*:audio/mpeg:DLNA.ORG_PN=MP3"),
				new ProtocolInfo("http-get:*:audio/mpeg:DLNA.ORG_PN=MP2"),
				new ProtocolInfo("http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE"),
				new ProtocolInfo("http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO"),
				new ProtocolInfo("http-get:*:audio/x-flac:*"),
				new ProtocolInfo("http-get:*:audio/x-aiff:*"),
				new ProtocolInfo("http-get:*:audio/x-ogg:*"),
				new ProtocolInfo("http-get:*:audio/wav:*"),
				new ProtocolInfo("http-get:*:audio/x-ape:*"),
				new ProtocolInfo("http-get:*:audio/x-m4a:*"),
				new ProtocolInfo("http-get:*:audio/x-m4b:*"),
				new ProtocolInfo("http-get:*:audio/x-wavpack:*"),
				new ProtocolInfo("http-get:*:audio/x-musepack:*"),
				new ProtocolInfo("http-get:*:audio/basic:*"),
				new ProtocolInfo("http-get:*:audio/L16;rate=11025;channels=2:DLNA.ORG_PN=LPCM"),
				new ProtocolInfo("http-get:*:audio/L16;rate=22050;channels=2:DLNA.ORG_PN=LPCM"),
				new ProtocolInfo("http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM"),
				new ProtocolInfo("http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM"),
				new ProtocolInfo("http-get:*:audio/L16;rate=88200;channels=2:DLNA.ORG_PN=LPCM"),
				new ProtocolInfo("http-get:*:audio/L16;rate=96000;channels=2:DLNA.ORG_PN=LPCM"),
				new ProtocolInfo("http-get:*:audio/L16;rate=192000;channels=2:DLNA.ORG_PN=LPCM"),				
				new ProtocolInfo(Protocol.HTTP_GET, ProtocolInfo.WILDCARD, "audio/mpeg", "DLNA.ORG_PN=MP3;DLNA.ORG_OP=01"), 
				new ProtocolInfo("http-get:*:audio/mpeg:DLNA.ORG_PN=MP3"), 
				new ProtocolInfo("http-get:*:audio/mpeg:DLNA.ORG_PN=MP3X"), 
				new ProtocolInfo("http-get:*:audio/x-ms-wma:*"), 
				new ProtocolInfo("http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE"), 
				new ProtocolInfo("http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAFULL"),
				new ProtocolInfo("http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAPRO"), 
				new ProtocolInfo("http-get:*:image/gif:*"),
				new ProtocolInfo("http-get:*:image/jpeg:*"),
				new ProtocolInfo("http-get:*:image/png:*"),
				new ProtocolInfo("http-get:*:image/x-ico:*"),
				new ProtocolInfo("http-get:*:image/x-ms-bmp:*"),
				new ProtocolInfo("http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG"),
				new ProtocolInfo("http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED"), 
				new ProtocolInfo("http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM"),
				new ProtocolInfo("http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN"), 
				new ProtocolInfo("http-get:*:image/x-ycbcr-yuv420:*"),
				new ProtocolInfo("http-get:*:video/mp4:*"),
				new ProtocolInfo("http-get:*:video/mpeg:*"),
				new ProtocolInfo("http-get:*:video/quicktime:*"),
				new ProtocolInfo("http-get:*:video/x-flc:*"),
				new ProtocolInfo("http-get:*:video/x-msvideo:*"),
				new ProtocolInfo(Protocol.HTTP_GET, ProtocolInfo.WILDCARD, "video/mpeg", "DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=01;DLNA.ORG_CI=0"),
				new ProtocolInfo("http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1"), 
				new ProtocolInfo("http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC"),
				new ProtocolInfo("http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC_XAC3"), 
				new ProtocolInfo("http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL"), 
				new ProtocolInfo("http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL_XAC3"),
				new ProtocolInfo("http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_PAL"), 
				new ProtocolInfo("http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_PAL_XAC3"), 
				new ProtocolInfo("http-get:*:video/wtv:*"), 
				new ProtocolInfo("http-get:*:video/x-ms-asf:DLNA.ORG_PN=MPEG4_P2_ASF_ASP_L4_SO_G726"), 
				new ProtocolInfo("http-get:*:video/x-ms-asf:DLNA.ORG_PN=MPEG4_P2_ASF_ASP_L5_SO_G726"), 
				new ProtocolInfo("http-get:*:video/x-ms-asf:DLNA.ORG_PN=MPEG4_P2_ASF_SP_G726"), 
				new ProtocolInfo("http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA"), 
				new ProtocolInfo("http-get:*:video/x-ms-wmv:*"),
				new ProtocolInfo("http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL"), 
				new ProtocolInfo("http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO"), 
				new ProtocolInfo("http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE"),
				new ProtocolInfo("http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL"), 
				new ProtocolInfo("http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO"), 
				new ProtocolInfo("http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVSPLL_BASE"),
				new ProtocolInfo("http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVSPML_BASE"), 
				new ProtocolInfo("http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVSPML_MP3"));
		
		
		
		
		
		
	}
	
	private ProtocolInfos getSinkProtocolInfos() {
		return new ProtocolInfos(
		new ProtocolInfo("http-get:*:*:*"),		
		new ProtocolInfo("http-get:*:audio/mkv:*"),
		new ProtocolInfo("http-get:*:audio/mpegurl:*"),
		new ProtocolInfo("http-get:*:audio/mpeg:*"),
		new ProtocolInfo("http-get:*:audio/mpeg3:*"),
		new ProtocolInfo("http-get:*:audio/mp3:*"),
		new ProtocolInfo("http-get:*:audio/mp4:*"),
		new ProtocolInfo("http-get:*:audio/basic:*"),
		new ProtocolInfo("http-get:*:audio/midi:*"),
		new ProtocolInfo("http-get:*:audio/ulaw:*"),
		new ProtocolInfo("http-get:*:audio/ogg:*"),
		new ProtocolInfo("http-get:*:audio/DVI4:*"),
		new ProtocolInfo("http-get:*:audio/G722:*"),
		new ProtocolInfo("http-get:*:audio/G723:*"),
		new ProtocolInfo("http-get:*:audio/G726-16:*"),
		new ProtocolInfo("http-get:*:audio/G726-24:*"),
		new ProtocolInfo("http-get:*:audio/G726-32:*"),
		new ProtocolInfo("http-get:*:audio/G726-40:*"),
		new ProtocolInfo("http-get:*:audio/G728:*"),
		new ProtocolInfo("http-get:*:audio/G729:*"),
		new ProtocolInfo("http-get:*:audio/G729D:*"),
		new ProtocolInfo("http-get:*:audio/G729E:*"),
		new ProtocolInfo("http-get:*:audio/GSM:*"),
		new ProtocolInfo("http-get:*:audio/GSM-EFR:*"),
		new ProtocolInfo("http-get:*:audio/L8:*"),
		new ProtocolInfo("http-get:*:audio/L16:*"),
		new ProtocolInfo("http-get:*:audio/LPC:*"),
		new ProtocolInfo("http-get:*:audio/MPA:*"),
		new ProtocolInfo("http-get:*:audio/PCMA:*"),
		new ProtocolInfo("http-get:*:audio/PCMU:*"),
		new ProtocolInfo("http-get:*:audio/QCELP:*"),
		new ProtocolInfo("http-get:*:audio/RED:*"),
		new ProtocolInfo("http-get:*:audio/VDVI:*"),
		new ProtocolInfo("http-get:*:audio/ac3:*"),
		new ProtocolInfo("http-get:*:audio/vorbis:*"),
		new ProtocolInfo("http-get:*:audio/speex:*"),
		new ProtocolInfo("http-get:*:audio/flac:*"),
		new ProtocolInfo("http-get:*:audio/x-flac:*"),
		new ProtocolInfo("http-get:*:audio/x-aiff:*"),
		new ProtocolInfo("http-get:*:audio/x-pn-realaudio:*"),
		new ProtocolInfo("http-get:*:audio/x-realaudio:*"),
		new ProtocolInfo("http-get:*:audio/x-wav:*"),
		new ProtocolInfo("http-get:*:audio/x-matroska:*"),
		new ProtocolInfo("http-get:*:audio/x-ms-wma:*"),
		new ProtocolInfo("http-get:*:audio/x-mpegurl:*"),
		new ProtocolInfo("http-get:*:application/x-shockwave-flash:*"),
		new ProtocolInfo("http-get:*:application/ogg:*"),
		new ProtocolInfo("http-get:*:application/sdp:*"),
		new ProtocolInfo("http-get:*:image/gif:*"),
		new ProtocolInfo("http-get:*:image/jpeg:*"),
		new ProtocolInfo("http-get:*:image/ief:*"),
		new ProtocolInfo("http-get:*:image/png:*"),
		new ProtocolInfo("http-get:*:image/tiff:*"),
		new ProtocolInfo("http-get:*:video/avi:*"),
		new ProtocolInfo("http-get:*:video/divx:*"),
		new ProtocolInfo("http-get:*:video/mpeg:*"),
		new ProtocolInfo("http-get:*:video/fli:*"),
		new ProtocolInfo("http-get:*:video/flv:*"),
		new ProtocolInfo("http-get:*:video/quicktime:*"),
		new ProtocolInfo("http-get:*:video/vnd.vivo:*"),
		new ProtocolInfo("http-get:*:video/vc1:*"),
		new ProtocolInfo("http-get:*:video/ogg:*"),
		new ProtocolInfo("http-get:*:video/mp4:*"),
		new ProtocolInfo("http-get:*:video/mkv:*"),
		new ProtocolInfo("http-get:*:video/BT656:*"),
		new ProtocolInfo("http-get:*:video/CelB:*"),
		new ProtocolInfo("http-get:*:video/JPEG:*"),
		new ProtocolInfo("http-get:*:video/H261:*"),
		new ProtocolInfo("http-get:*:video/H263:*"),
		new ProtocolInfo("http-get:*:video/H263-1998:*"),
		new ProtocolInfo("http-get:*:video/H263-2000:*"),
		new ProtocolInfo("http-get:*:video/MPV:*"),
		new ProtocolInfo("http-get:*:video/MP2T:*"),
		new ProtocolInfo("http-get:*:video/MP1S:*"),
		new ProtocolInfo("http-get:*:video/MP2P:*"),
		new ProtocolInfo("http-get:*:video/BMPEG:*"),
		new ProtocolInfo("http-get:*:video/xvid:*"),
		new ProtocolInfo("http-get:*:video/x-divx:*"),
		new ProtocolInfo("http-get:*:video/x-matroska:*"),
		new ProtocolInfo("http-get:*:video/x-ms-wmv:*"),
		new ProtocolInfo("http-get:*:video/x-ms-avi:*"),
		new ProtocolInfo("http-get:*:video/x-flv:*"),
		new ProtocolInfo("http-get:*:video/x-fli:*"),
		new ProtocolInfo("http-get:*:video/x-ms-asf:*"),
		new ProtocolInfo("http-get:*:video/x-ms-asx:*"),
		new ProtocolInfo("http-get:*:video/x-ms-wmx:*"),
		new ProtocolInfo("http-get:*:video/x-ms-wvx:*"),
		new ProtocolInfo("http-get:*:video/x-msvideo:*"),
		new ProtocolInfo("http-get:*:video/x-xvid:*"),
		new ProtocolInfo("http-get:*:audio/L16:*"),
		new ProtocolInfo("http-get:*:audio/mp3:*"),
		new ProtocolInfo("http-get:*:audio/x-mp3:*"),
		new ProtocolInfo("http-get:*:audio/mpeg:*"),
		new ProtocolInfo("http-get:*:audio/x-ms-wma:*"),
		new ProtocolInfo("http-get:*:audio/wma:*"),
		new ProtocolInfo("http-get:*:audio/mpeg3:*"),
		new ProtocolInfo("http-get:*:audio/wav:*"),
		new ProtocolInfo("http-get:*:audio/x-wav:*"),
		new ProtocolInfo("http-get:*:audio/ogg:*"),
		new ProtocolInfo("http-get:*:audio/x-ogg:*"),
		new ProtocolInfo("http-get:*:audio/musepack:*"),
		new ProtocolInfo("http-get:*:audio/x-musepack:*"),
		new ProtocolInfo("http-get:*:audio/flac:*"),
		new ProtocolInfo("http-get:*:audio/x-flac:*"),
		new ProtocolInfo("http-get:*:audio/mp4:*"),
		new ProtocolInfo("http-get:*:audio/m4a:*"),
		new ProtocolInfo("http-get:*:audio/aiff:*"),
		new ProtocolInfo("http-get:*:audio/x-aiff:*"),
		new ProtocolInfo("http-get:*:audio/basic:*"),
		new ProtocolInfo("http-get:*:audio/x-wavpack:*"),
		new ProtocolInfo("http-get:*:application/octet-stream:*"));
	}

	/**
	 * 
	 * Listener thread for http requests.
	 * 
	 */
	static class RequestListenerThread extends Thread {
		private ServerSocket serversocket;
		private BasicHttpParams params;
		private HttpService httpService;

		public RequestListenerThread(Context context) throws IOException, BindException {
			serversocket = new ServerSocket(PORT);
			params = new BasicHttpParams();
			params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 5000).setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
					.setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
					.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true).setParameter(CoreProtocolPNames.ORIGIN_SERVER, "HttpComponents/1.1");

			// Set up the HTTP protocol processor
			BasicHttpProcessor httpProcessor = new BasicHttpProcessor();
			httpProcessor.addInterceptor(new ResponseDate());
			httpProcessor.addInterceptor(new ResponseServer());
			httpProcessor.addInterceptor(new ResponseContent());
			httpProcessor.addInterceptor(new ResponseConnControl());

			// Set up the HTTP service
			this.httpService = new YaaccHttpService(httpProcessor, new DefaultConnectionReuseStrategy(), new DefaultHttpResponseFactory(), context);

		}

		@Override
		public void run() {
			Log.d(getClass().getName(), "Listening on port " + serversocket.getLocalPort());
			while (!Thread.interrupted()) {
				try {
					// Set up HTTP connection
					Socket socket = serversocket.accept();
					DefaultHttpServerConnection connection = new DefaultHttpServerConnection();
					Log.d(getClass().getName(), "Incoming connection from " + socket.getInetAddress());
					connection.bind(socket, params);
					// Start worker thread
					Thread workerThread = new WorkerThread(httpService, connection);
					workerThread.setDaemon(true);
					workerThread.start();
				} catch (InterruptedIOException ex) {
					break;
				} catch (IOException e) {
					Log.d(getClass().getName(), "I/O error initialising connection thread: ", e);
					break;
				}
			}
		}

	}

	static class WorkerThread extends Thread {

		private final HttpService httpservice;
		private final HttpServerConnection conn;

		public WorkerThread(final HttpService httpservice, final HttpServerConnection conn) {
			super();
			this.httpservice = httpservice;
			this.conn = conn;
		}

		@Override
		public void run() {
			Log.d(getClass().getName(), "New connection thread");
			try {
				Log.d(getClass().getName(), "conn.isOpen(): " + conn.isOpen());
				Log.d(getClass().getName(), "!Thread.interrupted(): " + !Thread.interrupted());
				while (!Thread.interrupted() && conn.isOpen()) {
                    conn.setSocketTimeout(500000); //not Timeout
					HttpContext context = new BasicHttpContext();
					httpservice.handleRequest(conn, context);
				}
			} catch (ConnectionClosedException ex) {
				Log.d(getClass().getName(), "Client closed connection", ex);
			} catch (IOException ex) {
				Log.d(getClass().getName(), "I/O error: ", ex);
			} catch (HttpException ex) {
				Log.d(getClass().getName(), "Unrecoverable HTTP protocol violation: ", ex);
			} finally {
				try {
					Log.d(getClass().getName(), "Shutdown connection!");
					conn.shutdown();
				} catch (IOException ignore) {
					// ignore it
					Log.d(getClass().getName(), "Error closing connection: ", ignore);
				}

			}
		}

	}

	// private boolean isYaaccUpnpServerServiceRunning() {
	// ActivityManager manager = (ActivityManager)
	// getSystemService(Context.ACTIVITY_SERVICE);
	// for (RunningServiceInfo service :
	// manager.getRunningServices(Integer.MAX_VALUE)) {
	// if (this.getClass().getName().equals(service.service.getClassName())) {
	// return true;
	// }
	// }
	// return false;
	// }

	private byte[] getIconAsByteArray(int drawableId) {

		Drawable drawable = getResources().getDrawable(drawableId);
		byte[] result = null;
		if (drawable != null) {
			Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
			ByteArrayOutputStream stream = new ByteArrayOutputStream();
			bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
			result = stream.toByteArray();
		}
		return result;
	}

	/**
	 * @return the upnpClient
	 */
	public UpnpClient getUpnpClient() {
		return upnpClient;
	}

	/**
	 * @param upnpClient the upnpClient to set
	 */
	private void setUpnpClient(UpnpClient upnpClient) {
		this.upnpClient = upnpClient;
	}

	/**
	 * @return the initialized
	 */
	public boolean isInitialized() {
		return initialized;
	}

	

}