/*
 * VFSDirectoryEntryTable.java - VFS directory entry table
 * :tabSize=4:indentSize=4:noTabs=false:
 * :folding=explicit:collapseFolds=1:
 *
 * Copyright (C) 2003, 2005 Slava Pestov
 *
 * This program 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 2
 * of the License, or any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package org.gjt.sp.jedit.browser;

//{{{ Imports
import javax.swing.event.*;
import javax.swing.table.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.font.*;
import java.awt.*;
import java.util.LinkedList;
import java.util.Set;

import org.gjt.sp.jedit.browser.VFSDirectoryEntryTableModel.Entry;
import org.gjt.sp.jedit.io.VFS;
import org.gjt.sp.jedit.io.VFSFile;
import org.gjt.sp.jedit.io.VFSManager;
import org.gjt.sp.jedit.msg.VFSPathSelected;
import org.gjt.sp.jedit.ActionContext;
import org.gjt.sp.jedit.EditAction;
import org.gjt.sp.jedit.EditBus;
import org.gjt.sp.jedit.MiscUtilities;
import org.gjt.sp.jedit.GUIUtilities;
import org.gjt.sp.jedit.jEdit;
import org.gjt.sp.util.Log;
import org.gjt.sp.util.ThreadUtilities;
//}}}

/**
 * @author Slava Pestov
 * @version $Id: VFSDirectoryEntryTable.java 24021 2015-08-14 18:36:19Z makarius $
 * @since jEdit 4.2pre1
 */
public class VFSDirectoryEntryTable extends JTable
{
	//{{{ VFSDirectoryEntryTable constructor
	VFSDirectoryEntryTable(BrowserView browserView)
	{
		super(new VFSDirectoryEntryTableModel());
		this.browserView = browserView;
		setShowGrid(false);

		setIntercellSpacing(new Dimension(0,0));

		setDefaultRenderer(Entry.class,
			renderer = new FileCellRenderer());

		header = getTableHeader();
		header.setReorderingAllowed(true);
		addMouseListener(new MainMouseHandler());
		header.addMouseListener(new MouseHandler());
		header.setDefaultRenderer(new HeaderRenderer(
			(DefaultTableCellRenderer)header.getDefaultRenderer()));

		setRowSelectionAllowed(true);

		getColumnModel().addColumnModelListener(new ColumnHandler());

		setAutoResizeMode(AUTO_RESIZE_OFF);
	} //}}}

	//{{{ selectFile() method
	public boolean selectFile(String path)
	{
		for(int i = 0; i < getRowCount(); i++)
		{
			Entry entry = (Entry) getValueAt(i,1);
			if(entry.dirEntry.getPath().equals(path))
			{
				setSelectedRow(i);
				return true;
			}
		}

		return false;
	} //}}}

	//{{{ doTypeSelect() method
	public void doTypeSelect(String str, boolean dirsOnly)
	{
		if(str.length() == 0)
			clearSelection();
		else if(getSelectedRow() == -1)
			doTypeSelect(str,0,getRowCount(),dirsOnly);
		else
		{
			int start = getSelectionModel().getMaxSelectionIndex();
			boolean retVal = doTypeSelect(str,start,getRowCount(),
				dirsOnly);

			if(!retVal)
			{
				// scan from selection to end failed, so
				// scan from start to selection
				doTypeSelect(str,0,start,dirsOnly);
			}
		}
	} //}}}

	//{{{ getSelectedFiles() method
	public VFSFile[] getSelectedFiles()
	{
		VFSDirectoryEntryTableModel model
			= (VFSDirectoryEntryTableModel)getModel();

		java.util.List<VFSFile> returnValue = new LinkedList<VFSFile>();
		int[] selectedRows = getSelectedRows();
		for (int selectedRow : selectedRows)
			returnValue.add(model.files[selectedRow].dirEntry);
		return returnValue.toArray(new VFSFile[returnValue.size()]);
	} //}}}

