/*
 * Androzic - android navigation client that uses OziExplorer maps (ozf2, ozfx3).
 * Copyright (C) 2010-2014  Andrey Novikov <http://andreynovikov.info/>
 *
 * This file is part of Androzic application.
 *
 * Androzic 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.

 * Androzic 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 Androzic.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.androzic;

import java.io.File;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.app.ListFragment;
import android.support.v7.app.AppCompatActivity;
import android.util.DisplayMetrics;
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.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.androzic.map.BaseMap;
import com.androzic.map.OnMapActionListener;
import com.androzic.map.online.OnlineMap;

public class MapList extends ListFragment
{
	private final Handler handler = new Handler();

	private OnMapActionListener mapActionsCallback;
	private MapListAdapter adapter;
	private TreeNode<BaseMap> mapsTree = new TreeNode<>();
	private MapComparator mapComparator = new MapComparator();
	private boolean populated;
	private TreeNode<BaseMap> currentTree;
	private ProgressBar progressBar;
	private int shortAnimationDuration;
	
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setRetainInstance(true);
		shortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime);
		currentTree = mapsTree;
		populated = false;
	}
	
	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
	{
		View view = inflater.inflate(R.layout.list_with_empty_view_and_progressbar, container, false);
		
		progressBar = (ProgressBar) view.findViewById(R.id.progressbar);

		view.setFocusableInTouchMode(true);
		view.requestFocus();
		view.setOnKeyListener(new View.OnKeyListener() {
			@Override
			public boolean onKey(View v, int keyCode, KeyEvent event)
			{
				if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK)
				{
					if (currentTree != mapsTree)
					{
						currentTree = currentTree.parent;
						adapter.notifyDataSetChanged();
						return true;
					}
				}
				return false;
			}
		});

		return view;
	}

	@Override
	public void onActivityCreated(Bundle savedInstanceState)
	{
		super.onActivityCreated(savedInstanceState);
		setHasOptionsMenu(true);

		TextView emptyView = (TextView) getListView().getEmptyView();
		if (emptyView != null)
			emptyView.setText(R.string.msg_empty_map_list);

		Activity activity = getActivity();

		adapter = new MapListAdapter(activity);
		setListAdapter(adapter);
	}
	
	@Override
	public void onAttach(Activity activity)
	{
		super.onAttach(activity);

		// This makes sure that the container activity has implemented
		// the callback interface. If not, it throws an exception
		try
		{
			mapActionsCallback = (OnMapActionListener) activity;
		}
		catch (ClassCastException e)
		{
			throw new ClassCastException(activity.toString() + " must implement OnMapActionListener");
		}
	}

	@Override
	public void onResume()
	{
		super.onResume();
		((AppCompatActivity) getActivity()).getSupportActionBar().setTitle(R.string.maplist_name);
		if (!populated)
			populateItems();
	}

	@Override
	public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
	{
		inflater.inflate(R.menu.maplist_menu, menu);
	}

	@Override
	public void onPrepareOptionsMenu(final Menu menu)
	{
		SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(getActivity());
		boolean useIndex = settings.getBoolean(getString(R.string.pref_usemapindex), getResources().getBoolean(R.bool.def_usemapindex));
		menu.findItem(R.id.action_reset_index).setEnabled(useIndex);
	}
	
	@SuppressLint("NewApi")
	private void crossfade(boolean direct)
	{
		View listView = adapter.getCount() > 0 ? getListView() : getListView().getEmptyView();
		final View from = direct ? progressBar : listView;
		final View to = direct ? listView : progressBar;

		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1)
		{
			from.setVisibility(View.GONE);
			to.setVisibility(View.VISIBLE);
		}
		else
		{
			// Set the content view to 0% opacity but visible, so that it is visible
			// (but fully transparent) during the animation.
			to.setAlpha(0f);
			to.setVisibility(View.VISIBLE);
	
			// Animate the content view to 100% opacity, and clear any animation
			// listener set on the view.
			to.animate().alpha(1f).setDuration(shortAnimationDuration).setListener(null);
	
			// Animate the loading view to 0% opacity. After the animation ends,
			// set its visibility to GONE as an optimization step (it won't
			// participate in layout passes, etc.)
			from.animate().alpha(0f).setDuration(shortAnimationDuration).setListener(new AnimatorListenerAdapter() {
				@Override
				public void onAnimationEnd(Animator animation)
				{
					from.setVisibility(View.GONE);
				}
			});
		}
	}

	private void populateItems()
	{
		crossfade(false);
		
		new Thread(new Runnable() 
		{
			public void run() 
			{
				populated = true;
				Androzic application = Androzic.getApplication();
				TreeNode<BaseMap> onlinemaps = null;
				String mappath = application.getMapPath();
	   	        
				for (BaseMap map : application.getMaps())
				{
					if (map instanceof OnlineMap)
					{
						if (onlinemaps == null)
							onlinemaps = mapsTree.addChild(getResources().getString(R.string.online_maps));
						onlinemaps.addChild(map.path, map);
					}
					else
					{
						String fn = map.path;
						if (fn.startsWith(mappath))
						{
							fn = fn.substring(mappath.length() + 1);
						}
						String[] components = fn.split(File.separator);
						TreeNode<BaseMap> folder = mapsTree;
						for (int i = 0; i < components.length - 1; i++)
						{
							TreeNode<BaseMap> subfolder = folder.findChild(components[i]);
							if (subfolder == null)
								subfolder = folder.addChild(components[i]);
							folder = subfolder;
						}
						folder.addChild(components[components.length - 1], map);
					}
					
					mapsTree.sort(mapComparator);
					currentTree = mapsTree;
				}

				handler.post(updateResults);
			} 
		}).start(); 
	}

	final Runnable updateList = new Runnable() 
	{
		public void run() 
        {
			populateItems();
        }
	};

	final Runnable updateResults = new Runnable() 
	{
		public void run() 
        {
			adapter.notifyDataSetChanged();
			crossfade(true);
        }
	};

	@Override
	public void onListItemClick(ListView l, View v, int position, long id) 
	{
		TreeNode<BaseMap> item = currentTree.children.get(position);
		if (item.data != null)
		{
			mapActionsCallback.onMapSelected(item.data);
			getFragmentManager().popBackStack();
		}
		else
		{
			item.sort(mapComparator);
			currentTree = item;
			adapter.notifyDataSetChanged();
		}
	}

	@Override
	public boolean onOptionsItemSelected(final MenuItem item)
	{
		switch (item.getItemId())
		{
			case R.id.action_reset_index:
				crossfade(false);
				mapsTree.clear();
				currentTree = mapsTree;
				adapter.notifyDataSetChanged();

				new Thread(new Runnable() 
				{ 
					public void run() 
					{
						Androzic application = Androzic.getApplication();
						application.resetMaps();
						handler.post(updateList);
					} 
				}).start(); 
				break;
		}
		return true;
	}

	public class MapListAdapter extends BaseAdapter
	{
		private static final int VIEW_TYPE_FOLDER = 0;
		private static final int VIEW_TYPE_MAP = 1;

		private LayoutInflater mInflater;
		private double ppcm;

		public MapListAdapter(Context context)
		{
			mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

			SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
			Resources resources = getResources();
			boolean scaleInMeters = settings.getBoolean(getString(R.string.pref_maplistscale), resources.getBoolean(R.bool.def_maplistscale));
			
			DisplayMetrics metrics = resources.getDisplayMetrics();
			ppcm = metrics.xdpi / 2.54;
			if (!scaleInMeters)
				ppcm *= 100;
		}

		@Override
		public int getViewTypeCount()
		{
			return 2;
		}

		@Override
		public int getItemViewType(int position)
		{
			TreeNode<BaseMap> item = getItem(position);
			if (item.data != null)
			{
				return VIEW_TYPE_MAP;
			}
			else
			{
				return VIEW_TYPE_FOLDER;
			}
		}

		@Override
		public TreeNode<BaseMap> getItem(int position)
		{
			return currentTree.children.get(position);
		}

		@Override
		public long getItemId(int position)
		{
			return currentTree.children.get(position).getId();
		}

		@Override
		public int getCount()
		{
			return currentTree.children.size();
		}

		@Override
		public View getView(int position, View convertView, ViewGroup parent)
		{
			MapListItemHolder itemHolder;
			TreeNode<BaseMap> item = getItem(position);

			int type = getItemViewType(position);
			if (convertView == null)
			{
				itemHolder = new MapListItemHolder();
				if (type == VIEW_TYPE_FOLDER)
				{
					convertView = mInflater.inflate(R.layout.list_item_folder, parent, false);
					itemHolder.name = (TextView) convertView.findViewById(R.id.name);
				}
				else if (type == VIEW_TYPE_MAP)
				{
					convertView = mInflater.inflate(R.layout.list_item_map, parent, false);
					itemHolder.name = (TextView) convertView.findViewById(R.id.name);
					itemHolder.scale = (TextView) convertView.findViewById(R.id.scale);
					itemHolder.filename = (TextView) convertView.findViewById(R.id.filename);
				}
				else
				{
					return null;
				}
				convertView.setTag(itemHolder);
			}
			else
			{
				itemHolder = (MapListItemHolder) convertView.getTag();
			}

			if (type == VIEW_TYPE_FOLDER)
			{
				itemHolder.name.setText(item.name);
			}
			else if (type == VIEW_TYPE_MAP)
			{
				itemHolder.name.setText(item.data.title);
				itemHolder.filename.setText(item.name);
				itemHolder.scale.setText(String.format("1:%,d", (int) (item.data.getAbsoluteMPP() * ppcm)));
			}

			return convertView;
		}
	
		@Override
		public boolean hasStableIds()
		{
			return true;
		}
	}

	private static class MapListItemHolder
	{
		TextView name;
		TextView scale;
		TextView filename;
	}

	public class TreeNode<T>
	{
		String name;
		T data;
		TreeNode<T> parent;
		List<TreeNode<T>> children;

		public TreeNode()
		{
			this.children = new LinkedList<>();
		}

		public TreeNode(String name)
		{
			this();
			this.name = name;
		}

		public TreeNode(String name, T data)
		{
			this(name);
			this.data = data;
		}

		public TreeNode<T> addChild(String name)
		{
			TreeNode<T> childNode = new TreeNode<>(name);
			childNode.parent = this;
			children.add(childNode);
			return childNode;
		}

		public TreeNode<T> addChild(String name, T child)
		{
			TreeNode<T> childNode = new TreeNode<>(name, child);
			childNode.parent = this;
			children.add(childNode);
			return childNode;
		}

		public TreeNode<T> findChild(String name)
		{
			for (TreeNode<T> child : children)
			{
				if (name.equals(child.name))
					return child;
			}
			return null;
		}

		public long getId()
		{
			if (data != null)
				return data.hashCode();
			else
				return name.hashCode();
		}

		public void clear()
		{
			for (TreeNode<T> child : children)
			{
				child.clear();
			}
			children.clear();
		}

		public void sort(Comparator<TreeNode<T>> comparator)
		{
			Collections.sort(children, comparator);
		}
	}

	class MapComparator implements Comparator<TreeNode<BaseMap>>
	{
		@Override
		public int compare(TreeNode<BaseMap> a, TreeNode<BaseMap> b)
		{
			if (a.data != null && b.data != null)
				return a.data.title.compareToIgnoreCase(b.data.title);
			if (a.data != null)
				return 1;
			if (b.data != null)
				return -1;
			return a.name.compareToIgnoreCase(b.name);
		}
	}
}