/*
 * BufferSetManager.java - Manages the buffersets.
 * :tabSize=4:indentSize=4:noTabs=false:
 * :folding=explicit:collapseFolds=1:
 *
 * Copyright (C) 2008, 2010 Matthieu Casanova
 *
 * 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.bufferset;

//{{{ Imports
import org.gjt.sp.jedit.*;
import org.gjt.sp.jedit.io.VFS;
import org.gjt.sp.jedit.io.VFSManager;
import org.gjt.sp.jedit.msg.EditPaneUpdate;
import org.gjt.sp.jedit.msg.PropertiesChanged;
import org.gjt.sp.jedit.visitors.JEditVisitorAdapter;
import org.gjt.sp.util.Log;
import org.gjt.sp.jedit.EditBus.EBHandler;
import java.util.*;
//}}}

/**
 * The buffersets manager. A singleton instance of this
 * can be obtained from jEdit.getBufferSetManager()
 *
 * @author Matthieu Casanova
 * @since jEdit 4.3pre15
 * @version $Id: jEdit.java 25120 2020-04-03 14:58:39Z kpouer $
 */
public class BufferSetManager
{
	//{{{ BufferSetManager constructor
	public BufferSetManager()
	{
		EditBus.addToBus(this);
		try
		{
			scope = BufferSet.Scope.valueOf(jEdit.getProperty("bufferset.scope", "global"));
		}
		catch (IllegalArgumentException e)
		{
			Log.log(Log.ERROR, this, e);
			scope = BufferSet.Scope.global;
		}
	} //}}}

	//{{{ handleEditPaneUpdate() method
	@EBHandler
	public void handleEditPaneUpdate(EditPaneUpdate message)
	{
		if (message.getWhat() == EditPaneUpdate.DESTROYED)
		{
			EditPane editPane = message.getEditPane();
			BufferSet bufferSet = editPane.getBufferSet();
			Buffer[] allBuffers = bufferSet.getAllBuffers();
			for (Buffer buffer : allBuffers)
			{
				_removeBuffer(bufferSet, buffer);
			}
		}
	} //}}}

	//{{{ handlePropertiesChanged() method
	@EBHandler
	public void handlePropertiesChanged(PropertiesChanged msg)
	{
		// pass on PropertiesChanged message to BufferSets so
		// they can resort themselves as needed.
		// todo : if it is global or per view, it is probably too many calls
		jEdit.getEditPaneManager().forEach(editPane -> editPane.getBufferSet().propertiesChanged());
	} //}}}

	//{{{ countBufferSets() method
	/**
	 * Count the bufferSets in which the buffer is.
	 * @param buffer the buffer
	 * @return the number of buffersets in which buffer is
	 * @see org.gjt.sp.jedit.jEdit#closeBuffer(org.gjt.sp.jedit.EditPane, org.gjt.sp.jedit.Buffer)
	 */
	public int countBufferSets(Buffer buffer)
	{
		return getOwners(buffer).size();
	} //}}}

	//{{{ setScope() method
	public void setScope(BufferSet.Scope scope)
	{
		if (scope == this.scope)
			return;
		jEdit.setProperty("bufferset.scope", scope.name());
		if (scope.compareTo(this.scope) > 0)
		{
			// The new scope is wider
			if (scope == BufferSet.Scope.global)
			{
				final List<Buffer> buffers = jEdit.getBufferManager().getBuffers();
				jEdit.visit(new JEditVisitorAdapter()
				{
					@Override
					public void visit(EditPane editPane)
					{
						BufferSet bufferSet = editPane.getBufferSet();
						buffers.forEach(bufferSet::addBuffer);
					}
				});
			}
			else
			{
				final Map<View,Set<Buffer>> buffersMap = new HashMap<>();
				jEdit.visit(new JEditVisitorAdapter()
				{
					@Override
					public void visit(EditPane editPane)
					{
						BufferSet bufferSet = editPane.getBufferSet();
						Buffer[] buffers = bufferSet.getAllBuffers();
						Set<Buffer> set = buffersMap.computeIfAbsent(editPane.getView(), k -> new HashSet<>());
						set.addAll(Arrays.asList(buffers));
					}
				});
				jEdit.visit(new JEditVisitorAdapter()
				{
					@Override
					public void visit(EditPane editPane)
					{
						BufferSet bufferSet = editPane.getBufferSet();
						Set<Buffer> set = buffersMap.get(editPane.getView());
						for (Buffer buffer : set)
						{
							bufferSet.addBuffer(buffer);
						}
					}
				});
			}
		}
		this.scope = scope;
		EditBus.send(new PropertiesChanged(this));
	} //}}}

	//{{{ getScope() method
	public BufferSet.Scope getScope()
	{
		return scope;
	} //}}}

	//{{{ addBuffer() methods
	/**
	 * Add a buffer into the current editPane of the given view.
	 * If the view is null, it will be added to the current
	 * editPane of the active view.
	 * @param view a view (or null)
	 * @param buffer the buffer to add
	 */
	public void addBuffer(View view, Buffer buffer)
	{
		EditPane editPane = view == null ? null : view.getEditPane();
		addBuffer(editPane, buffer);
	}

	/**
	 * Add a buffer into the given editPane.
	 * If the editPane is null, it will be added to the current
	 * editPane of the active view.
	 * @param editPane an EditPane (or null)
	 * @param buffer the buffer to add
	 */
	public void addBuffer(EditPane editPane, final Buffer buffer)
	{
		if (editPane == null)
		{
			editPane = jEdit.getActiveView().getEditPane();
		}
		BufferSet bufferSet = editPane.getBufferSet();
		switch (scope)
		{
			case editpane:
				bufferSet.addBuffer(buffer);
				break;
			case view:
				editPane.getView().forEachEditPane(pane -> pane.getBufferSet().addBuffer(buffer));
				break;
			case global:
				jEdit.getEditPaneManager().forEach(pane -> pane.getBufferSet().addBuffer(buffer));
				break;
		}
	} //}}}