	//{{{ getExpandedDirectories() method
	public void getExpandedDirectories(Set<String> set)
	{
		VFSDirectoryEntryTableModel model
			= (VFSDirectoryEntryTableModel)getModel();

		if(model.files != null)
		{
			for(int i = 0; i < model.files.length; i++)
			{
				if(model.files[i].expanded)
					set.add(model.files[i].dirEntry.getPath());
			}
		}
	} //}}}

	//{{{ toggleExpanded() method
	public void toggleExpanded(final int row)
	{
		VFSDirectoryEntryTableModel model
		= (VFSDirectoryEntryTableModel)getModel();

		Entry entry = model.files[row];
		if(entry.dirEntry.getType() == VFSFile.FILE)
			return;

		Runnable delayedAwtTask = new Runnable()
		{
			public void run()
			{
				setSelectedRow(row);
			}
		};
		if(entry.expanded)
		{
			model.collapse(VFSManager.getVFSForPath(
				entry.dirEntry.getPath()),row);
			resizeColumns();
			ThreadUtilities.runInDispatchThread(delayedAwtTask);
		}
		else
		{
			browserView.clearExpansionState();
			browserView.loadDirectory(entry,entry.dirEntry.getPath(),
				false, delayedAwtTask);
		}



	} //}}}

	//{{{ setDirectory() method
	public void setDirectory(VFS vfs, Object node, java.util.List<VFSFile> list,
		Set<String> tmpExpanded)
	{
		timer.stop();
		typeSelectBuffer.setLength(0);

		VFSDirectoryEntryTableModel model = (VFSDirectoryEntryTableModel)getModel();
		int startIndex;
		if(node == null)
		{
			startIndex = 0;
			model.setRoot(vfs,list);
		}
		else
		{
			startIndex =
				model.expand(
				vfs,
				(Entry)node,
				list);
			startIndex++;
		}

		for(int i = 0; i < list.size(); i++)
		{
			Entry e = model.files[startIndex + i];
			String path = e.dirEntry.getPath();
			if(tmpExpanded.contains(path))
			{
				browserView.loadDirectory(e,path,false);
				tmpExpanded.remove(path);
			}
		}

		resizeColumns();
	} //}}}

	//{{{ maybeReloadDirectory() method
	public void maybeReloadDirectory(String path)
	{
		VFSDirectoryEntryTableModel model
		= (VFSDirectoryEntryTableModel)getModel();

		for(int i = 0; i < model.files.length; i++)
		{
			Entry e = model.files[i];
			if(!e.expanded || e.dirEntry.getType() == VFSFile.FILE)
				continue;

			VFSFile dirEntry = e.dirEntry;
			// work around for broken FTP plugin!
			String otherPath;
			if(dirEntry.getSymlinkPath() == null)
				otherPath = dirEntry.getPath();
			else
				otherPath = dirEntry.getSymlinkPath();
			if(MiscUtilities.pathsEqual(path,otherPath))
			{
				browserView.saveExpansionState();
				browserView.loadDirectory(e,path,false);
				return;
			}
		}
	} //}}}

	//{{{ propertiesChanged() method
	public void propertiesChanged()
	{
		renderer.propertiesChanged();

		VFSFile template = new VFSFile(
			"foo","foo","foo",VFSFile.FILE,0L,false);
		setRowHeight(renderer.getTableCellRendererComponent(
			this,new Entry(template,0),
			false,false,0,0).getPreferredSize().height);
		Dimension prefSize = getPreferredSize();
		setPreferredScrollableViewportSize(new Dimension(prefSize.width,
			getRowHeight() * 12));
	} //}}}

	//{{{ scrollRectToVisible() method
	@Override
	public void scrollRectToVisible(Rectangle rect)
	{
		// avoid scrolling to the right
		rect.width = 0;
		super.scrollRectToVisible(rect);
	} //}}}

