/* 
 * Copyright 2012 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.gui;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

import android.app.TabActivity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.View.OnKeyListener;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TabHost;
import android.widget.TabWidget;
import android.widget.TextView;
import de.avm.android.fritzapp.R;
import de.avm.android.fritzapp.com.ComSettingsChecker;
import de.avm.android.fritzapp.com.DataHub;
import de.avm.android.fritzapp.service.BoxServiceConnection;
import de.avm.android.fritzapp.service.IBoxService;
import de.avm.android.fritzapp.util.ResourceHelper;
import de.avm.android.tr064.Tr064Capabilities;
import de.avm.android.tr064.exceptions.SslErrorException;
import de.avm.android.tr064.model.Call;
import de.avm.android.tr064.model.CallLog;
import de.avm.android.tr064.model.Call.CALL_TYPE;
import de.usbi.android.util.adapter.ArrayAdapterExt;

/* GUI for the callist.*/
public class CallLogActivity extends TabActivity implements OfflineActivity
{
	private static final String TAG = "CallLogActivity";
	
	private static final int FASTLOAD_SIZE = 10;
	
	private static final String SAVED_TAB = "tab";
	private static final String SAVED_LOADING = "loading";
	private static final String SAVED_BLANK = "blank";
	
	private static final String PROGRESS = "progress-dummy";
	
	// Tabs
	private class TabInfo
	{
		public CALL_TYPE type;
		public int resId;
		public ListView view = null;
		
		public TabInfo(CALL_TYPE type, int resId)
		{
			this.type = type; this.resId = resId;
		}
	}

	// the tabs and its list views to be shown in this order
	private TabInfo[] mTabInfos = new TabInfo[]
	{
		new TabInfo(CALL_TYPE.UNSPECIFIED, R.id.ListContentAll), 	
		new TabInfo(CALL_TYPE.OUTGOING, R.id.ListContent1), 	
		new TabInfo(CALL_TYPE.INCOMING, R.id.ListContent2), 	
		new TabInfo(CALL_TYPE.MISSED, R.id.ListContent3) 	
	};

	private DataHub mFritzBox = new DataHub();

	private LoadListTask mLoaderTask = null;
	private WaitDialog mWaitDialog = null;
	
	private static final int LOAD_IDLE = 0;
	private static final int LOAD_FAST = 1;
	private static final int LOAD_ALL = 2;
	private int mLoadOnResume = LOAD_IDLE;
	private boolean mInitiallyBlank = false;
	
	// using savedInstanceState to restore loaded list is far too slow!
	private static CallLog mCallLog = null; 

	private static final long UPDATE_TIMEOUT = 60000;
	private Timer mUpdateTimeout = null;
	private Handler mHandler = new Handler();
	private BoxServiceConnection mBoxServiceConnection = new BoxServiceConnection();
	
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
        setContentView(R.layout.calllog);
		ResourceHelper.setAltTitleSymbol(this);

		// Title
		ResourceHelper.setTitle(this, R.string.calllog_label);

		// Tabs
    	createTabs();