	//{{{ moveBuffer() method
	/**
	 * Moves a buffer from a old position to a new position in the
	 * BufferSet used in an EditPane.
	 */
	public void moveBuffer(EditPane editPane,
		int oldPosition, int newPosition)
	{
		editPane.getBufferSet().moveBuffer(oldPosition, newPosition);
	} //}}}

	//{{{ removeBuffer() methods
	/**
	 * Remove a buffer from the EditPane's bufferSet.
	 *
	 * @param editPane the editPane It cannot be null
	 * @param buffer the buffer
	 */
	public void removeBuffer(EditPane editPane, Buffer buffer)
	{
		switch (scope)
		{
			case editpane:
				BufferSet bufferSet = editPane.getBufferSet();
				removeBuffer(bufferSet, buffer);
				break;
			case view:
				editPane.getView().forEachEditPane(pane -> removeBuffer(pane.getBufferSet(), buffer));
				break;
			case global:
				jEdit._closeBuffer(null, buffer);
				break;
		}
	}

	/**
	 * Remove a buffer from a bufferSet.
	 * And make sure that the bufferSet is not empty after that
	 *
	 * @param bufferSet the bufferSet
	 * @param buffer the buffer that will be removed
	 */
	void removeBuffer(BufferSet bufferSet, Buffer buffer)
	{
		Log.log(Log.DEBUG, this, "removeBuffer("+bufferSet+','+buffer+')');
		_removeBuffer(bufferSet, buffer);
		bufferRemoved(bufferSet);
	}

	/**
	 * remove a buffer from all bufferSets.
	 *
	 * @param buffer the buffer that must be removed
	 */
	public void removeBuffer(Buffer buffer)
	{
		for (BufferSet bufferSet : getOwners(buffer))
		{
			bufferSet.removeBuffer(buffer);
			bufferRemoved(bufferSet);
		}
	} //}}}

	//{{{ bufferRemoved() method
	/**
	 * This method is called when a buffer has been removed from a bufferSet.
	 * If it is empty, an untitled buffer is created and added to the bufferSet
	 * @param bufferSet the bufferSet from which the buffer was removed
	 */
	private void bufferRemoved(BufferSet bufferSet)
	{
		if (bufferSet.size() == 0)
		{
			Buffer newEmptyBuffer = createUntitledBuffer();
			EditPane editPaneOwner = getOwner(bufferSet);
			addBuffer(editPaneOwner, newEmptyBuffer);
		}
	} //}}}

	//{{{ _removeBuffer() method
	/**
	 * Remove a buffer from a bufferSet.
	 * Used when closing an EditPane
	 *
	 * @param bufferSet the bufferSet
	 * @param buffer the buffer that will be removed
	 */
	private void _removeBuffer(BufferSet bufferSet, Buffer buffer)
	{
		Set<BufferSet> owners = getOwners(buffer);
		owners.remove(bufferSet);
		bufferSet.removeBuffer(buffer);

		if (owners.isEmpty())
		{
			Log.log(Log.DEBUG, this, "Buffer:"+buffer+" is in no bufferSet anymore, closing it");
			jEdit._closeBuffer(null, buffer);
		}
	} //}}}

	//{{{ createUntitledBuffer() method
	/**
	 * Create an untitled buffer
	 * @return the new untitled buffer
	 */
	public static Buffer createUntitledBuffer()
	{
		int untitledCount = jEdit.getNextUntitledBufferId();

		View view = jEdit.getActiveView();
		String parent = null;

		if (view != null)
		{
			Buffer buffer = view.getBuffer();
			parent = buffer.getDirectory();
		}
		if (parent == null)
		{
			parent = MiscUtilities.getBackupDirectory();
		}
		VFS vfs = null;
		if (parent != null) {
			vfs = VFSManager.getVFSForPath(parent);
		}
		if (vfs != null && (vfs.getCapabilities() & VFS.WRITE_CAP) == 0)
		{
			// cannot write on that VFS, creating untitled buffer in home directory
			parent = System.getProperty("user.home");
		}
		Buffer newEmptyBuffer = jEdit.openTemporary(view, parent,
							    "Untitled-" + untitledCount,true, true);
		jEdit.commitTemporary(newEmptyBuffer);
		return newEmptyBuffer;
	} //}}}

	//{{{ Private members

	//{{{ getOwners() method
	/**
	 * @return set of BufferSets that contain buffer
	 * @since 4.4pre1
	 */
	public Set<BufferSet> getOwners(Buffer buffer)
	{
		final Set<BufferSet> candidates = new HashSet<>();
		// Collect all BufferSets.
		jEdit.getEditPaneManager().forEach(editPane -> candidates.add(editPane.getBufferSet()));
		// Remove all that doesn't contain the buffer.
		candidates.removeIf(bufferSet -> !bufferSet.contains(buffer));
		// Remaining are the result.
		return candidates;
	} //}}}

	//{{{ getOwner() method
	/**
	 * Return the editpane that owns the BufferSet
	 * @param bufferSet the bufferSet
	 * @return the owner of the given bufferSet
	 */
	private static EditPane getOwner(BufferSet bufferSet)
	{
		List<View> views = jEdit.getViewManager().getViews();
		for (View view : views)
		{
			EditPane[] editPanes = view.getEditPanes();
			for (EditPane editPane : editPanes)
			{
				if (editPane.getBufferSet() == bufferSet)
				{
					return editPane;
				}
			}
		}
		return null;
	} //}}}

	/** The scope of the bufferSets. */
	private BufferSet.Scope scope;
	//}}}
}
