/* 
 * Copyright 2015 by AVM GmbH <info@avm.de>
 *
 * This software contains free software; you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License ("License") as 
 * published by the Free Software Foundation  (version 3 of the License). 
 * This software 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 copy of the 
 * License you received along with this software for more details.
 */

package de.avm.android.fritzapp.util;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Locale;

import de.avm.android.fritzapp.sipua.phone.CallerInfo;
import de.avm.android.tr064.model.ContactEmail;
import de.avm.android.tr064.model.ContactNumber;
import de.avm.android.tr064.model.IContactable;
import de.avm.fundamentals.logger.FileLog;

import android.content.Context;
import android.database.Cursor;
import android.net.Uri;

/**
 * Wrapper for contacts API
 *
 *	This class dispatches to access old or new contacts API. 
 */
public abstract class LocalContacts
{
	private static final String TAG = "LocalContacts";
	
	/**
	 * Contact info to be used with class LocalContacts 
	 */
	public class Contact
	{
		public Uri mUri = null;
		private String mName = "";
		public ArrayList<IContactable> mAddresses = new ArrayList<IContactable>();
		
		public String getName()
		{
			return mName;
		}
		
		public void setName(String name)
		{
			mName = (name == null) ? "" : name;
		}
	}
	
	public interface OnExportContactListener
	{
		/**
		 * Called once for each exported contact
		 * @param xmlFragment the contact's XML
		 * @throws Exception could be raised in listener
		 */
		void onExportContact(String xmlFragment) throws Exception;
	}
	
	private static LocalContacts mInstance = null;
	private StringBuilder mXmlBuffer = null; 
	
	/**
	 * Gets this single instance of LocalContacts class
	 * 
	 * @return the instance
	 */
	public static LocalContacts getInstance()
	{
		if (mInstance == null)
		{
			try
			{
				// Support for old API removed (class LocalContactsCupcake)
				mInstance = new LocalContactsEclair();
			}
			catch (Exception e)
			{
				throw new IllegalStateException(e);
			}
		}
		
		return mInstance;
	}
	
	/**
	 * URI to be used for contacts DB
	 * 
	 * @return the URI
	 */
	public abstract Uri getContentUri();
	
	/**
	 * Query default selection
	 * 
	 * @return selection string
	 */
	public abstract String getSelection();
	
	/**
	 * URI to be used with
	 * getSummaryItem(Cursor cursor)
	 * 
	 * @return the URI
	 */
	public abstract Uri getSummaryUri();
	
	/**
	 * Query projection to be used with
	 * getSummaryItem(Cursor cursor)
	 * 
	 * @return projection array
	 */
	public abstract String[] getSummaryProjection();
	
	/**
	 * Query default order
	 * 
	 * @return order string
	 */
	public abstract String getSummaryOrder();

	
	/**
	 * Gets Name, URI and primary number from contact cursor
	 * 
	 * @param cursor
	 * @return contact info
	 */
	public abstract Contact getSummaryItem(Context context, Cursor cursor);
	
	/**
	 * Gets Name, URI and all numbers and email addresses from contact URI
	 * 
	 * @param context context for database access
	 * @param cursor
	 * @return contact info or null if not found
	 * @throws IllegalArgumentException contact not found or invisible
	 */
	public abstract Contact getContact(Context context, Cursor cursor)
			throws IllegalArgumentException;
	
	/**
	 * Gets Name, URI and all numbers and email addresses from contact URI
	 * 
	 * @param context context for database access
	 * @param contactUri URI of contact
	 * @return contact info or null if not found
	 * @throws IllegalArgumentException contact not found or invisible
	 */
	public abstract Contact getContact(Context context, Uri contactUri)
			throws IllegalArgumentException;
	
	/**
	 * Marks a contact as already contacted
	 * @param contactUri URI of contact
	 */
	public abstract void markAsContacted(Context context, Uri contactUri);
	
