content: /*
   * 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);
  		}
  	}
  }
  