	//{{{ processKeyEvent() method
	@Override
	public void processKeyEvent(KeyEvent evt)
	{
		if(evt.getID() == KeyEvent.KEY_PRESSED)
		{
			VFSDirectoryEntryTableModel model =
				(VFSDirectoryEntryTableModel)getModel();
			int row = getSelectedRow();
			ActionContext ac = VFSBrowser.getActionContext();
			ActionContext jac = jEdit.getActionContext();
			EditAction browserUp = ac.getAction("vfs.browser.up");			
			VFSBrowser browser = browserView.getBrowser();
			switch(evt.getKeyCode())
			{
			case KeyEvent.VK_LEFT:
				evt.consume();
				if ((evt.getModifiers() & InputEvent.ALT_MASK)>0)
				{
					browser.previousDirectory();
				}
				else 
				{
					if(row != -1)
					{
						if(model.files[row].expanded)
						{
							toggleExpanded(row);
							return;
						}

						for(int i = row - 1; i >= 0; i--)
						{
							if(model.files[i].expanded &&
							   model.files[i].level < model.files[row].level)
							{
								setSelectedRow(i);
								return;
							}
						}
					}

					String dir = browserView.getBrowser()
						.getDirectory();
					dir = MiscUtilities.getParentOfPath(dir);
					browserView.getBrowser().setDirectory(dir);
				}
				break;
			case KeyEvent.VK_TAB:
				evt.consume();
				if ((evt.getModifiers() & InputEvent.SHIFT_MASK) > 0)
				{
					browserView.getParentDirectoryList().requestFocus();
				}
				else
				{
					browser.focusOnDefaultComponent();	
				}
				break;
			case KeyEvent.VK_BACK_SPACE:
				evt.consume();
				ac.invokeAction(evt, browserUp);
				break;
			case KeyEvent.VK_UP:
				if ((evt.getModifiers() & InputEvent.ALT_MASK) >0)
				{
					evt.consume();
					ac.invokeAction(evt, browserUp);
				}
				break;
			case KeyEvent.VK_DELETE:
				evt.consume();
				EditAction deleteAct = ac.getAction("vfs.browser.delete");
				ac.invokeAction(evt, deleteAct);
				break;
			case KeyEvent.VK_N:
				if ((evt.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) == InputEvent.CTRL_DOWN_MASK)
				{
					evt.consume();
					EditAction ea = ac.getAction("vfs.browser.new-file");
					ac.invokeAction(evt, ea);
				}
				break;
			case KeyEvent.VK_INSERT:
				evt.consume();
				EditAction newDir = ac.getAction("vfs.browser.new-directory");
				ac.invokeAction(evt, newDir);
				break;
			case KeyEvent.VK_ESCAPE:
				EditAction cda = ac.getAction("vfs.browser.closedialog");
				ac.invokeAction(evt, cda);
				break;
			case KeyEvent.VK_F2:
				EditAction ren = ac.getAction("vfs.browser.rename");
				evt.consume();
				ac.invokeAction(evt, ren);
				break;
			case KeyEvent.VK_F5:
				evt.consume();
				EditAction reload= ac.getAction("vfs.browser.reload");
				ac.invokeAction(evt, reload);
				break;
			case KeyEvent.VK_F6:
			case KeyEvent.VK_RIGHT:
				evt.consume();
				if ((evt.getModifiers() & InputEvent.ALT_MASK)>0)
				{
					browser.nextDirectory();
				}
				else if(row != -1)
				{
					if(!model.files[row].expanded)
						toggleExpanded(row);
				}
				break;
			case KeyEvent.VK_ENTER:
				evt.consume();
				browserView.getBrowser().filesActivated(
					evt.isShiftDown()
					? VFSBrowser.M_OPEN_NEW_VIEW
					: VFSBrowser.M_OPEN,false);

				break;
			}
		}
		else if(evt.getID() == KeyEvent.KEY_TYPED)
		{

			if(evt.isControlDown() || evt.isAltDown()
				|| evt.isMetaDown())
			{
				evt.consume();
				return;
			}

			// hack...
			if(evt.isShiftDown() && evt.getKeyChar() == '\n')
			{
				evt.consume();
				return;
			}


			VFSBrowser browser = browserView.getBrowser();

			switch(evt.getKeyChar())
			{
			case '~':
				evt.consume();
				if(browser.getMode() == VFSBrowser.BROWSER)
					browser.setDirectory(System.getProperty(
						"user.home"));
				break;
			case '/':
				evt.consume();
				if(browser.getMode() == VFSBrowser.BROWSER)
					browser.rootDirectory();
				break;
			case '-':
				evt.consume();
				if(browser.getMode() == VFSBrowser.BROWSER)
				{
					browser.setDirectory(
						browser.getView().getBuffer()
						.getDirectory());
				}
				break;
			default:
				evt.consume();
				typeSelectBuffer.append(evt.getKeyChar());
				doTypeSelect(typeSelectBuffer.toString(),
					browser.getMode() == VFSBrowser
					.CHOOSE_DIRECTORY_DIALOG);

				timer.stop();
				timer.setInitialDelay(750);
				timer.setRepeats(false);
				timer.start();
				return;
			}
		}

		if(!evt.isConsumed())
			super.processKeyEvent(evt);
	} //}}}

