/* 
 * 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.com.discovery;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Locale;

import org.json.JSONException;
import org.json.JSONObject;

import de.avm.android.tr064.Tr064Boxinfo;
import de.avm.android.tr064.net.Fingerprint;
import de.avm.fundamentals.logger.FileLog;

import android.text.TextUtils;

/**
 *  Info/Preferences of a box
 */
public class BoxInfo
{
	private static final String TAG ="BoxInfo";
	
	public static final String SIPUSER_DEFAULT = "620";
	public static final String TAM_DEFAULT = "**600";
	
	private static final String FIELD_UDN = "udn"; 
	private static final String FIELD_LOCATION = "loc"; 
	private static final String FIELD_MRU = "mru"; 
	private static final String FIELD_BOXUSERNAME = "un"; 
	private static final String FIELD_BOXPASSWORD = "pw"; 
	private static final String FIELD_VOIPNAME = "tel"; 
	private static final String FIELD_VOIPPASSWORD = "tpw"; 
	private static final String FIELD_VOIPTITLE = "tti"; 
	private static final String FIELD_TAM = "tam"; 
	private static final String FIELD_NOAUTOCONNECT = "noauto"; 
	private static final String FIELD_NAME = "name"; 
	private static final String FIELD_FINGERPRINT = "pkfp"; 
	
	private String mUdn; 
	private URL mLocation; 
	private String mServer = ""; 
	private boolean mIsTr64 = false;
	private long mMru = 0;
	private Tr064Boxinfo mTr064Boxinfo = null;
	private boolean mAutoConnect = true;

	private String mName = "";
	private String mBoxUsername = "";
	private String mBoxPassword = ""; 
	private String mVoIPName = ""; 
	private String mVoIPPassword = "";
	private String mVoIPTitle = "";
	private String mTam = "";
	private Fingerprint mPinnedPubkeyFingerprint = null;
	private Fingerprint mCertificateFingerprint = null;
	
	/**
	 * only (TR-064-)boxes tried to connect to have saved preferences
	 */
	public boolean hasPreferences()
	{
		return mIsTr64 && (mMru > 0);
	}
	
	public boolean isAvailable()
	{
		return mServer.length() > 0;
	}
	
	public String getUdn()
	{
		return mUdn;
	}

	public boolean isTr64()
	{
		return mIsTr64;
	}

	public long getMru()
	{
		return mMru;
	}
	
	public Tr064Boxinfo getTr064Boxinfo()
	{
		return mTr064Boxinfo;
	}

	/**
	 * Sets MRU to current time
	 * 
	 * @param setMru
	 * 		true to set MRU, false to remove it
	 */
	public void updateMru(boolean setMru)
	{
		mMru = (setMru) ? System.currentTimeMillis() : 0;
	}

	public URL getLocation()
	{
		return mLocation;
	}

	public String getServer()
	{
		return mServer;
	}
	
	public String getBoxUsername()
	{
		return mBoxUsername;
	}
	
	public String getBoxPassword()
	{
		return mBoxPassword;
	}

	public void setBoxPassword(String username, String password)
	{
		mBoxUsername = username;
		mBoxPassword = password;
	}

	public void setBoxPassword(String password)
	{
		mBoxUsername = "";
		mBoxPassword = password;
	}
	
	public void setIsAutoconnect(boolean on)
	{
		mAutoConnect = on;
	}
	
	public boolean isAutoConnect()
	{
		return mAutoConnect;
	}

	/**
	 * Checks if user and password has been set for VoIP
	 * 
	 * @return true if user and password available
	 */
	public boolean hasVoipCredentials()
	{
		return !(TextUtils.isEmpty(getVoIPName()) || 
				TextUtils.isEmpty(getVoIPPassword())); 
	}
	
	public String getVoIPName()
	{
		if (TextUtils.isEmpty(mVoIPName) && hasPreferences())
			return SIPUSER_DEFAULT; 
		return mVoIPName;
	}

	public String getVoIPPassword()
	{
		return mVoIPPassword;
	}
	
	public void setVoipCredentials(String name, String password)
	{
		mVoIPName = name;
		mVoIPPassword = password;
	}

	public String getVoIPTitle()
	{
		return (TextUtils.isEmpty(mVoIPTitle)) ? getVoIPName() : mVoIPTitle;
	}