		if (savedInstanceState == null)
		{
			mCallLog = null;
			mLoadOnResume = LOAD_FAST;
			mInitiallyBlank = true;
		}
		else
		{
			// restore log from prev. instance
			if (mCallLog != null)
			{
		    	mLoadOnResume = savedInstanceState.getInt(SAVED_LOADING);
		    	for (TabInfo tabInfo : mTabInfos)
		    	{
		    		CallLogAdapter adapter = new CallLogAdapter(mCallLog,
		    				tabInfo.type);
		    		if (mLoadOnResume == LOAD_ALL)
		    			adapter.appendProgressIndicator();
		    		tabInfo.view.setAdapter(adapter);
		    	}
				mInitiallyBlank = savedInstanceState.getBoolean(SAVED_BLANK);
			}
			else
			{
				mLoadOnResume = LOAD_FAST;
				mInitiallyBlank = true;
			}
			getTabHost().setCurrentTab(savedInstanceState.getInt(SAVED_TAB));
		}
	}

	@Override
	protected void onDestroy()
	{
		super.onDestroy();
		if (isFinishing()) mCallLog = null;
	}

	@Override
	protected void onResume()
	{
		super.onResume();
		if (!mBoxServiceConnection.bindService(getApplicationContext()))
			Log.w(TAG, "Failed to bind to BoxService.");
		switch (mLoadOnResume)
		{
			case LOAD_FAST:
				refreshCallLog();
				break;
			case LOAD_ALL:
				mLoaderTask = (LoadListTask)new LoadListTask()
						.execute(DataHub.CALLLOG_ALL);
				break;
		}
        startUpdateTimeout();
	}	

	@Override
	protected void onPause()
	{
		super.onPause();
		
		stopUpdateTimeout();
		dismissWait();
		mLoaderTask = null;
		mBoxServiceConnection.unbindService();
	}

	protected void onSaveInstanceState(Bundle outState)
	{
		outState.putInt(SAVED_LOADING, mLoadOnResume);
		outState.putInt(SAVED_TAB, getTabHost().getCurrentTab());
		outState.putBoolean(SAVED_BLANK, mInitiallyBlank);
	}

    private void startUpdateTimeout()
    {
    	stopUpdateTimeout();
		mUpdateTimeout = new Timer();
    	mUpdateTimeout.schedule(new TimerTask()
		{
			public void run()
			{
				mHandler.post(new Runnable()
				{
					public void run()
					{
				    	for (TabInfo tabInfo : mTabInfos)
				    	{
				    		ListAdapter adapter = tabInfo.view.getAdapter();
				    		if ((adapter != null) &&
				    			CallLogAdapter.class.equals(adapter.getClass()))
				    			((CallLogAdapter)adapter).notifyDataSetChanged();
				    	}
					}
				});
			}
		}, UPDATE_TIMEOUT, UPDATE_TIMEOUT);
    }
    
    private void stopUpdateTimeout()
    {
		if (mUpdateTimeout != null)
		{
			mUpdateTimeout.cancel();
			mUpdateTimeout.purge();
			mUpdateTimeout = null;
		}
    }

	private void createTabs()
	{
		// add tabs
		TabHost tabHost = getTabHost();
    	for (final TabInfo tabInfo : mTabInfos)
    	{
    		tabInfo.view = (ListView)findViewById(tabInfo.resId);
    		tabInfo.view.setSelector(R.drawable.list_selector_background);
    		tabInfo.view.setAdapter(null);

    		tabHost.addTab(tabHost.newTabSpec(tabInfo.type.toString())
    				.setIndicator("", tabInfo.type.getIconForCallType(this))
    				.setContent(tabInfo.resId));
    		
    		tabInfo.view.setOnKeyListener(new OnKeyListener()
    		{
    			public boolean onKey(View v, int keyCode, KeyEvent event)
    			{
    				if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER)
    				{
    					View item = tabInfo.view.getSelectedView();
    					if (item != null)
    						return item.performClick();
    				}
    				return false;
    			}
    		});
    		tabInfo.view.setOnItemClickListener(new AdapterView.OnItemClickListener()
    		{
    	        public void onItemClick(AdapterView<?> parent, View v, int position, long id)
    	        {
    	            onListItemClick((ListView)parent, v, position, id);
    	        }
    	    });
    	}

		// get rid of space occupied by empty labels in tab indicator
    	// available since Android 1.6
		try
		{
			fixTabWidget();
		}
    	catch (Exception exp)
    	{
    		exp.printStackTrace();
    	}
	}
	
	private void fixTabWidget()
			throws SecurityException, NoSuchMethodException,
			IllegalArgumentException, IllegalAccessException, InvocationTargetException
	{
		TabWidget tabWidget = getTabHost().getTabWidget();
    	Integer tabCount = null;

    	Method method = tabWidget.getClass().getMethod("getTabCount", (Class[])null);
		tabCount = (Integer)method.invoke(tabWidget, (Object[])null);
		if ((tabCount != null) && (tabCount.intValue() > 0))
		{
	    	method = tabWidget.getClass().getMethod("getChildTabViewAt",
	    			new Class[] {int.class});
	    	
	    	for (int ii = 0; ii < tabCount; ii++)
	    	{
	    		View viewIndicator = (View)method.invoke(tabWidget,
	    				new Object[] {ii});
	    		if (viewIndicator == null) continue;
	
	    		View view = viewIndicator.findViewById(android.R.id.title);
				if (view != null) view.setVisibility(View.GONE);
				
				view = viewIndicator.findViewById(android.R.id.icon);
				if (view != null)
				{
					ViewGroup.LayoutParams params = view.getLayoutParams();
					if (params.getClass() == RelativeLayout.LayoutParams.class)
					{
						((RelativeLayout.LayoutParams)params)
								.addRule(RelativeLayout.CENTER_VERTICAL);
						view.setLayoutParams(params);
					}
				}
			}
		}
	}
	
	private void refreshCallLog()
	{
		if (mLoaderTask == null)
		{
			showWait();
			mLoaderTask = (LoadListTask)new LoadListTask().execute(FASTLOAD_SIZE);
		}
	}

    private void onListItemClick(ListView listView, View view, int position, long id)
    {
    	Call call = (Call)listView.getItemAtPosition(position);
    	if ((call != null) && (call.getType() != Call.CALL_TYPE.UNSPECIFIED))
    	{
    		Intent intent = new Intent(this, CallDetailsActivity.class);
    		intent.putExtra(CallDetailsActivity.EXTRA_CALL_DATA, call);
    		startActivity(intent);
    	}
    }
	
	/**
	 * Adapter der die Daten der CallLog der Liste zur Verfügung stellt und es
	 * außerdem ermöglicht nach dem CALL_TYPE zu filtern.
	 */
	private class CallLogAdapter extends ArrayAdapterExt<Call>
	{
		/**
		 * Instantiates a new call log adapter.
		 * 
		 * @param callLog
		 *            the calllog
		 * @param filter
		 *            the initial filter 
		 */
		public CallLogAdapter(CallLog callLog, CALL_TYPE filter)
		{
			if (filter != CALL_TYPE.UNSPECIFIED)
			{
				for (Call call : callLog.getCalls())
					if (call.matchType(filter)) addEntry(call);
			}
			else addEntries(callLog.getCalls());
		}
		
		/**
		 * Updates adapter with new list
		 * 
		 * @param cl
		 *            the calllog
		 * @param filter
		 *            the initial filter
		 */
		public void update(CallLog callLog, CALL_TYPE filter)
		{
			int index = -1;
			ArrayList<Call> newList = callLog.getCalls(); 
			
			// check for changed items
			for (Call call : newList)
			{
				if (call.matchType(filter))
				{
					index++;
					if (index < getCount())
					{
						if (!call.equals(getItem(index)))
							setEntry(index, call);
					}
					else addEntry(call);
				}
			}
			
			// remove items
			while (getCount() > index + 1)
				removeEntry(getCount() - 1);
			
			notifyDataSetChanged();
		}
		
		public void appendProgressIndicator()
		{
			// add item with call type == UNSPECIFIED and id == PROGRESS
			Call dummy = new Call();
			dummy.setId(PROGRESS);
			addEntry(dummy);
			notifyDataSetChanged();
		}
		
		@Override
		public View populateView(final Call item, View view, ViewGroup viewGroup)
		{
			ViewHolder viewHolder; 
			if (view == null)
			{
				view = View.inflate(getBaseContext(), R.layout.t_callloglistitem, null);
				viewHolder = new ViewHolder(view);
				view.setTag(viewHolder);
			}
			else viewHolder = (ViewHolder)view.getTag();
			
			if ((item.getType() == Call.CALL_TYPE.UNSPECIFIED) &&
					PROGRESS.equals(item.getId()))
			{
				// progress indicator
				viewHolder.Content.setVisibility(View.GONE);
				if (viewHolder.Progress.getClass().equals(ViewStub.class))
					viewHolder.Progress = ((ViewStub)viewHolder.Progress).inflate();
				else
					viewHolder.Progress.setVisibility(View.VISIBLE);
			}
			else
			{
				// call item
				viewHolder.Content.setVisibility(View.VISIBLE);
				viewHolder.Progress.setVisibility(View.GONE);

				viewHolder.TypeIcon.setImageDrawable(item.getIcon(getBaseContext()));
				String partnerNumber = item.getPartnerNumber();
				if (partnerNumber.length() == 0)
					partnerNumber = getBaseContext().getString(R.string.unknown);
				if (item.getPartnerName().trim().length() == 0) 
				{
					viewHolder.Name.setText(partnerNumber);
					viewHolder.Number.setText("");
				}
				else
				{
					viewHolder.Name.setText(item.getPartnerName());
					viewHolder.Number.setText(partnerNumber);
				}
	            // Set the date/time field by mixing relative and absolute times.
				viewHolder.Info.setText(DateUtils.getRelativeTimeSpanString(
						item.getTimeStamp().getTime(), System.currentTimeMillis(),
						DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE));
			}
			
			return view;
		}
		
		private class ViewHolder
		{
			public View Content;
			public View Progress;
			public ImageView TypeIcon;
			public TextView Name;
			public TextView Number;
			public TextView Info;
			
			public ViewHolder(View view)
			{
				// number label not used here
				((View)view.findViewById(R.id.CallLogEntryNumberLabel))
						.setVisibility(View.GONE);

				Content = (View)view.findViewById(R.id.Content);
				Progress = (View)view.findViewById(R.id.Progress);
				TypeIcon = (ImageView)view.findViewById(R.id.TypeIcon);
				Name = (TextView)view.findViewById(R.id.CallLogEntryName);
				Number = (TextView)view.findViewById(R.id.CallLogEntryNumber);
				Info = (TextView)view.findViewById(R.id.CallLogEntryInfo);
			}
		}
	}
	
	public static Intent showIntent(Context context)
	{
		if (canShow())
			return new Intent(context, CallLogActivity.class)
					.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
		return null;
	}
	
	public static Boolean canShow()
	{
		Tr064Capabilities capabilities = ComSettingsChecker.getTr064Capabilities();
		return (capabilities != null) &&
				capabilities.has(Tr064Capabilities.Capability.CALLLIST);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu)
	{
	    getMenuInflater().inflate(R.menu.calllog_menu, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item)
	{
		switch (item.getItemId())
		{
			case R.id.Refresh:
				mLoadOnResume = LOAD_FAST;
				refreshCallLog();
				break;
		}
		return true;
	}

	private void onError(LoadListTask thread, String message)
	{
		if (mLoaderTask == thread)
		{
			dismissWait();
			mLoaderTask = null;

			if (mLoadOnResume == LOAD_FAST)
			{
				mCallLog = null;
				for (TabInfo tabInfo : mTabInfos)
		    		tabInfo.view.setAdapter(null);
			}
			else if (mCallLog != null)
			{
				for (TabInfo tabInfo : mTabInfos)
				{
					CallLogAdapter adapter =
						(CallLogAdapter)tabInfo.view.getAdapter();
					if (adapter != null)
						adapter.update(mCallLog, tabInfo.type);
				}
				
			}
			mLoadOnResume = LOAD_IDLE;
			mInitiallyBlank = false;
	
			try
			{
				TextDialog.createOk(this, message).show();
			}
			catch(Exception e)
			{
				e.printStackTrace();
			}
		}
	}

	private void onListLoaded(LoadListTask thread, CallLog calllog)
	{
		if (mLoaderTask == thread)
		{
			boolean mustLoadedAll = (mLoadOnResume == LOAD_FAST) &&
					(calllog.getCalls().size() == FASTLOAD_SIZE);
			
			mCallLog = calllog;
			mInitiallyBlank = false;
	    	for (TabInfo tabInfo : mTabInfos)
	    	{
	    		CallLogAdapter adapter = (CallLogAdapter)tabInfo.view.getAdapter();
	    		if ((adapter == null) || (mLoadOnResume == LOAD_FAST))
	    		{
	    			adapter = new CallLogAdapter(calllog, tabInfo.type);
		    		if (mustLoadedAll) adapter.appendProgressIndicator();
		    		tabInfo.view.setAdapter(adapter);
	    		}
	    		else adapter.update(calllog, tabInfo.type); 
	    	}
			dismissWait();
			if (mustLoadedAll)
			{
				// load all now
				mLoadOnResume = LOAD_ALL;
				mLoaderTask = (LoadListTask)new LoadListTask().execute(DataHub.CALLLOG_ALL);
			}
			else
			{
				mLoaderTask = null;
				mLoadOnResume = LOAD_IDLE;
			}
		}
	}
	
	private void showWait()
	{
		if (mWaitDialog == null)
		{
			mWaitDialog = WaitDialog.show(this, R.string.wait_dialog,
					new DialogInterface.OnCancelListener()
			{
				public void onCancel(DialogInterface dialog)
				{
					if (dialog == mWaitDialog)
					{
						// cancel pending load
						mWaitDialog = null;
						dialog.dismiss();
						mLoaderTask = null;
						mLoadOnResume = LOAD_IDLE;
						
						// if first loading, close activity
						if (mInitiallyBlank)
							CallLogActivity.this.finish();
					}
					else dialog.dismiss();
				}
			});
		}
	}
	
	private void dismissWait()
	{
		if (mWaitDialog != null)
		{
			mWaitDialog.dismiss();
			mWaitDialog = null;
		}
	}
	
	private class LoadListTask extends AsyncTask<Integer, Integer,
			CallLog>
	{
		private Exception mError = null;
		
		@Override
		protected CallLog doInBackground(Integer... params)
		{
			try
			{
				return mFritzBox.getCallLog(CallLogActivity.this,
						params[0]);
			}
			catch(Exception exp)
			{
				mError = exp;
				exp.printStackTrace();
				if (IOException.class.isAssignableFrom(exp.getClass()))
				{
					IBoxService srv = mBoxServiceConnection.getService();
					if (srv != null) srv.reconnect();
				}
				return null;
			}
		}
		
		@Override
		protected void onPostExecute(CallLog calllog)
		{
			super.onPostExecute(calllog);
			
			if (calllog == null)
			{
				if (SslErrorException.isSslError(mError))
					onError(this, SslErrorException.getDisplayMessage(
							CallLogActivity.this, mError));
				else
					onError(this, CallLogActivity.this.
							getString(R.string.soap_tranfer_failed));
			}
			else onListLoaded(this, calllog);
		}
	}
}
