package com.ethostream.ethoandroid;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONObject;

import android.app.ListActivity;
import android.app.ProgressDialog;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnKeyListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ListView;
import android.widget.TextView;

public class APStatus extends ListActivity
{
	
	private static final String REMOTE_QUERY_SITE = "http://android.telkonet.com/dbapstatus.php";
	
	private static final String AP_ID = "ID";
	private static final String AP_NAME = "Name";
	private static final String AP_LOCATION = "Location";
	private static final String AP_MAC= "MacAddress";
	private static final String AP_IP = "IP";
	private static final String AP_CHANNEL = "Channel";
	private static final String AP_USER = "Username";
	private static final String AP_PASSWD = "Password";
	private static final String AP_PARENT = "Parent";
	private static final String AP_PROPID = "PropertyID";
	private static final String AP_TYPE = "DeviceType";
	private static final String AP_OKIFRED = "NoMonitor";
	private static final String AP_STATUS = "Status";
	private static final String AP_LASTGOOD = "LastGood";
	private static final String AP_LASTFAIL = "LastFailed";
	private static final String AP_DEPTH = "Depth";
	private static final String AP_DIRECTURL = "DirectURL";
	private static final String AP_VID = "VID";

	protected static final int ACTIVITY_DEVICE = 0;
	
	private int propID = 0;
	ArrayList<ContentValues> deviceList = new ArrayList<ContentValues>();
	private RowAdapter rowAdapter = null;

	private Context context;
	TextView filter;
	CheckBox onlyRed;
	
	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		context = this;
		
		Bundle bundle = getIntent().getExtras();
		propID = Integer.parseInt(bundle.getString("ID"));
		if (propID < 1)
		{
			Log.e(this.toString(), "Invalid ID, quitting.");
			setResult(RESULT_CANCELED);
			finish();
		}
		
		setContentView(R.layout.apstatus);
		
		DownloadThread dlt = new DownloadThread();
		dlt.execute();
		try
		{
			//TODO: add timeout and message
			dlt.get();
		} catch (InterruptedException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ExecutionException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		rowAdapter = new RowAdapter(this, R.layout.apstatus_item, deviceList);
		setListAdapter(rowAdapter);

		
		ListView lv = getListView();
		filter = (TextView) findViewById(R.id.apstatus_filter);
		
		//TODO: Apparently android phones don't like to honor the "no suggestions" hints, and these will prevent the OnKeyListener from detecting the key press. There was a work around online that I will (unfortunately) have to implement (since it will be slower). :(
		filter.setOnKeyListener(new OnKeyListener(){
			@Override
			public boolean onKey(View v, int keyCode, KeyEvent event)
			{
				TextView tv = (TextView)v;
	            CharSequence val = tv.getText();
	            rowAdapter.getFilter().filter(val);
				return false;
			}
		});
		
		/*
		 * Checkbox with a listener - uses the same filter method as the textbox
		 * filter and passes whatever text happens to be in the filter text box
		 * when it is triggered
		 */
		onlyRed = (CheckBox) findViewById(R.id.apstatus_showOnlyRed);
		onlyRed.setOnCheckedChangeListener(new OnCheckedChangeListener()
		{
			public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
			{
				rowAdapter.getFilter().filter(filter.getText());
			}
		});

		//TODO: Global TODO: Should really try to set initial values to save on growing the stack

		lv.setOnItemClickListener(new OnItemClickListener() 
		{
			public void onItemClick(AdapterView<?> parent, View view, int position, long id) 
			{
				// When clicked, do something
				switchView(ACTIVITY_DEVICE, "DID", ((RowAdapter) parent.getAdapter()).getItem(position).getAsString(AP_ID));
			}
		});
		
	}
	
	private void refresh()
	{
		rowAdapter.clear();
		DownloadThread dlt = new DownloadThread();
		dlt.execute();
		try
		{
			//TODO: add timeout and message
			dlt.get();
		} catch (InterruptedException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ExecutionException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		rowAdapter = new RowAdapter(this, R.layout.apstatus_item, deviceList);
		setListAdapter(rowAdapter);
		//filter.setText("");
		onlyRed.setChecked(false);
	}
	
    @Override
    public boolean onCreateOptionsMenu(Menu menu) 
    {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.apstatus_menu, menu);
        return true;
    }
    