	public void setVoIPTitle(String title)
	{
		mVoIPTitle = title;
	}
	
	public String getTam()
	{
		if ((mTam.length() == 0) && hasPreferences())
			return TAM_DEFAULT; 
		return mTam;
	}
	
	public void setTam(String tam)
	{
		mTam = tam;
	}

	public String getDisplayName()
	{
		if (TextUtils.isEmpty(mName)) return getUdn();
		return mName;
	}

	public String getDisplayDescription()
	{
		if (isAvailable())
		{
			try
			{
				return getLocation().getHost();
			}
			catch (Exception e)
			{
                FileLog.w(TAG, e.getMessage(), e);
			}
		}
		return "";
	}
	
	public boolean hasPinnedPubkeyFingerprint()
	{
		return mPinnedPubkeyFingerprint != null;
	}
	
	public Fingerprint getPinnedPubkeyFingerprint()
	{
		return mPinnedPubkeyFingerprint;
	}
	
	public void setPinnedPubkeyFingerprint(Fingerprint fingerprint)
	{
		mPinnedPubkeyFingerprint = fingerprint;
	}
	
	public boolean hasCertificateFingerprint()
	{
		return mCertificateFingerprint != null;
	}
	
	public Fingerprint getCertificateFingerprint()
	{
		return mCertificateFingerprint;
	}
	
	public void setCertificateFingerprint(Fingerprint fingerprint)
	{
		mCertificateFingerprint = fingerprint;
	}
	
	/**
	 * Create new instance from discovery
	 * 
	 * @param isTr64
	 * 		true for TR-064 UDN
	 * @param udn
	 * 		the UDN
	 * @param location
	 *		the URL of device description
	 * @param server
	 * 		the server info string
	 * @param tr064Boxinfo
	 * 		parsed TR-064 info to store along
	 */
	public BoxInfo(boolean isTr64, String udn, URL location, String server,
			Tr064Boxinfo tr064Boxinfo)
	{
		mIsTr64 = isTr64;
		mUdn = udn;
		mLocation = location;
		mServer = server;
		mTr064Boxinfo = tr064Boxinfo;
		
		updateName();
	}

	/**
	 * Create new instance from preferences 
	 * 
	 * @param json
	 * 		the saved content
	 * @throws JSONException
	 * 		failed to parse JSON
	 * @throws MalformedURLException
	 * 		failed to parse URL of location
	 * @throws NumberFormatException
	 * 		invalid MRU value
	 */
	public BoxInfo(JSONObject json)
			throws JSONException, MalformedURLException, NumberFormatException
	{
		mIsTr64 = true;
		mUdn = json.getString(FIELD_UDN);
		mLocation = new URL(json.getString(FIELD_LOCATION));
		mMru = json.getLong(FIELD_MRU);
		if (mMru <= 0)
			throw new NumberFormatException("Value " + FIELD_MRU + " has to be > 0");
		mAutoConnect = !json.optBoolean(FIELD_NOAUTOCONNECT);
		mBoxUsername = json.optString(FIELD_BOXUSERNAME, null);
		mBoxPassword = json.optString(FIELD_BOXPASSWORD, "");
		mVoIPName = json.optString(FIELD_VOIPNAME, "");
		mVoIPPassword = json.optString(FIELD_VOIPPASSWORD, "");
		mVoIPTitle = json.optString(FIELD_VOIPTITLE, "");
		mTam = json.optString(FIELD_TAM, "");
		mName = json.optString(FIELD_NAME, "");
		String fingerprint = json.optString(FIELD_FINGERPRINT, "");
		if (!TextUtils.isEmpty(fingerprint))
		{
			try
			{
				mPinnedPubkeyFingerprint = new Fingerprint(fingerprint);
			}
			catch(Exception e)
			{
				FileLog.w(TAG, "Failed to load pinned fingerprint.", e);
				mPinnedPubkeyFingerprint = null;
			}
		}
	}
	
