/*
 * �Copyright 2013 Jose F. Maldonado�
 *
 *  This file is part of aFileDialog.
 *
 *  aFileDialog is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published 
 *  by the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  aFileDialog 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 Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with aFileDialog. If not, see <http://www.gnu.org/licenses/>.
 */

package ar.com.daidalos.afiledialog;

import java.io.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Environment;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import ar.com.daidalos.afiledialog.view.FileItem;
import cn.leancloud.sample.R;

/**
 * This class implements the common features of a file chooser.
 */
class FileChooserCore {

	// ----- Attributes ----- //
	
	/**
	 * The file chooser in which all the operations are performed.
	 */
	private FileChooser chooser;
	
	/**
	 * The listeners for the event of select a file.
	 */
	private List<OnFileSelectedListener> listeners;
	
	/**
	 * A regular expression for filter the files.
	 */
	private String filter;
	
	/**
	 * A boolean indicating if only the files that can be selected (they pass the filter) must be show.
	 */
	private boolean showOnlySelectable;
	
	/**
	 * A boolean indicating if the user can create files.
	 */
	private boolean canCreateFiles;
	
	/**
	 * A boolean indicating if the chooser is going to be used to select folders.
	 */
	private boolean folderMode;
	
	/**
	 * A file that indicates the folder that is currently being displayed.
	 */
	private File currentFolder;
	
	/**
	 * This attribut allows to override the default value of the labels.
	 */
	private FileChooserLabels labels;
	
	/**
	 * A boolean that indicates if a confirmation dialog must be displaying when selecting a file.
	 */
	private boolean showConfirmationOnSelect;
	
	/**
	 * A boolean that indicates if a confirmation dialog must be displaying when creating a file.
	 */
	private boolean showConfirmationOnCreate;
	
	/**
	 * A boolean indicating if the folder's full path must be show in the title.
	 */
	private boolean showFullPathInTitle;
	
	// ---- Static attributes ----- //
	
	/**
	 * Static attribute for save the folder displayed by default.
	 */
	private static File defaultFolder;
	
	/**
	 * Static constructor.
	 */
	static {
		defaultFolder = null;
	}
	
	// ----- Constructor ----- //
	
	/**
	 * Creates an instance of this class.
	 * 
	 * @param fileChooser The graphical file chooser.
	 */
	public FileChooserCore(FileChooser fileChooser) {
		// Initialize attributes.
		this.chooser = fileChooser;	
		this.listeners = new LinkedList<OnFileSelectedListener>();
		this.filter = null;
		this.showOnlySelectable = false;
		this.setCanCreateFiles(false);
		this.setFolderMode(false);
		this.currentFolder = null;
		this.labels = null;
		this.showConfirmationOnCreate = false;
		this.showConfirmationOnSelect = false;
		this.showFullPathInTitle = false;
		
		// Add listener for the  buttons.
		LinearLayout root = this.chooser.getRootLayout();
        Button addButton = (Button) root.findViewById(R.id.buttonAdd);
		addButton.setOnClickListener(addButtonClickListener);
		Button okButton = (Button) root.findViewById(R.id.buttonOk);
		okButton.setOnClickListener(okButtonClickListener);
	}
	
	// ----- Events methods ----- //