	//{{{ setSelectedRow() method
	public void setSelectedRow(int row)
	{
		getSelectionModel().setSelectionInterval(row,row);
		scrollRectToVisible(getCellRect(row,0,true));
	} //}}}

	//{{{ Private members
	private final BrowserView browserView;
	private final JTableHeader header;
	private final FileCellRenderer renderer;
	private final StringBuffer typeSelectBuffer = new StringBuffer();
	private final Timer timer = new Timer(0,new ClearTypeSelect());
	private boolean resizingColumns;

	//{{{ doTypeSelect() method
	private boolean doTypeSelect(String str, int start, int end,
		boolean dirsOnly)
	{
		VFSFile[] files = ((VFSDirectoryEntryTableModel)
			getModel()).getFiles();

		int index = VFSFile.findCompletion(files,start,end,str,dirsOnly);
		if(index != -1)
		{
			setSelectedRow(index);
			return true;
		}
		else
			return false;
	} //}}}

	//{{{ resizeColumns() method
	private void resizeColumns()
	{
		VFSDirectoryEntryTableModel model = (VFSDirectoryEntryTableModel)getModel();

		FontRenderContext fontRenderContext = new FontRenderContext(null,true,false);
		int[] widths = new int[model.getColumnCount()];
		for(int i = 0; i < widths.length; i++)
		{
			String columnName = model.getColumnName(i);
			if(columnName != null)
			{
				widths[i] = (int)renderer.plainFont
					.getStringBounds(columnName,
					fontRenderContext).getWidth();
			}
		}

		for(int i = 1; i < widths.length; i++)
		{
			//String extAttr = model.getExtendedAttribute(i);
			widths[i] = Math.max(widths[i],model.getColumnWidth(i));
		}

		for(int i = 0; i < model.files.length; i++)
		{
			Entry entry = model.files[i];
			Font font = entry.dirEntry.getType()
				== VFSFile.FILE
				? renderer.plainFont : renderer.boldFont;

			widths[0] = Math.max(widths[0],renderer.getEntryWidth(
				entry,font,fontRenderContext));
		}

		widths[0] += 10;

		TableColumnModel columns = getColumnModel();

		try
		{
			resizingColumns = true;
			for(int i = 0; i < widths.length; i++)
			{
				columns.getColumn(i).setPreferredWidth(widths[i]);
				columns.getColumn(i).setWidth(widths[i]);
			}
		}
		finally
		{
			resizingColumns = false;
		}

		doLayout();
	} //}}}

