/**
 * Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
 * This file is part of CSipSimple.
 *
 *  CSipSimple 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.
 *  If you own a pjsip commercial license you can also redistribute it
 *  and/or modify it under the terms of the GNU Lesser General Public License
 *  as an android library.
 *
 *  CSipSimple 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 CSipSimple.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.csipsimple.service;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.graphics.Typeface;
import android.provider.CallLog;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.Builder;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.StyleSpan;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.csipsimple.R;
import com.csipsimple.api.SipCallSession;
import com.csipsimple.api.SipManager;
import com.csipsimple.api.SipMessage;
import com.csipsimple.api.SipProfile;
import com.csipsimple.api.SipProfileState;
import com.csipsimple.api.SipUri;
import com.csipsimple.models.CallerInfo;
import com.csipsimple.utils.Compatibility;
import com.csipsimple.utils.CustomDistribution;
import com.csipsimple.utils.Log;
import com.csipsimple.widgets.RegistrationNotification;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

public class SipNotifications {

	private final NotificationManager notificationManager;
	private final Context context;
	private Builder inCallNotification;
	private Builder missedCallNotification;
	private Builder messageNotification;
	private Builder messageVoicemail;
	private boolean resolveContacts = true;

	public static final int REGISTER_NOTIF_ID = 1;
	public static final int CALL_NOTIF_ID = REGISTER_NOTIF_ID + 1;
	public static final int CALLLOG_NOTIF_ID = REGISTER_NOTIF_ID + 2;
	public static final int MESSAGE_NOTIF_ID = REGISTER_NOTIF_ID + 3;
	public static final int VOICEMAIL_NOTIF_ID = REGISTER_NOTIF_ID + 4;

	private static boolean isInit = false;

	public SipNotifications(Context aContext) {
		context = aContext;
		notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

		if (!isInit) {
			cancelAll();
			cancelCalls();
			isInit = true;
		}
		
		if( ! Compatibility.isCompatible(9) ) {
		    searchNotificationPrimaryText(aContext);
		}
	}

    private Integer notificationPrimaryTextColor = null;

    private static String TO_SEARCH = "Search";
	// Retrieve notification textColor with android < 2.3
	@SuppressWarnings("deprecation")
    private void searchNotificationPrimaryText(Context aContext) {
	    try {
	        Notification ntf = new Notification();
//	        ntf.setLatestEventInfo(aContext, TO_SEARCH, "", null);
	        LinearLayout group = new LinearLayout(aContext);
	        ViewGroup event = (ViewGroup) ntf.contentView.apply(aContext, group);
	        recurseSearchNotificationPrimaryText(event);
	        group.removeAllViews();
	    } catch (Exception e) {
	        Log.e(THIS_FILE, "Can't retrieve the color", e);
	    }
	}
	
	private boolean recurseSearchNotificationPrimaryText(ViewGroup gp) {
	    final int count = gp.getChildCount();
	    for (int i = 0; i < count; ++i) {
	        if (gp.getChildAt(i) instanceof TextView){
	            final TextView text = (TextView) gp.getChildAt(i);
	            final String szText = text.getText().toString();
	            if (TO_SEARCH.equals(szText)) {
	                notificationPrimaryTextColor = text.getTextColors().getDefaultColor();
	                return true;
	            }
	        } else if (gp.getChildAt(i) instanceof ViewGroup) {
	            if(recurseSearchNotificationPrimaryText((ViewGroup) gp.getChildAt(i))) {
	                return true;
	            }
	        }
	    }
	    return false;
	}
	

	// Foreground api

	private static final Class<?>[] SET_FG_SIG = new Class[] { boolean.class };
	private static final Class<?>[] START_FG_SIG = new Class[] { int.class, Notification.class };
	private static final Class<?>[] STOP_FG_SIG = new Class[] { boolean.class };
	private static final String THIS_FILE = "Notifications";

	private Method mSetForeground;
	private Method mStartForeground;
	private Method mStopForeground;
	private Object[] mSetForegroundArgs = new Object[1];
	private Object[] mStartForegroundArgs = new Object[2];
	private Object[] mStopForegroundArgs = new Object[1];

	private void invokeMethod(Method method, Object[] args) {
		try {
			method.invoke(context, args);
		} catch (InvocationTargetException e) {
			// Should not happen.
			Log.w(THIS_FILE, "Unable to invoke method", e);
		} catch (IllegalAccessException e) {
			// Should not happen.
			Log.w(THIS_FILE, "Unable to invoke method", e);
		}
	}

	/**
	 * This is a wrapper around the new startForeground method, using the older
	 * APIs if it is not available.
	 */
	private void startForegroundCompat(int id, Notification notification) {
		// If we have the new startForeground API, then use it.
		if (mStartForeground != null) {
			mStartForegroundArgs[0] = Integer.valueOf(id);
			mStartForegroundArgs[1] = notification;
			invokeMethod(mStartForeground, mStartForegroundArgs);
			return;
		}

		// Fall back on the old API.
		mSetForegroundArgs[0] = Boolean.TRUE;
		invokeMethod(mSetForeground, mSetForegroundArgs);
		notificationManager.notify(id, notification);
	}

	/**
	 * This is a wrapper around the new stopForeground method, using the older
	 * APIs if it is not available.
	 */
	private void stopForegroundCompat(int id) {
		// If we have the new stopForeground API, then use it.
		if (mStopForeground != null) {
			mStopForegroundArgs[0] = Boolean.TRUE;
			invokeMethod(mStopForeground, mStopForegroundArgs);
			return;
		}

		// Fall back on the old API. Note to cancel BEFORE changing the
		// foreground state, since we could be killed at that point.
		notificationManager.cancel(id);
		mSetForegroundArgs[0] = Boolean.FALSE;
		invokeMethod(mSetForeground, mSetForegroundArgs);
	}

	private boolean isServiceWrapper = false;

	public void onServiceCreate() {
		try {
			mStartForeground = context.getClass().getMethod("startForeground", START_FG_SIG);
			mStopForeground = context.getClass().getMethod("stopForeground", STOP_FG_SIG);
			isServiceWrapper = true;
			return;
		} catch (NoSuchMethodException e) {
			// Running on an older platform.
			mStartForeground = mStopForeground = null;
		}
		try {
			mSetForeground = context.getClass().getMethod("setForeground", SET_FG_SIG);
		} catch (NoSuchMethodException e) {
			throw new IllegalStateException("OS doesn't have Service.startForeground OR Service.setForeground!");
		}
		isServiceWrapper = true;
	}

	public void onServiceDestroy() {
		// Make sure our notification is gone.
		cancelAll();
		cancelCalls();
	}

	// Announces

	// Register
	public synchronized void notifyRegisteredAccounts(ArrayList<SipProfileState> activeAccountsInfos, boolean showNumbers) {
		if (!isServiceWrapper) {
			Log.e(THIS_FILE, "Trying to create a service notification from outside the service");
			return;
		}
		int icon = R.drawable.ic_stat_sipok;
		CharSequence tickerText = context.getString(R.string.service_ticker_registered_text);
		long when = System.currentTimeMillis();
		

        Builder nb = new Builder(context);
        nb.setSmallIcon(icon);
        nb.setTicker(tickerText);
        nb.setWhen(when);
		Intent notificationIntent = new Intent(SipManager.ACTION_SIP_DIALER);
		notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
		
		RegistrationNotification contentView = new RegistrationNotification(context.getPackageName());
		contentView.clearRegistrations();
		if(!Compatibility.isCompatible(9)) {
		    contentView.setTextsColor(notificationPrimaryTextColor);
		}
		contentView.addAccountInfos(context, activeAccountsInfos);

		// notification.setLatestEventInfo(context, contentTitle,
		// contentText, contentIntent);
		nb.setOngoing(true);
		nb.setOnlyAlertOnce(true);
        nb.setContentIntent(contentIntent);
        nb.setContent(contentView);
		
		Notification notification = nb.build();
		notification.flags |= Notification.FLAG_NO_CLEAR;
		// We have to re-write content view because getNotification setLatestEventInfo implicitly
        notification.contentView = contentView;
		if (showNumbers) {
            // This only affects android 2.3 and lower
            notification.number = activeAccountsInfos.size();
        }
		startForegroundCompat(REGISTER_NOTIF_ID, notification);
	}
	
	/**
	 * Format the remote contact name for the call info
	 * @param callInfo the callinfo to format
	 * @return the name to display for the contact
	 */
	private String formatRemoteContactString(String remoteContact) {
        String formattedRemoteContact = remoteContact;
        if(resolveContacts) {
            CallerInfo callerInfo = CallerInfo.getCallerInfoFromSipUri(context, formattedRemoteContact);
            if (callerInfo != null && callerInfo.contactExists) {
                StringBuilder remoteInfo = new StringBuilder();
                remoteInfo.append(callerInfo.name);
                remoteInfo.append(" <");
                remoteInfo.append(SipUri.getCanonicalSipContact(remoteContact));
                remoteInfo.append(">");
                formattedRemoteContact = remoteInfo.toString();
            }
        }
        return formattedRemoteContact;
	}
	
	/**
	 * Format the notification title for a call info
	 * @param title
	 * @param callInfo
	 * @return
	 */
	private String formatNotificationTitle(int title, long accId) {
        StringBuilder notifTitle = new StringBuilder(context.getText(title));
        SipProfile acc = SipProfile.getProfileFromDbId(context, accId,
                new String[] {SipProfile.FIELD_DISPLAY_NAME});
        if ((acc != null) && !TextUtils.isEmpty(acc.display_name)) {
            notifTitle.append(" - ");
            notifTitle.append(acc.display_name);
        }
        return notifTitle.toString();
	}

	// Calls
	public void showNotificationForCall(SipCallSession callInfo) {
		// This is the pending call notification
		// int icon = R.drawable.ic_incall_ongoing;
		@SuppressWarnings("deprecation")
        int icon = android.R.drawable.stat_sys_phone_call;
		CharSequence tickerText = context.getText(R.string.ongoing_call);
		long when = System.currentTimeMillis();

		if(inCallNotification == null) {
		    inCallNotification = new Builder(context);
		    inCallNotification.setSmallIcon(icon);
		    inCallNotification.setTicker(tickerText);
		    inCallNotification.setWhen(when);
		    inCallNotification.setOngoing(true);
		}
        
		Intent notificationIntent = SipService.buildCallUiIntent(context, callInfo);
		PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
		
        inCallNotification.setContentTitle(formatNotificationTitle(R.string.ongoing_call, callInfo.getAccId()));
        inCallNotification.setContentText(formatRemoteContactString(callInfo.getRemoteContact()));
		inCallNotification.setContentIntent(contentIntent);

		Notification notification = inCallNotification.build();
		notification.flags |= Notification.FLAG_NO_CLEAR;
		notificationManager.notify(CALL_NOTIF_ID, notification);
	}

	public void showNotificationForMissedCall(ContentValues callLog) {
		int icon = android.R.drawable.stat_notify_missed_call;
		CharSequence tickerText = context.getText(R.string.missed_call);
		long when = System.currentTimeMillis();

		if (missedCallNotification == null) {
	        missedCallNotification = new Builder(context);
	        missedCallNotification.setSmallIcon(icon);
	        missedCallNotification.setTicker(tickerText);
	        missedCallNotification.setWhen(when);
	        missedCallNotification.setOnlyAlertOnce(true);
	        missedCallNotification.setAutoCancel(true);
	        missedCallNotification.setDefaults(Notification.DEFAULT_ALL);
		}

		Intent notificationIntent = new Intent(SipManager.ACTION_SIP_CALLLOG);
		notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);

		String remoteContact = callLog.getAsString(CallLog.Calls.NUMBER);
		long accId = callLog.getAsLong(SipManager.CALLLOG_PROFILE_ID_FIELD);
		missedCallNotification.setContentTitle(formatNotificationTitle(R.string.missed_call, accId));
		missedCallNotification.setContentText(formatRemoteContactString(remoteContact));
		missedCallNotification.setContentIntent(contentIntent);
		
		notificationManager.notify(CALLLOG_NOTIF_ID, missedCallNotification.build());
	}

	public void showNotificationForMessage(SipMessage msg) {
		if (!CustomDistribution.supportMessaging()) {
			return;
		}
		// CharSequence tickerText = context.getText(R.string.instance_message);
		if (!msg.getFrom().equalsIgnoreCase(viewingRemoteFrom)) {
			String from = formatRemoteContactString(msg.getFullFrom());
			if(from.equalsIgnoreCase(msg.getFullFrom()) && !from.equals(msg.getDisplayName())) {
			    from = msg.getDisplayName() + " " + from;
			}
			CharSequence tickerText = buildTickerMessage(context, from, msg.getBody());

			if (messageNotification == null) {
				messageNotification = new Builder(context);
				messageNotification.setSmallIcon(SipUri.isPhoneNumber(from) ? R.drawable.stat_notify_sms : android.R.drawable.stat_notify_chat);
				messageNotification.setTicker(tickerText);
				messageNotification.setWhen(System.currentTimeMillis());
				messageNotification.setDefaults(Notification.DEFAULT_ALL);
				messageNotification.setAutoCancel(true);
				messageNotification.setOnlyAlertOnce(true);
			}

			Intent notificationIntent = new Intent(SipManager.ACTION_SIP_MESSAGES);
			notificationIntent.putExtra(SipMessage.FIELD_FROM, msg.getFrom());
			notificationIntent.putExtra(SipMessage.FIELD_BODY, msg.getBody());
			notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
			PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
			
			messageNotification.setContentTitle(from);
			messageNotification.setContentText(msg.getBody());
			messageNotification.setContentIntent(contentIntent);
			
			notificationManager.notify(MESSAGE_NOTIF_ID, messageNotification.build());
		}
	}

    public void showNotificationForVoiceMail(SipProfile acc, int numberOfMessages) {
        if (messageVoicemail == null) {

            messageVoicemail = new Builder(context);
            messageVoicemail.setSmallIcon(android.R.drawable.stat_notify_voicemail);
            messageVoicemail.setTicker(context.getString(R.string.voice_mail));
            messageVoicemail.setWhen(System.currentTimeMillis());
            messageVoicemail.setDefaults(Notification.DEFAULT_ALL);
            messageVoicemail.setAutoCancel(true);
            messageVoicemail.setOnlyAlertOnce(true);
        }

        PendingIntent contentIntent = null;
        Intent notificationIntent;
        if (acc != null && !TextUtils.isEmpty(acc.vm_nbr) && acc.vm_nbr != "null") {
            notificationIntent = new Intent(Intent.ACTION_CALL);
            notificationIntent.setData(SipUri.forgeSipUri(SipManager.PROTOCOL_CSIP, acc.vm_nbr
                    + "@" + acc.getDefaultDomain()));
            notificationIntent.putExtra(SipProfile.FIELD_ACC_ID, acc.id);
        } else {
            notificationIntent = new Intent(SipManager.ACTION_SIP_DIALER);
        }
        notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        contentIntent = PendingIntent.getActivity(context, 0, notificationIntent,
                PendingIntent.FLAG_CANCEL_CURRENT);

        String messageText = "";
        if (acc != null) {
            messageText += acc.getProfileName();
            if(numberOfMessages>0) {
                messageText += " : ";
            }
        }
        if(numberOfMessages > 0) {
            messageText += Integer.toString(numberOfMessages);
        }
        
        messageVoicemail.setContentTitle(context.getString(R.string.voice_mail));
        messageVoicemail.setContentText(messageText);
        if (contentIntent != null) {
            messageVoicemail.setContentIntent(contentIntent);
            notificationManager.notify(VOICEMAIL_NOTIF_ID, messageVoicemail.build());
        }
    }

	private static String viewingRemoteFrom = null;

	public void setViewingMessageFrom(String remoteFrom) {
		viewingRemoteFrom = remoteFrom;
	}

	protected static CharSequence buildTickerMessage(Context context, String address, String body) {
		String displayAddress = address;

		StringBuilder buf = new StringBuilder(displayAddress == null ? "" : displayAddress.replace('\n', ' ').replace('\r', ' '));
		buf.append(':').append(' ');

		int offset = buf.length();

		if (!TextUtils.isEmpty(body)) {
			body = body.replace('\n', ' ').replace('\r', ' ');
			buf.append(body);
		}

		SpannableString spanText = new SpannableString(buf.toString());
		spanText.setSpan(new StyleSpan(Typeface.BOLD), 0, offset, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

		return spanText;
	}

	// Cancels
	public final void cancelRegisters() {
		if (!isServiceWrapper) {
			Log.e(THIS_FILE, "Trying to cancel a service notification from outside the service");
			return;
		}
		stopForegroundCompat(REGISTER_NOTIF_ID);
	}

	public final void cancelCalls() {
		notificationManager.cancel(CALL_NOTIF_ID);
	}

	public final void cancelMissedCalls() {
		notificationManager.cancel(CALLLOG_NOTIF_ID);
	}

	public final void cancelMessages() {
		notificationManager.cancel(MESSAGE_NOTIF_ID);
	}

	public final void cancelVoicemails() {
		notificationManager.cancel(VOICEMAIL_NOTIF_ID);
	}

	public final void cancelAll() {
		// Do not cancel calls notification since it's possible that there is
		// still an ongoing call.
		if (isServiceWrapper) {
			cancelRegisters();
		}
		cancelMessages();
		cancelMissedCalls();
		cancelVoicemails();
	}

}