	/**
	 * Implementation of the click listener for when the add button is clicked.
	 */
	private View.OnClickListener addButtonClickListener = new View.OnClickListener() {
		public void onClick(View v) {
			// Get the current context.
			Context context = v.getContext();
			
			// Create an alert dialog.
			AlertDialog.Builder alert = new AlertDialog.Builder(context);
			
			// Define the dialog's labels.
			String title = context.getString(FileChooserCore.this.folderMode? R.string.daidalos_create_folder : R.string.daidalos_create_file);
			if(FileChooserCore.this.labels != null && FileChooserCore.this.labels.createFileDialogTitle != null) title = FileChooserCore.this.labels.createFileDialogTitle;
			String message = context.getString(FileChooserCore.this.folderMode? R.string.daidalos_enter_folder_name : R.string.daidalos_enter_file_name);
			if(FileChooserCore.this.labels != null && FileChooserCore.this.labels.createFileDialogMessage != null) message = FileChooserCore.this.labels.createFileDialogMessage;
			String posButton = (FileChooserCore.this.labels != null && FileChooserCore.this.labels.createFileDialogAcceptButton != null)? FileChooserCore.this.labels.createFileDialogAcceptButton : context.getString(R.string.daidalos_accept);
			String negButton = (FileChooserCore.this.labels != null && FileChooserCore.this.labels.createFileDialogCancelButton != null)? FileChooserCore.this.labels.createFileDialogCancelButton : context.getString(R.string.daidalos_cancel);
			
			// Set the title and the message.
			alert.setTitle( title );
			alert.setMessage( message );

			// Set an EditText view to get the file's name.
			final EditText input = new EditText(context);
			alert.setView(input);

			// Set the 'ok' and 'cancel' buttons.
			alert.setPositiveButton(posButton, new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog, int whichButton) {
					String fileName = input.getText().toString();
					// Verify if a value has been entered.
					if(fileName != null && fileName.length() > 0) {
						// Notify the listeners.
						FileChooserCore.this.notifyListeners(FileChooserCore.this.currentFolder, fileName);
					}
				}
			});
			alert.setNegativeButton(negButton, new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog, int whichButton) {
					// Do nothing, automatically the dialog is going to be closed.
				}
			});

			// Show the dialog.
			alert.show();
		}
	};

	/**
	 * Implementation of the click listener for when the ok button is clicked.
	 */
	private View.OnClickListener okButtonClickListener = new View.OnClickListener() {
		public void onClick(View v) {
			// Notify the listeners.
			FileChooserCore.this.notifyListeners(FileChooserCore.this.currentFolder, null);
		}
	};
	
	/**
	 * Implementation of the click listener for when a file item is clicked.
	 */
	private FileItem.OnFileClickListener fileItemClickListener = new FileItem.OnFileClickListener() {
		public void onClick(FileItem source) {
			// Verify if the item is a folder.
			File file = source.getFile();
			if(file.isDirectory()) {
				// Open the folder.
				FileChooserCore.this.loadFolder(file);
			} else {
				// Notify the listeners.
				FileChooserCore.this.notifyListeners(file, null);
			}
		}
	};
	
	/**
	 * Add a listener for the event of a file selected.
	 * 
	 * @param listener The listener to add.
	 */
	public void addListener(OnFileSelectedListener listener) {
		this.listeners.add(listener);
	}
	
	/**
	 * Removes a listener for the event of a file selected.
	 * 
	 * @param listener The listener to remove.
	 */
	public void removeListener(OnFileSelectedListener listener) {
		this.listeners.remove(listener);
	}
	
	/**
	 * Removes all the listeners for the event of a file selected.
	 */
	public void removeAllListeners() {
		this.listeners.clear();
	}
	
	/**
	 * Interface definition for a callback to be invoked when a file is selected. 
	 */
	public interface OnFileSelectedListener {
		/**
		 * Called when a file has been selected.
		 * 
		 * @param file The file selected.
		 */
		void onFileSelected(File file);
		
		/**
		 * Called when an user wants to be create a file.
		 * 
		 * @param folder The file's parent folder.
		 * @param name The file's name.
		 */
		void onFileSelected(File folder, String name);
	}
	
	/**
	 * Notify to all listeners that a file has been selected or created.
	 * 
	 * @param file The file or folder selected or the folder in which the file must be created.
	 * @param name The name of the file that must be created or 'null' if a file was selected (instead of being created).
	 */
	private void notifyListeners(final File file, final String name) {
		// Determine if a file has been selected or created.
		final boolean creation = name != null && name.length() > 0;
		
		// Verify if a confirmation dialog must be show.
		if((creation && this.showConfirmationOnCreate || !creation && this.showConfirmationOnSelect)) {
			// Create an alert dialog.
			Context context = this.chooser.getContext();
			AlertDialog.Builder alert = new AlertDialog.Builder(context);
						
			// Define the dialog's labels.
			String message = null;
			if(FileChooserCore.this.labels != null && ((creation && FileChooserCore.this.labels.messageConfirmCreation != null) || (!creation && FileChooserCore.this.labels.messageConfirmSelection != null)))  {
				message = creation? FileChooserCore.this.labels.messageConfirmCreation : FileChooserCore.this.labels.messageConfirmSelection;
			} else {
				if(FileChooserCore.this.folderMode) {
					message = context.getString(creation? R.string.daidalos_confirm_create_folder : R.string.daidalos_confirm_select_folder);
				} else {
					message = context.getString(creation? R.string.daidalos_confirm_create_file : R.string.daidalos_confirm_select_file);
				}
			}
			if(message != null) message = message.replace("$file_name", name!=null? name : file.getName());
			String posButton = (FileChooserCore.this.labels != null && FileChooserCore.this.labels.labelConfirmYesButton != null)? FileChooserCore.this.labels.labelConfirmYesButton : context.getString(R.string.daidalos_yes);
			String negButton = (FileChooserCore.this.labels != null && FileChooserCore.this.labels.labelConfirmNoButton != null)? FileChooserCore.this.labels.labelConfirmNoButton : context.getString(R.string.daidalos_no);
						
			// Set the message and the 'yes' and 'no' buttons.
			alert.setMessage( message );
			alert.setPositiveButton(posButton, new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog, int whichButton) {
					// Notify to listeners.
					for(int i=0; i<FileChooserCore.this.listeners.size(); i++) {
						if(creation) {
							FileChooserCore.this.listeners.get(i).onFileSelected(file, name);				
						} else {
							FileChooserCore.this.listeners.get(i).onFileSelected(file);
						}
					}	
				}
			});
			alert.setNegativeButton(negButton, new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog, int whichButton) {
					// Do nothing, automatically the dialog is going to be closed.
				}
			});

			// Show the dialog.
			alert.show();
		} else {
			// Notify to listeners.
			for(int i=0; i<FileChooserCore.this.listeners.size(); i++) {
				if(creation) {
					FileChooserCore.this.listeners.get(i).onFileSelected(file, name);				
				} else {
					FileChooserCore.this.listeners.get(i).onFileSelected(file);
				}
			}			
		}		
	}
	
	// ----- Get and set methods ----- //
	
	/**
	 * Allows to define if a confirmation dialog must be show when selecting a file.
	 * 
	 * @param show 'true' for show the confirmation dialog, 'false' for not show the dialog.
	 */
	public void setShowConfirmationOnSelect(boolean show) {
		this.showConfirmationOnSelect = show;
	}
	
	/**
	 * Allows to define if a confirmation dialog must be show when creating a file.
	 * 
	 * @param show 'true' for show the confirmation dialog, 'false' for not show the dialog.
	 */
	public void setShowConfirmationOnCreate(boolean show) {
		this.showConfirmationOnCreate = show;
	}
	
	/**
	 * Allows to define if, in the title, must be show only the current folder's name or the full file's path..
	 * 
	 * @param show 'true' for show the full path, 'false' for show only the name.
	 */
	public void setShowFullPathInTitle(boolean show) {
		this.showFullPathInTitle = show;
	}
	
	/**
	 * Defines the value of the labels.
	 * 
	 * @param label The labels.
	 */
	public void setLabels(FileChooserLabels labels) {
		this.labels = labels;
		
		// Verify if the buttons for add a file or select a folder has been modified.
		if(labels != null) {
			LinearLayout root = this.chooser.getRootLayout();
			
			if(labels.labelAddButton != null) {
				Button addButton = (Button) root.findViewById(R.id.buttonAdd);
				addButton.setText(labels.labelAddButton);
			}
			
			if(labels.labelSelectButton != null) {
				Button okButton = (Button) root.findViewById(R.id.buttonOk);
				okButton.setText(labels.labelSelectButton);
			}			
		}
	}
	
	/**
	 * Set a regular expression to filter the files that can be selected.
	 * 
	 * @param filter A regular expression.
	 */
	public void setFilter(String filter) {
		if(filter == null || filter.length() == 0 ) {
			this.filter = null;
		} else {
			this.filter = filter;
		}
		
		// Reload the list of files.
		this.loadFolder(this.currentFolder);
	}
	
	/**
	 * Defines if the chooser is going to be used to select folders, instead of files.
	 * 
	 * @param folderMode 'true' for select folders or 'false' for select files.
	 */
	public void setFolderMode(boolean folderMode) {
		this.folderMode = folderMode;
		
		// Show or hide the 'Ok' button.
		updateButtonsLayout();
		
		// Reload the list of files.
		this.loadFolder(this.currentFolder);
	}
	
	/**
	 * Defines if the user can create files, instead of only select files.
	 * 
	 * @param canCreate 'true' if the user can create files or 'false' if it can only select them.
	 */
	public void setCanCreateFiles(boolean canCreate) {
		this.canCreateFiles = canCreate;
		
		// Show or hide the 'Add' button.
		updateButtonsLayout();
	}
	
	/**
	 * Defines if only the files that can be selected (they pass the filter) must be show.
	 * 
	 * @param show 'true' if only the files that can be selected must be show or 'false' if all the files must be show.
	 */
	public void setShowOnlySelectable(boolean show) {
		this.showOnlySelectable = show;
		
		// Reload the list of files.
		this.loadFolder(this.currentFolder);
	}
	
	/**
	 * Returns the current folder.
	 * 
	 * @return The current folder.
	 */
	public File getCurrentFolder() {
		return this.currentFolder;
	}
	
	// ----- Miscellaneous methods ----- //
	
	/**
	 * Changes the height of the layout for the buttons, according if the buttons are visible or not. 
	 */
	private void updateButtonsLayout() {
		// Get the buttons layout.
		LinearLayout root = this.chooser.getRootLayout();
		LinearLayout buttonsLayout = (LinearLayout) root.findViewById(R.id.linearLayoutButtons);

		// Verify if the 'Add' button is visible or not.
		View addButton = root.findViewById(R.id.buttonAdd);
		addButton.setVisibility(this.canCreateFiles? View.VISIBLE : View.INVISIBLE);
		addButton.getLayoutParams().width = this.canCreateFiles? ViewGroup.LayoutParams.MATCH_PARENT : 0;

		// Verify if the 'Ok' button is visible or not.
		View okButton = root.findViewById(R.id.buttonOk);
		okButton.setVisibility(this.folderMode? View.VISIBLE : View.INVISIBLE);
		okButton.getLayoutParams().width = this.folderMode? ViewGroup.LayoutParams.MATCH_PARENT : 0;
		
		// If both buttons are invisible, hide the layout.
		ViewGroup.LayoutParams params = buttonsLayout.getLayoutParams();
		if(this.canCreateFiles || this.folderMode) {
			// Show the layout.
			params.height = ViewGroup.LayoutParams.WRAP_CONTENT;

			// If only the 'Ok' button is visible, put him first. Otherwise, put 'Add' first.
			buttonsLayout.removeAllViews();
			if(this.folderMode && !this.canCreateFiles) {
				buttonsLayout.addView(okButton);
				buttonsLayout.addView(addButton);
			} else {
				buttonsLayout.addView(addButton);			
				buttonsLayout.addView(okButton);
			}
		} else {
			// Hide the layout.
			params.height = 0;
		}
	}
	
	/**
	 * Loads all the files of the SD card root.
	 */
	public void loadFolder() {
		this.loadFolder(defaultFolder);
	}
	
	/**
	 * Loads all the files of a folder in the file chooser.
	 * 
	 * If no path is specified ('folderPath' is null) the root folder of the SD card is going to be used.
	 * 
	 * @param folderPath The folder's path.
	 */
	public void loadFolder(String folderPath) {
		// Get the file path.
		File path = null;
		if(folderPath != null && folderPath.length() > 0) {
			path = new File(folderPath);
		}
		
		this.loadFolder(path);
	}
	
	/**
	 * Loads all the files of a folder in the file chooser.
	 * 
	 * If no path is specified ('folder' is null) the root folder of the SD card is going to be used.
	 * 
	 * @param folder The folder.
	 */
	public void loadFolder(File folder) {
		// Remove previous files.
		LinearLayout root = this.chooser.getRootLayout();
		LinearLayout layout = (LinearLayout) root.findViewById(R.id.linearLayoutFiles);
		layout.removeAllViews();
		
		// Get the file path.
		if(folder == null || !folder.exists()) {
			if(defaultFolder != null) {
				this.currentFolder = defaultFolder;			
			} else {
				this.currentFolder = Environment.getExternalStorageDirectory();
			}
		} else {
			this.currentFolder = folder;
		}
		
		// Verify if the path exists.
		if(this.currentFolder.exists() && layout != null) {
			List<FileItem> fileItems = new LinkedList<FileItem>();
			
			// Add the parent folder.
			if(this.currentFolder.getParent() != null) {
				File parent = new File(this.currentFolder.getParent());
				if(parent.exists()) {
					fileItems.add(new FileItem(this.chooser.getContext(), parent, ".."));					
				}
			}
			
			// Verify if the file is a directory.
			if(this.currentFolder.isDirectory()) {
				// Get the folder's files.
				File[] fileList = this.currentFolder.listFiles();
				if(fileList != null) {
					// Order the files alphabetically and separating folders from files.
					Arrays.sort(fileList, new Comparator<File>() {
						public int compare(File file1, File file2) {
							if(file1 != null && file2 != null) {
								if(file1.isDirectory() && (!file2.isDirectory())) return -1;
								if(file2.isDirectory() && (!file1.isDirectory())) return 1;
								return file1.getName().compareTo(file2.getName());
							}
							return 0;
						}
					});		
					
					// Iterate all the files in the folder.
					for(int i=0; i<fileList.length; i++) {
						// Verify if file can be selected (is a directory or folder mode is not activated and the file pass the filter, if defined).
						boolean selectable = true;
						if(!fileList[i].isDirectory()) {
							selectable = !this.folderMode && (this.filter == null || fileList[i].getName().matches(this.filter));
						}
						
						// Verify if the file must be show.
						if(selectable || !this.showOnlySelectable) {
							// Create the file item and add it to the list.
							FileItem fileItem = new FileItem(this.chooser.getContext(), fileList[i]);
							fileItem.setSelectable(selectable);
							fileItems.add(fileItem);
						}
					}
				}
					
				// Set the name of the current folder.
				String currentFolderName = this.showFullPathInTitle? this.currentFolder.getPath() : this.currentFolder.getName();
				this.chooser.setCurrentFolderName(currentFolderName);
			} else {
				// The file is not a folder, add only this file.
				fileItems.add(new FileItem(this.chooser.getContext(), this.currentFolder));
			}
	
			
			// Add click listener and add the FileItem objects to the layout.
			for(int i=0; i<fileItems.size(); i++) {
				fileItems.get(i).addListener(this.fileItemClickListener);
				layout.addView(fileItems.get(i));
			}
			
			// Refresh default folder.
			defaultFolder = this.currentFolder;
		}			
	}
}