    @Override
    public boolean onOptionsItemSelected(MenuItem item) 
    {
        // Handle item selection
        switch (item.getItemId()) 
        {
        case R.id.apstatus_menu_adddevice:
        	// Do something
        	switchView(ACTIVITY_DEVICE, "DID", "0");
            return true;
        case R.id.apstatus_menu_refresh:
        	// Do something
        	refresh();
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }
	
	private void switchView(int ACTIVITY, String key, String value)
    {
    	switch(ACTIVITY)
    	{
    		case ACTIVITY_DEVICE:
    		{
            	Intent i = new Intent(this, AddDevice.class);
            	i.putExtra(key, value);
            	i.putExtra("PID", Integer.toString(propID));
            	i.putExtra("PARENTS", deviceList);
            	startActivityForResult(i, ACTIVITY_DEVICE);
            	break;
    		}
    		default:
    		{
    			break;
    		}
    	}
    }
	
	private class DownloadThread extends AsyncTask<Void, Void, ArrayList<ContentValues>> 
	{
		final ProgressDialog dialog = new ProgressDialog(context);
		@Override
		protected void onPreExecute()
		{
			dialog.setTitle(R.string.activity_loading_label);
			dialog.setMessage(context.getString(R.string.activity_loading_message));
			dialog.show();
		}
		@Override
		protected ArrayList<ContentValues> doInBackground(Void... params) 
		{
			ArrayList<ContentValues> tmpDeviceList = getRemotePropertyInfo();
			return orderByParent(tmpDeviceList);
		}
		@Override
		protected void onPostExecute(ArrayList<ContentValues> retValue) 
	     {
	         dialog.dismiss();
	     }
	 }
	
	//TODO: Global TODO: JSON functions should fail more gracefully if there is a remote error
	private ArrayList<ContentValues> getRemotePropertyInfo()
	{
		InputStream is = null;
		ArrayList<NameValuePair> queryResult = new ArrayList<NameValuePair>();
		StringBuilder sb = null;
		String result;
		
		//httpPost
		try{
			HttpClient httpclient = new DefaultHttpClient();
			HttpPost httppost = new HttpPost(REMOTE_QUERY_SITE + "?ID=" + Integer.toString(propID));
			httppost.setEntity(new UrlEncodedFormEntity(queryResult));
			HttpResponse response = httpclient.execute(httppost);
			HttpEntity entity = response.getEntity();
			is = entity.getContent();
		}catch(Exception e){
			Log.e(this.toString(), "Error in http connection"+e.toString());
			return new ArrayList<ContentValues>(0);
		}
		//convertString
		try{
			BufferedReader reader = new BufferedReader(new InputStreamReader(is,"iso-8859-1"),8);
			sb = new StringBuilder();
			sb.append(reader.readLine() + "\n");
			String line="0";
			while ((line = reader.readLine()) != null) {
				sb.append(line + "\n");
			}
			is.close();
			result=sb.toString();
		}catch(Exception e){
			Log.e("log_tag", "Error converting result "+e.toString());
			return new ArrayList<ContentValues>(0);
		}
		//parseJSON
		try
		{
			JSONArray jArray = new JSONArray(result);
			JSONObject json_data=null;
			for (int i = 0; i < jArray.length(); i++)
			{
				json_data = jArray.getJSONObject(i);
				ContentValues cv = new ContentValues();
				cv.put(AP_ID, json_data.getString(AP_ID));
				cv.put(AP_IP, json_data.getString(AP_IP));
				cv.put(AP_NAME, json_data.getString(AP_NAME));
				cv.put(AP_LOCATION, json_data.getString(AP_LOCATION));
				cv.put(AP_MAC, json_data.getString(AP_MAC));
				cv.put(AP_CHANNEL, json_data.getString(AP_CHANNEL));
				cv.put(AP_USER, json_data.getString(AP_USER));
				cv.put(AP_PASSWD, json_data.getString(AP_PASSWD));
				cv.put(AP_PARENT, json_data.getString(AP_PARENT));
				cv.put(AP_PROPID, json_data.getString(AP_PROPID));
				cv.put(AP_TYPE, json_data.getString(AP_TYPE));
				cv.put(AP_OKIFRED, json_data.getString(AP_OKIFRED));
				cv.put(AP_STATUS, json_data.getString(AP_STATUS));
				cv.put(AP_LASTGOOD, json_data.getString(AP_LASTGOOD));
				cv.put(AP_LASTFAIL, json_data.getString(AP_LASTFAIL));
				cv.put(AP_DIRECTURL, json_data.getString(AP_DIRECTURL));
				cv.put(AP_VID, json_data.getString(AP_VID));
				deviceList.add(cv);
			}
		}
		catch(Exception e)
		{
			Log.e(this.toString(), "FAILURE TO ACQUIRE REMOTE PROP INFO");
			e.printStackTrace();
			return new ArrayList<ContentValues>(0);
		}
		return deviceList;
	}
	
	private ArrayList<ContentValues> orderByParent(ArrayList<ContentValues> unorderedList)
	{
		// create an  empty list that will be made in order, initialize to size of unorderedList
		ArrayList<ContentValues> orderedList = new ArrayList<ContentValues>(unorderedList.size());
		// All roots will have depth of 0
		int depth = 0;
		int parentid = 0;
		// first need to find roots
		for (int i = 0; i < unorderedList.size(); i++)
		{
			if (unorderedList.get(i).getAsInteger(AP_PARENT) == 0)
			{
				// Found a root AP
				// set depth
				unorderedList.get(i).put(AP_DEPTH, depth);
				parentid = unorderedList.get(i).getAsInteger(AP_ID);
				// put into ordered list
				orderedList.add(unorderedList.get(i));
				// Call recursive function to find children in order
				findChildren(unorderedList, orderedList, parentid, depth + 1);
			}
		}
		// last root ap found
		// here we should be done, but we need to check for APs that have a
		// 	parent set, but the parent doesn't exist anymore
		if (orderedList.size() != unorderedList.size())
		{
			Log.i(this.toString(), "List size mismatch; Looking for Orphans...");
			findOrphans(unorderedList, orderedList, parentid, depth);
		}
		
		return orderedList;
	}
	
	private void findChildren(ArrayList<ContentValues> unorderedList, ArrayList<ContentValues> orderedList, int parentid, int depth)
	{
		// recursive function, want to find child of parent id, and all its children
		int newparentid = 0;
		for (int i = 0; i < unorderedList.size(); i++)
		{
			if (unorderedList.get(i).getAsInteger(AP_PARENT) == parentid)
			{
				// Found a child AP
				// set depth
				unorderedList.get(i).put(AP_DEPTH, depth);
				newparentid = unorderedList.get(i).getAsInteger(AP_ID);
				// put into ordered list
				orderedList.add(unorderedList.get(i));
				// Call recursive function to find children in order
				findChildren(unorderedList, orderedList, newparentid, depth + 1);
			}
		}
		// last child ap found		
		return;	
	}
	
	private void findOrphans(ArrayList<ContentValues> unorderedList, ArrayList<ContentValues> orderedList, int parentid, int depth)
	{
		// recursive function, want to find any AP with a parent ID set, but that hasn't been found as a child
		int newparentid = 0;
		boolean orphan = true;
		for (int i = 0; i < unorderedList.size(); i++)
		{
			// NOTE: this assumes that a device can not be its own parent
			for (int j = 0; j < orderedList.size(); j++)
			{
				if (unorderedList.get(i).getAsInteger(AP_ID).intValue() == orderedList.get(j).getAsInteger(AP_ID).intValue())
				{
					// Found a match, we don't care...
					orphan = false;
					break;
				}
				if (unorderedList.get(i).getAsInteger(AP_PARENT).intValue() == orderedList.get(j).getAsInteger(AP_ID).intValue())
				{
					// Found a match, we don't care...
					orphan = false;
					break;
				}
			}
			if (orphan)
			{
				// Make sure that this orphan doesn't have a orphan parent
				for (int j = i; j < unorderedList.size(); j++)
				{
					if (unorderedList.get(i).getAsInteger(AP_PARENT).intValue() == unorderedList.get(j).getAsInteger(AP_ID).intValue())
					{
						// Found a match, we don't care...
						orphan = false;
						break;
					}
				}
				if (orphan)
				{
					// Found an orphan AP
					// set depth
					unorderedList.get(i).put(AP_DEPTH, depth);
					newparentid = unorderedList.get(i).getAsInteger(AP_ID);
					// put into ordered list
					orderedList.add(unorderedList.get(i));
					// Call recursive function to find children in order
					findChildren(unorderedList, orderedList, newparentid, depth + 1);
				}
			}
			orphan = true;
		}

		// last orphan AP found		
		return;	
	}
	
	private class RowAdapter extends ArrayAdapter<ContentValues> implements Filterable
	{

        private ArrayList<ContentValues> list;
        private ArrayList<ContentValues> originalList;
        private Filter filter;

        public RowAdapter(Context context, int textViewResourceId, ArrayList<ContentValues> apList) 
        {
                super(context, textViewResourceId, apList);
                list = apList;
                originalList = list;
        }
        
        @Override
        public int getCount() 
        {
            return list.size();
        }
        
        public ContentValues getItem(int position) 
        {
            return list.get(position);
        }
        
        @Override
        public View getView(int position, View convertView, ViewGroup parent) 
        {
        	View v = convertView;
        	if (v == null) 
        	{
        		LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        		v = vi.inflate(R.layout.apstatus_item, parent, false);
        	}
        	
        	ContentValues cv = list.get(position);
        	TextView location = (TextView) v.findViewById(R.id.apstatus_location);
        	TextView name = (TextView) v.findViewById(R.id.apstatus_name);
        	TextView mac = (TextView) v.findViewById(R.id.apstatus_mac);
        	TextView ip = (TextView) v.findViewById(R.id.apstatus_ip);
        	
        	location.setText(cv.getAsString(AP_LOCATION));
        	name.setText(cv.getAsString(AP_NAME));
        	mac.setText(cv.getAsString(AP_MAC));
        	ip.setText(cv.getAsString(AP_IP));
        	
        	/*
        	 * set color of view based on status, must include else statement
        	 * because views may be reused, resulting in color being applied to 
        	 * inappropriate rows 
        	 */
        	if (cv.getAsInteger(AP_STATUS) == 0)
        	{
        		v.setBackgroundColor(android.graphics.Color.RED);
        	}
        	else
        	{
        		v.setBackgroundColor(android.graphics.Color.TRANSPARENT);
        	}
        	// set indentation based on depth (root = 0, each child +1)
        	if (cv.getAsInteger(AP_DEPTH) != 0)
        	{
            	v.setPadding((20 * cv.getAsInteger(AP_DEPTH)), 0, 0, 0);
        	}
        	else
        	{
        		v.setPadding(0, 0, 0, 0);
        	}        	
        	return v;
        }
        
        public Filter getFilter()
        {
        	if (filter == null)
        	{
        		filter = new APFilter();
        	}
        	return filter;
        }
        
        private class APFilter extends Filter
        {

			@Override
			protected FilterResults performFiltering(CharSequence constraint)
			{
				//TODO: For some reason filtering is only being performed after the backspace key is pressed
				
				
				final ArrayList<ContentValues> constraintList = new ArrayList<ContentValues>(list.size());
				FilterResults results = new FilterResults();
				CheckBox showRed = (CheckBox)findViewById(R.id.apstatus_showOnlyRed);
				
				// If for some reason the filter list is lost, start over with the original list
				if (list == null)
				{
					list = originalList;
					results.values = list;
					results.count = list.size();
					return results;
				}
				
				// If there is no filter, and no RED return all APs
				if ((constraint == null || constraint.length() == 0) && !showRed.isChecked())
				{
					results.values = originalList;
					results.count = originalList.size();
					return results;
				}
				
				// If there is no filter, but RED is set, return all RED APs
				if (showRed.isChecked() && constraint.length() == 0)
				{
					int size = list.size();
					for (int i = 0; i < size; i++)
					{
						if (list.get(i).getAsInteger(AP_STATUS) == 0)
						{
							constraintList.add(list.get(i));
						}
					}
					results.values = constraintList;
					results.count = constraintList.size();
					return results;
				}
				
				//
				// Special cases handled, start filtering
				//
				
				// Reset the filter list in case the next stroke (e.g. backspace) reveals more results rather than less
				list = originalList;
				
				// Convert the filter to lower case for case insensitive filtering
				String constraintString = constraint.toString().toLowerCase();
				
				// Iterate through the list of APs
				int size = list.size();
				for (int i = 0; i < size; i++)
				{
					// Case insensitive string evaluation
					// Based on Location, IP, MAC, Name (Connection Type), and Status (must be RED)
					if (showRed.isChecked())
					{
						if ((list.get(i).getAsString(AP_LOCATION).toLowerCase().contains(constraintString) ||
								list.get(i).getAsString(AP_IP).toLowerCase().contains(constraintString) ||
								list.get(i).getAsString(AP_MAC).toLowerCase().contains(constraintString) ||
								list.get(i).getAsString(AP_NAME).toLowerCase().contains(constraintString)) &&
								list.get(i).getAsInteger(AP_STATUS) == 0)
						{
							constraintList.add(list.get(i));
						}
					}
					// Case insensitive string evaluation
					// Based on Location, IP, MAC, and  Name (Connection Type) (may be RED or NOT)
					else
					{
						if (list.get(i).getAsString(AP_LOCATION).toLowerCase().contains(constraintString) ||
								list.get(i).getAsString(AP_IP).toLowerCase().contains(constraintString) ||
								list.get(i).getAsString(AP_MAC).toLowerCase().contains(constraintString) ||
								list.get(i).getAsString(AP_NAME).toLowerCase().contains(constraintString))
						{
							constraintList.add(list.get(i));
						}
					}
				}
				// Return the filtered list
				results.values = constraintList;
				results.count = constraintList.size();
				return results;
			}

			@SuppressWarnings("unchecked")
			@Override
			protected void publishResults(CharSequence constraint,
					FilterResults results)
			{
				// TODO See if it is possible to avoid the unchecked warning
				list = (ArrayList<ContentValues>) results.values;
				if (results.count > 0) {
                    notifyDataSetChanged();
                } else {
                    notifyDataSetInvalidated();
                }
			}
        	
        }
        
	}
	
}
