/*
 * Copyright (C) 2015 AVM GmbH <info@avm.de>
 * Copyright (C) 2009 The Sipdroid Open Source Project
 * Copyright (C) 2008 Hughes Systique Corporation, USA (http://www.hsc.com)
 * 
 * This file is part of Sipdroid (http://www.sipdroid.org)
 * 
 * Sipdroid 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 source code 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 source code; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package de.avm.android.fritzapp.sipua;

import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Timer;
import java.util.TimerTask;

import org.sipdroid.net.KeepAliveSip;
import org.zoolu.net.IpAddress;
import org.zoolu.net.SocketAddress;
import org.zoolu.sip.address.NameAddress;
import org.zoolu.sip.provider.SipProvider;
import org.zoolu.sip.provider.SipStack;

import android.content.Context;
import android.content.SharedPreferences.Editor;
import android.os.Build;
import android.os.PowerManager;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import de.avm.android.fritzapp.R;
import de.avm.android.fritzapp.com.ComSettingsChecker;
import de.avm.android.fritzapp.service.KeepAliveAlarm;
import de.avm.android.fritzapp.sipua.ui.Receiver;
import de.avm.android.fritzapp.sipua.ui.Sipdroid;
import de.avm.android.fritzapp.util.BuildHelper;
import de.avm.fundamentals.logger.FileLog;

public class SipdroidEngine implements RegisterAgentListener {

    private static final String TAG = "SipdroidEngine";
	public static final int UNINITIALIZED = 0x0;
	public static final int INITIALIZED = 0x2;

	/** User Agent */
	private UserAgent ua;

	/** Register Agent */
	private RegisterAgent ra;

	private KeepAliveSip ka;
	
	/** UserAgentProfile */
	private UserAgentProfile user_profile;

	private SipProvider sip_provider;
	
	private static final long REREGISTER_TIMEOUT = 15000; // 15s
	private Timer reregister_timeout = null;
	
    private static RegisterWakeLock mRegisterWakeLock = null;
	
	public void finalize() {
		remove_reregister_timeout();
	}

	public boolean StartEngine() {
		try {
            mRegisterWakeLock = new RegisterWakeLock(getUIContext());

			user_profile = new UserAgentProfile(null);
			user_profile.username = Sipdroid.getSipUser();
			user_profile.passwd = Sipdroid.getSipPassword();
			user_profile.realm = Sipdroid.getServerPref();

			SipStack.init(null);
			SipStack.debug_level = 0; // NB: change this for debug messages
			//SipStack.log_path = "/data/data/org.sipdroid.sipua";  // NB: uncomment this for debug messages
			SipStack.max_retransmission_timeout = 4000;
			SipStack.transaction_timeout = 30000;
			SipStack.default_transport_protocols = new String[1];
			SipStack.default_transport_protocols[0] = Sipdroid.getProtocolPref();
			SipStack.default_port = Sipdroid.getPortPref();
			
			String version = "FRITZApp/" + Sipdroid.getVersion() + "/" + Build.MODEL;
			SipStack.ua_info = version;
			SipStack.server_info = version;
				
			IpAddress.setLocalIpAddress(ComSettingsChecker.getMyIpAddress());
			
			sip_provider = new SipProvider(IpAddress.getLocalIpAddress(), 0);
			
			user_profile.contact_url = user_profile.username
				+ "@"
				+ IpAddress.getLocalIpAddress() + (sip_provider.getPort() != 0?":"+sip_provider.getPort():"");
			
			user_profile.from_url = user_profile.username
				+ "@"
				+ user_profile.realm;
			if (PreferenceManager.getDefaultSharedPreferences(getUIContext()).getString("callerid","").length() == 0) {
				user_profile.callerid = user_profile.from_url;
			} else {
				user_profile.callerid = PreferenceManager.getDefaultSharedPreferences(getUIContext()).getString("callerid","")
					+ "@"
					+ user_profile.realm;
			}

			ua = new UserAgent(sip_provider, user_profile);
			ra = new RegisterAgent(sip_provider, user_profile.callerid, // modified
					user_profile.contact_url, user_profile.username,
					user_profile.realm, user_profile.passwd, this, user_profile);
			// ka = new KeepAliveSip(sip_provider,100000); // we don't need keep alives. NB

			register();
			listen();
		} catch (Exception E) {
		}

		return true;
	}
	
	void setOutboundProxy() {
		try {
			sip_provider.setOutboundProxy(new SocketAddress(
					IpAddress.getByName(PreferenceManager.getDefaultSharedPreferences(getUIContext()).getString("dns","")),
					SipStack.default_port));
		} catch (UnknownHostException e) {
		}
	}
	
	public void CheckEngine() {
		if (!sip_provider.hasOutboundProxy())
			setOutboundProxy();
	}

	public Context getUIContext() {
		return Receiver.mContext;
	}
	
	private void remove_reregister_timeout() {
		if(reregister_timeout != null) {
			reregister_timeout.cancel();
			reregister_timeout.purge();
			reregister_timeout = null;
		}
	}
	
	public void expire() {
		if (ra != null && ra.CurrentState == RegisterAgent.REGISTERED) {
			remove_reregister_timeout();
			reregister_timeout = new Timer();
			reregister_timeout.schedule(new TimerTask() {
				public void run() {
					if (ra != null && ra.CurrentState == RegisterAgent.REGISTERED) {					
						ra.CurrentState = RegisterAgent.UNREGISTERED;
						ComSettingsChecker.updateSipState(Receiver.mContext,
								ComSettingsChecker.SIP_NOTREGISTERED, "");
						cancel();
					}
				}
			}, REREGISTER_TIMEOUT);
		}
		register();
	}
	
	public void unregister() {
		if (ra != null && ra.unregister()) {
			KeepAliveAlarm.setAlarm(Receiver.mContext, 0);
			ComSettingsChecker.updateSipState(Receiver.mContext,
                    ComSettingsChecker.SIP_NOTREGISTERED, "");
            mRegisterWakeLock.acquire();
		}
	}
	
	public void register() {
		try {
		if (user_profile == null || user_profile.username.equals("") ||
				user_profile.realm.equals("") || user_profile.passwd.equals("")) return;
		IpAddress.setLocalIpAddress(ComSettingsChecker.getMyIpAddress());
		user_profile.contact_url = user_profile.username
			+ "@"
			+ IpAddress.getLocalIpAddress() + (sip_provider.getPort() != 0?":"+sip_provider.getPort():"");
		if (!Receiver.isFast()) {
			unregister();
		} else {
			if (ra != null && ra.register()) {
				if (reregister_timeout == null) 
					ComSettingsChecker.updateSipState(Receiver.mContext,
							ComSettingsChecker.SIP_IDLE, "");
                mRegisterWakeLock.acquire();
			}
		}
		} catch (Exception ex) {
		}
	}

	public void halt() { // modified
		remove_reregister_timeout();
        mRegisterWakeLock.release();
		if (ka != null) {
			KeepAliveAlarm.setAlarm(Receiver.mContext, 0);
			ka.halt();
		}
		ComSettingsChecker.updateSipState(Receiver.mContext,
				ComSettingsChecker.SIP_NOTREGISTERED, "");
		if (ra != null)
			ra.halt();
		if (ua != null)
			ua.hangup();
		if (sip_provider != null)
			sip_provider.halt();
	}

	/**
	 * Get callers name from user agent
	 * 
	 * @return the name, or null
	 */
	public String getIncomingCallersName()
	{
		if (ua != null)
			return ua.getIncomingCallersName();
		return null;
	}

	/**
	 * Get callees name from user agent
	 * 
	 * @return the name, or null
	 */
	public String getIncomingCalleesName()
	{
		if (ua != null)
			return ua.getIncomingCalleesName();
		return null;
	}
	
	public boolean isRegistered()
	{
		if (ra == null)
		{
			return false;
		}
		return ra.isRegistered();
	}
	
	public void onUaRegistrationSuccess(RegisterAgent ra, NameAddress target,
			NameAddress contact, String result) {
		remove_reregister_timeout();		
		if (isRegistered()) {
			if (ka != null && Receiver.on_wlan)
				KeepAliveAlarm.setAlarm(Receiver.mContext, 60);
			ComSettingsChecker.updateSipState(Receiver.mContext,
					ComSettingsChecker.SIP_AVAILABLE, "");
		} else
			ComSettingsChecker.updateSipState(Receiver.mContext,
					ComSettingsChecker.SIP_NOTREGISTERED, "");
		Receiver.registered();
		ra.subattempts = 0;
		ra.startMWI();
        mRegisterWakeLock.release();
	}

	String lastmsgs;
	
    public void onMWIUpdate(boolean voicemail, int number, String vmacc) {
		if (voicemail) {
			String msgs = getUIContext().getString(R.string.voicemail);
			if (number != 0) {
				msgs = msgs + ": " + number;
			}
			Receiver.MWI_account = vmacc;
			if (lastmsgs == null || !msgs.equals(lastmsgs)) {
				Receiver.onText(Receiver.MWI_NOTIFICATION, msgs,
                        android.R.drawable.stat_notify_voicemail);
				lastmsgs = msgs;
			}
		} else {
			Receiver.onText(Receiver.MWI_NOTIFICATION, null, 0);
			lastmsgs = null;
		}
	}

	static long lasthalt;
	
	/** When a UA failed on (un)registering. */
	public void onUaRegistrationFailure(RegisterAgent ra, NameAddress target,
			NameAddress contact, String result) {
		ComSettingsChecker.updateSipState(Receiver.mContext,
				ComSettingsChecker.SIP_AWAY, result);
        mRegisterWakeLock.release();
		if (SystemClock.uptimeMillis() > lasthalt + 45000) {
			lasthalt = SystemClock.uptimeMillis();
			sip_provider.haltConnections();
		}
		updateDNS();
		ra.stopMWI();
	}
	
	public void updateDNS() {
		Editor edit = PreferenceManager.getDefaultSharedPreferences(getUIContext()).edit();
		try {
			edit.putString("dns", IpAddress.getByName(Sipdroid.getServerPref()).toString());
		} catch (UnknownHostException e1) {
			return;
		}
		edit.apply();
	}

	/** Receives incoming calls (auto accept) */
	public void listen() 
	{
		ua.printLog("UAS: WAITING FOR INCOMING CALL");
		
		if (!ua.user_profile.audio && !ua.user_profile.video)
		{
			ua.printLog("ONLY SIGNALING, NO MEDIA");
		}
		
		ua.listen();
	}
	
	public void info(char c) {
		ua.info(c);
	}
	
	/** Makes a new call */
	public boolean call(String target_url, boolean send_anonymous) {
		ua.printLog("UAC: CALLING " + target_url);
		
		if (!ua.user_profile.audio && !ua.user_profile.video)
		{
			 ua.printLog("ONLY SIGNALING, NO MEDIA");
		}
		return ua.call(target_url, send_anonymous);
	}

	public void answercall() 
	{
		ua.accept();
	}

	public void rejectcall() {
		ua.printLog("UA: HANGUP");
		ua.hangup();
	}

	public void togglehold() {
		ua.reInvite(null, 0);
	}

	public void transfer(String number) {
		ua.callTransfer(number, 0);
	}
	
	public void togglemute()
	{
		Receiver.onMuteChanged(ua.muteMediaApplication());
	}
	
	public int speaker(int mode) {
		return ua.speakerMediaApplication(mode);
	}
	
	/** When a new call is incoming */
	public void onState(int state,String text) {
			Receiver.onState(state,text);
	}

	public void keepAlive() {
		if (ka != null && Receiver.on_wlan && isRegistered())
			try
            {
				ka.sendToken();
				KeepAliveAlarm.setAlarm(Receiver.mContext, 60);
			}
            catch (IOException e)
            {
				if (!Sipdroid.release) FileLog.w(TAG, e.getMessage(), e);

            }
	}

    private static class RegisterWakeLock
    {
        /**
         * On a register without network connection onUaRegistrationFailure is not called in
         * every cases! Therefore wake locks must be released automatically, to not preventing
         * device from standby infinitely. The timeout is a bit longer than the request's
         * timeout in RegisterAgent.
         */
        private static final long TIMEOUT = 31000;
        private PowerManager.WakeLock mPartialWakeLock;
        private PowerManager.WakeLock mDimWakeLock = null;

        public RegisterWakeLock(Context context)
        {
            PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
            mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                    SipdroidEngine.class.getName());
            if (BuildHelper.isDimOnlyDevice())
                mDimWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK,
                        SipdroidEngine.class.getName());
        }

        public void acquire()
        {
            mPartialWakeLock.acquire(TIMEOUT);
            if ((mDimWakeLock != null) && Receiver.on_wlan)
                mDimWakeLock.acquire(TIMEOUT);
        }

        public void release()
        {
            if (mPartialWakeLock.isHeld())
                mPartialWakeLock.release();
            if ((mDimWakeLock != null) && mDimWakeLock.isHeld())
                mDimWakeLock.release();
        }
    }
}