	//{{{ saveWidths() method
	private void saveWidths()
	{
		if(resizingColumns)
			return;

		VFSDirectoryEntryTableModel model = (VFSDirectoryEntryTableModel)getModel();
		TableColumnModel columns = getColumnModel();

		for(int i = 1; i < model.getColumnCount(); i++)
			model.setColumnWidth(i,columns.getColumn(i).getWidth());
	} //}}}

	//}}}

	//{{{ ClearTypeSelect class
	class ClearTypeSelect implements ActionListener
	{
		public void actionPerformed(ActionEvent evt)
		{
			typeSelectBuffer.setLength(0);
		}
	} //}}}

	//{{{ ColumnHandler class
	class ColumnHandler implements TableColumnModelListener
	{
		public void columnAdded(TableColumnModelEvent e) {}
		public void columnRemoved(TableColumnModelEvent e) {}
		public void columnMoved(TableColumnModelEvent e) {
			((VFSDirectoryEntryTableModel)getModel()).columnMoved(e.getFromIndex(), e.getToIndex());
		}
		public void columnSelectionChanged(ListSelectionEvent e) {}

		public void columnMarginChanged(ChangeEvent e)
		{
			saveWidths();
		}
	} //}}}

	//{{{ class MainMouseHandler
	class MainMouseHandler extends MouseInputAdapter
	{

		@Override
		public void mouseClicked(MouseEvent e)
		{
			super.mouseClicked(e);
			if (browserView.getBrowser().getMode() != VFSBrowser.BROWSER) return;
			int ind = getSelectionModel().getMinSelectionIndex();
			if (ind == -1)
				return;
			Entry node = (Entry) getModel().getValueAt(ind, 0);
			boolean isDir = node.dirEntry.getType() == VFSFile.DIRECTORY;
			EditBus.send(new VFSPathSelected(browserView.getBrowser().getView(),
							 node.dirEntry.getPath(), isDir));
		}

	} //}}}

	//{{{ MouseHandler class
	class MouseHandler extends MouseInputAdapter
	{
		@Override
		public void mousePressed(MouseEvent evt)
		{
			// double click on columns header
			if (evt.getSource() == header && evt.getClickCount() == 2)
			{
				VFSDirectoryEntryTableModel model = (VFSDirectoryEntryTableModel) header.getTable().getModel();
				TableColumnModel columnModel = header.getColumnModel();
				int viewColumn = columnModel.getColumnIndexAtX(evt.getX());
				int column = columnModel.getColumn(viewColumn).getModelIndex();
				saveWidths();
				if(model.sortByColumn(column))
				{
					resizeColumns();
					Log.log(Log.DEBUG,this,"VFSDirectoryEntryTable sorted by "
					+ model.getColumnName(column)
					+ (model.getAscending() ? " ascending" : " descending") );
				}
			}
		}
	} //}}}

	//{{{ HeaderRenderer
	static class HeaderRenderer extends DefaultTableCellRenderer
	{
		private final DefaultTableCellRenderer tcr;

		HeaderRenderer(DefaultTableCellRenderer tcr)
		{
			this.tcr = tcr;
		}

		@Override
		public Component getTableCellRendererComponent(JTable table, Object value,
			boolean isSelected, boolean hasFocus, int row, int column)
		{
			JLabel l = (JLabel)tcr.getTableCellRendererComponent(table,value,isSelected,hasFocus,row,column);
			VFSDirectoryEntryTableModel model = (VFSDirectoryEntryTableModel)table.getModel();
			Icon icon = column == model.getSortColumn()
				? model.getAscending() ? ASC_ICON : DESC_ICON
				: null;
			l.setIcon(icon);
			// l.setHorizontalTextPosition(l.LEADING);
			return l;
		}
	} //}}}

	//{{{ SortOrder Icons

	static final Icon ASC_ICON  = GUIUtilities.loadIcon("arrow-asc.png");
	static final Icon DESC_ICON = GUIUtilities.loadIcon("arrow-desc.png");

	//}}}

}
