/* 
 * 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;

import java.io.IOException;
import java.util.ArrayList;

import de.avm.android.fritzapp.R;
import de.avm.android.fritzapp.com.discovery.BoxInfo;
import de.avm.android.fritzapp.gui.SettingsVoIPConfigActivity;
import de.avm.android.fritzapp.util.DeviceId;
import de.avm.android.fritzapp.util.PasswordBuilder;
import de.avm.android.tr064.Tr064Boxinfo;
import de.avm.android.tr064.Tr064Capabilities;
import de.avm.android.tr064.exceptions.BaseException;
import de.avm.android.tr064.exceptions.SslErrorException;
import de.avm.android.tr064.model.VoIPClientInfo;
import de.avm.android.tr064.model.VoIPInfoEx;
import de.avm.fundamentals.logger.FileLog;

import android.content.Context;
import android.os.AsyncTask;
import android.os.Build;
import android.text.TextUtils;

/**
 * Task to configure VoIP client settings for boxes with
 * ComSettingsChecker.TR064_VOIPCONF_EXT
 */
public class VoIPAutoConfigurator
		extends AsyncTask<Integer, Integer, VoIPAutoConfigurator.Result>
{
    private static final String TAG = "VoIPAutoConfigurator";
	public enum Result { SUCCESS, ERROR, MANUAL }
	
	/**
	 * Listener called on completion
	 */
	public interface OnCompleteListener
	{
        public void onProgress();
        public void onComplete(Result result, String errorMessage);
	}
	
	/**
	 * Starts configuration Task
	 * 
	 * @param context
	 * 		valid context
	 * @param completeListener
	 * 		listener called on completion of the task
	 * @return
	 * 		running task instance or null if auto config not available
	 */
	public static VoIPAutoConfigurator start(Context context,
			OnCompleteListener completeListener)
	{
		try
		{
			BoxInfo boxInfo = ComSettingsChecker.getBoxInfo();
			if (boxInfo != null)
			{
				Tr064Boxinfo tr64Info = boxInfo.getTr064Boxinfo();
				if ((tr64Info != null) && (tr64Info.getTr064Capabilities()
						.has(Tr064Capabilities.Capability.VOIP_CONF_ID)))
				{
					VoIPAutoConfigurator result = new VoIPAutoConfigurator(context);
					result.mOnCompleteListener = completeListener;
					result.mBoxInfo = boxInfo; 
					result.execute((Integer[])null);
					return result;
				}
			}
		}
		catch(IllegalStateException e)
		{
            FileLog.w(TAG, e.getMessage(), e);
		}
		return null;
	}
	
	private static final int NEW_PASSWORD_LENGTH = 8;

	private Context mContext = null;
	private DeviceId mDeviceId = null;
	private OnCompleteListener mOnCompleteListener = null;
	private BoxInfo mBoxInfo = null;
	private DataHub mFritzBox = new DataHub();
	private String mErrorMessage = null;

	private VoIPAutoConfigurator(Context context)
	{
		mContext = context;
		mDeviceId = DeviceId.create(context);
	}

	@Override
	protected Result doInBackground(Integer... args)
	{
		boolean publishedProgress = false;
		try
		{
			String configuredUser = (mBoxInfo.hasVoipCredentials()) ?
					mBoxInfo.getVoIPName() : "";
			int foundDeviceId = -1;
			int foundConfiguredUser = -1;
			boolean foundConfiguredUserHasDeviceId = false;
			int devicesWithoutId = 0;
			
			// get clients and look for my device ID and already configured user
			int count = mFritzBox.getVoIPConfNumberOfClients(mContext);
			ArrayList<VoIPClientInfo> clientList = new ArrayList<>();
			for (int ii = 0; ii < count; ii++)
			{
				VoIPClientInfo info = mFritzBox.getVoIPConfClientInfo(mContext, ii);
				clientList.add(info);
				try
				{
					if (!TextUtils.isEmpty(configuredUser) &&
							configuredUser.equals(info.getUsername()))
						foundConfiguredUser = ii;
					DeviceId id = DeviceId.parse(info.getId());
					if (id == null)
					{
						devicesWithoutId++;
					}
					else
					{
						if (foundConfiguredUser ==  ii)
							foundConfiguredUserHasDeviceId = true;
						if (mDeviceId.equals(id)) foundDeviceId = ii; // it's me
					}
				}
                catch(Exception ignored) { }
			}
			SettingsVoIPConfigActivity.updateCachedInfo(mBoxInfo.getUdn(),
					devicesWithoutId > 0);

			VoIPClientInfo clientInfo = null;
			String password = "";
			if (!TextUtils.isEmpty(configuredUser))
			{
				// a client is configured
				if (foundConfiguredUser < 0)
				{
					// configured client not listed anymore
                    if (devicesWithoutId > 0) return Result.MANUAL;
				}
				else if (foundConfiguredUser != foundDeviceId)
				{
					if (foundConfiguredUserHasDeviceId)
					{
						// configured client is bound to another device
                        if (devicesWithoutId > 0) return Result.MANUAL;
					}
					else
					{
						// configured client is not bound to this device
						return Result.SUCCESS;
					}
				}
				else
				{
					// configured client is bound to this device ->
					// set known password
					password = mBoxInfo.getVoIPPassword();
					clientInfo = clientList.get(foundConfiguredUser);
				}
			}

			if (TextUtils.isEmpty(password)) password = createPassword();
			
			if ((clientInfo == null) && (foundDeviceId >= 0))
				clientInfo = clientList.get(foundDeviceId);
			
			if (clientInfo == null)
				clientInfo = addNewClient(clientList, password);
			else
				clientInfo = updateClient(clientInfo, password);
			
			publishedProgress = true;
			publishProgress((Integer[])null);
			
			if (clientInfo != null)
			{
				// wait a bit to have changes available on box
				Thread.sleep(2300);
				
				// success
				mBoxInfo.setVoipCredentials(clientInfo.getUsername(), password);
				mBoxInfo.setVoIPTitle(clientInfo.getName());
				return Result.SUCCESS;
			}
		}
		catch(Exception e)
		{
            FileLog.w(TAG, e.getMessage(), e);
			if (SslErrorException.isSslError(e))
			{
				ConnectionProblem problem = (SslErrorException.isCertificateError(e)) ?
						ConnectionProblem.CERTIFICATE_ERROR : ConnectionProblem.SSL_ERROR;
				problem.setCause(e);
				mErrorMessage = problem.getDisplayMessage(mContext);
			}
		}
		finally
		{
			if (!publishedProgress) publishProgress((Integer[])null);
		}

		return Result.ERROR;
	}
	
	@Override
	protected void onProgressUpdate(Integer... values)
	{
		super.onProgressUpdate(values);
		
		if (mOnCompleteListener != null)
			mOnCompleteListener.onProgress();
	}
	
	@Override
	protected void onPostExecute(Result result)
	{
		super.onPostExecute(result);
		
		// save changes
		if (ComSettingsChecker.getBoxInfo() == mBoxInfo)
			ComSettingsChecker.SaveBoxInfo(mContext);
		
		if (mOnCompleteListener != null)
			mOnCompleteListener.onComplete(result, (result == Result.ERROR) ?
					mErrorMessage : null);
	}
	
	private String createPassword()
			throws BaseException, IOException
	{
		VoIPInfoEx infoEx = mFritzBox.getVoIPConfInfoEx(mContext);
		int len = (infoEx.getVoIPPasswordSpec().getMinChars() > NEW_PASSWORD_LENGTH) ?
				infoEx.getVoIPPasswordSpec().getMinChars() : NEW_PASSWORD_LENGTH;
		if (len > infoEx.getVoIPPasswordSpec().getMaxChars())
			len = infoEx.getVoIPPasswordSpec().getMaxChars();
		return new PasswordBuilder()
				.setValidChars(infoEx.getVoIPPasswordSpec().getAllowedChars())
				.setLength(len).create();
	}
	
	private VoIPClientInfo addNewClient(ArrayList<VoIPClientInfo> clientList, String password)
			throws BaseException, IOException
	{
		// unique name
		String nameFmt = String.format(mContext.getString(R.string
				.settings_new_voipclient_name_fmt), Build.MODEL).trim();
		String newName = "";
		for (int ii = 0; (newName.length() == 0) && (ii < 100); ii++)
		{
			String name = nameFmt;
			if (ii > 0) name += " " + Integer.toString(ii + 1);
			boolean found = false;
			for (VoIPClientInfo info : clientList)
				if (name.equals(info.getName()))
				{
					found = true;
					break;
				}
			if (!found) newName = name;
		}

		// add
		VoIPClientInfo clientInfo = null; 
		int index = mFritzBox.addVoIPConfClientInfo(mContext, password,
				newName, mDeviceId.toString());
		if (index >= 0)
			clientInfo = mFritzBox.getVoIPConfClientInfo(mContext, index);
		else
			mErrorMessage = mContext.getString(R.string.voipclient_max_count);
		
		return clientInfo;
	}
	
	private VoIPClientInfo updateClient(VoIPClientInfo clientInfo, String password)
			throws BaseException, IOException
	{
		if (mFritzBox.setVoIPConfClientInfo(mContext, clientInfo.getIndex(),
				password, clientInfo.getName(),
				clientInfo.getOutgoingNumber(), clientInfo.getId()))
			return clientInfo;
		
		return null;
	}
}