	/**
	 * Gets content as JSON to save in preferences
	 * @return
	 * 		the JSON object or null
	 */
	public JSONObject getJson()
	{
		if (!hasPreferences()) return null;
		
		JSONObject json = new JSONObject();
		try
		{
			json.put(FIELD_UDN, mUdn);
			json.put(FIELD_LOCATION, mLocation.toString());
			json.put(FIELD_MRU, mMru);
			if (!mAutoConnect) json.put(FIELD_NOAUTOCONNECT, true);
			if (!TextUtils.isEmpty(mBoxUsername)) json.put(FIELD_BOXUSERNAME, mBoxUsername);
			if (!TextUtils.isEmpty(mBoxPassword)) json.put(FIELD_BOXPASSWORD, mBoxPassword);
			if (!TextUtils.isEmpty(mVoIPName)) json.put(FIELD_VOIPNAME, mVoIPName);
			if (!TextUtils.isEmpty(mVoIPPassword)) json.put(FIELD_VOIPPASSWORD, mVoIPPassword);
			if (!TextUtils.isEmpty(mVoIPTitle)) json.put(FIELD_VOIPTITLE, mVoIPTitle);
			if (!TextUtils.isEmpty(mTam)) json.put(FIELD_TAM, mTam);
			if (!TextUtils.isEmpty(mName)) json.put(FIELD_NAME, mName);
			if (mPinnedPubkeyFingerprint != null)
				json.put(FIELD_FINGERPRINT, mPinnedPubkeyFingerprint.toString());
		}
		catch (JSONException e)
		{
			FileLog.e(TAG, "Cannot create JSON for UDN " + mUdn + ".", e);
			json = null;
		}
		return json;
	}
	
	/**
	 * Removes all discovery info 
	 */
	public void reset()
	{
		mServer = "";
	}

	/**
	 * Updates data from discovery
	 * 
	 * @param location
	 *		the URL of device description
	 * @param server
	 * 		the server info string
	 * @return
	 * 		true if data changed
	 */
	public boolean updateLocation(URL location, String server)
	{
		if (!mLocation.equals(location) || !mServer.equals(server))
		{
			mLocation = location;
			mServer = server;
			updateName();
			return true;
		}
		return false;
	}

	/**
	 * Updates TR-064 info
	 * 
	 * @param tr064Boxinfo
	 * 		parsed TR-064 info to store along
	 * @return
	 * 		always true
	 */
	public boolean updateTr064Boxinfo(Tr064Boxinfo tr064Boxinfo)
	{
		mTr064Boxinfo = tr064Boxinfo; 
		updateName();
		return true;
	}
	
	private void updateName()
	{
		if (mTr064Boxinfo != null)
		{
			String friendlyName = mTr064Boxinfo.getFriendlyName();
			if (TextUtils.isEmpty(friendlyName))
				friendlyName = mTr064Boxinfo.getModel();
			if (!TextUtils.isEmpty(friendlyName))
			{
				mName = friendlyName;
				return;
			}
		}

		String server = mServer; 
		if (TextUtils.isEmpty(server)) return;
		
		// show model name part only
		try
		{
			int posBegin = server.toLowerCase(Locale.US).indexOf("upnp/");
			if (posBegin >= 0)
			{
				posBegin = server.indexOf(" " + BoxInfoList.MANUFACTURER + " ", posBegin);
				if (posBegin > 0)
				{
					posBegin += BoxInfoList.MANUFACTURER.length() + 2;
					int posEnd = server.lastIndexOf(' '); 
					if (posEnd > posBegin)
						server = server.substring(posBegin, posEnd).trim();
					else
						server = server.substring(posBegin).trim();
				}
			}
			else if (server.startsWith(BoxInfoList.MANUFACTURER + " "))
				server = server.substring(BoxInfoList.MANUFACTURER.length()).trim();
		}
		catch (Exception e)
		{
            FileLog.w(TAG, e.getMessage(), e);
			server = null;
		}

		if (!TextUtils.isEmpty(server)) mName = server;
	}
	
	/**
	 * Removes preferences, keeps discovery info
	 * (does not remove certificate from store!) 
	 */
	public void removePreferences()
	{
		mMru = 0;
		mAutoConnect = true;
		mBoxUsername = "";
		mBoxPassword = ""; 
		mVoIPName = ""; 
		mVoIPPassword = "";
		mVoIPTitle = "";
		mTam = "";
		mName = "";
		mPinnedPubkeyFingerprint = null;
		mCertificateFingerprint = null;
	}
}