	/**
	 * Opens an InputStream for the contacts's default photo and returns
	 * the photo as a byte stream. If there is not photo null will be returned.
	 * 
	 * @param context context for database access
	 * @param contactUri the contact whose photo should be used
	 * @return an InputStream of the photo, or null if no photo is present
	 */
	public abstract InputStream openContactPhotoInputStream (Context context,
			Uri contactUri);
	
	/**
	 * Gets phone number label to display from type/label
	 * 
	 * @param context
	 * @param type
	 * @param label
	 * @return
	 */
	public abstract CharSequence getDisplayPhoneLabel(Context context,
			int type, CharSequence label);
	
	/**
	 * Gets URI to query for a number
	 * 
	 * @param number the number to filter
	 * @return the URI to query with
	 */
	public abstract Uri getCallerInfoUri(String number);
	
	/**
	 * Query projection to be used with
	 * getCallerInfoItem(Cursor cursor)
	 * 
	 * @return projection array
	 */
	public abstract String[] getCallerInfoProjection();
	
	/**
	 * Gets Name, URI and primary number from contact cursor
	 * 
	 * @param cursor
	 * @return contact info
	 */
	public abstract CallerInfo getCallerInfoItem(Context context, Cursor cursor);
	
	/**
	 * Exports all contacts, blocks until finished
	 * @param listener called for every contact
	 * @return 
	 * @throws Exception 
	 */
	public void exportAllContacts(Context context, OnExportContactListener listener)
			throws Exception
	{
		if (listener == null)
			throw new IllegalArgumentException("Argument listener must not be null.");

		Cursor cursor = context.getContentResolver().query(getSummaryUri(),
				getSummaryProjection(), getSelection(), null, getSummaryOrder());
		try
		{
			if ((cursor != null) && cursor.moveToFirst())
				for (boolean next = true; next; next = cursor.moveToNext())
					listener.onExportContact(exportContact(context, getContact(context,
							cursor)));
		}
		catch(Exception e)
		{
			FileLog.e(TAG, "Failed to export contact.", e);
		}
		finally
		{
			if (cursor != null) cursor.close();
		}
	}
	
	private String exportContact(Context context, Contact contact)
	{
		if (mXmlBuffer == null)
			mXmlBuffer = new StringBuilder();
		else
			mXmlBuffer.setLength(0);

		mXmlBuffer.append("<contact><category>0</category><person><realName>");
		mXmlBuffer.append(encodeEntities(contact.getName()));
		mXmlBuffer.append("</realName></person><telephony>");
		for (IContactable address : contact.mAddresses)
			if (address.getClass().equals(ContactNumber.class))
			{
				mXmlBuffer.append("<number type=\"");
				mXmlBuffer.append(encodeEntities(((ContactNumber)address).getTypeAsKey(true/*false*/)));
				mXmlBuffer.append("\" prio=\"0\">");
				mXmlBuffer.append(encodeEntities(((ContactNumber)address).getNumber()));
				mXmlBuffer.append("</number>");
			}
		mXmlBuffer.append("</telephony>");
		mXmlBuffer.append("<services>");
		for (IContactable address : contact.mAddresses)
			if (address.getClass().equals(ContactEmail.class))
			{
				mXmlBuffer.append("<email classifier=\"");
				mXmlBuffer.append(((ContactEmail)address).getType()
						.toString().toLowerCase(Locale.US));
				mXmlBuffer.append("\">");
				mXmlBuffer.append(encodeEntities(((ContactEmail)address)
						.getEmail()));
				mXmlBuffer.append("</email>");
			}
		mXmlBuffer.append("</services></contact>");

		return mXmlBuffer.toString();
	}
	
	public static String encodeEntities(String str)
	{
        return str.replace("<", "&lt;")
			.replace("'", "&apos;")
			.replace("\"", "&quot;")
			.replace(">", "&gt;")
			.replace("&", "&amp;");
	}
}
