/*
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 *
 * 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
 * (at your option) 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 ( see the LICENSE file ).
 *
 * 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 com.aelitis.azureus.ui.swt.mdi;

import java.util.*;
import java.util.regex.Pattern;

import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.global.GlobalManager;
import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.ui.swt.mainwindow.PluginsMenuHelper;
import org.gudy.azureus2.ui.swt.mainwindow.PluginsMenuHelper.IViewInfo;
import org.gudy.azureus2.ui.swt.mainwindow.PluginsMenuHelper.PluginAddedViewListener;
import org.gudy.azureus2.ui.swt.plugins.UISWTViewEventListener;
import org.gudy.azureus2.ui.swt.pluginsimpl.UISWTViewCore;

import com.aelitis.azureus.core.AzureusCore;
import com.aelitis.azureus.core.AzureusCoreFactory;
import com.aelitis.azureus.core.AzureusCoreRunningListener;
import com.aelitis.azureus.ui.common.updater.UIUpdatable;
import com.aelitis.azureus.ui.common.viewtitleinfo.ViewTitleInfo;
import com.aelitis.azureus.ui.mdi.*;
import com.aelitis.azureus.ui.skin.SkinConstants;
import com.aelitis.azureus.ui.swt.skin.SWTSkinObject;
import com.aelitis.azureus.ui.swt.views.skin.SkinView;
import com.aelitis.azureus.util.ConstantsVuze;
import com.aelitis.azureus.util.ContentNetworkUtils;
import com.aelitis.azureus.util.MapUtils;

public abstract class BaseMDI
	extends SkinView
	implements MultipleDocumentInterfaceSWT, UIUpdatable
{
	protected MdiEntrySWT currentEntry;

	private Map<String, MdiEntryCreationListener> mapIdToCreationListener = new LightHashMap<String, MdiEntryCreationListener>();
	private Map<String, MdiEntryCreationListener2> mapIdToCreationListener2 = new LightHashMap<String, MdiEntryCreationListener2>();

	// Sync changes to entry maps on mapIdEntry
	protected Map<String, MdiEntrySWT> mapIdToEntry = new LightHashMap<String, MdiEntrySWT>();

	private List<MdiListener> listeners = new ArrayList<MdiListener>();

	private List<MdiEntryLoadedListener> listLoadListeners = new ArrayList<MdiEntryLoadedListener>();

	private static LinkedHashMap<String, Object> mapAutoOpen = new LinkedHashMap<String, Object>();

	private String[] preferredOrder;

	public void addListener(MdiListener l) {
		synchronized (listeners) {
			if (listeners.contains(l)) {
				return;
			}
			listeners.add(l);
		}
	}

	public void removeListener(MdiListener l) {
		synchronized (listeners) {
			listeners.remove(l);
		}
	}

	public void addListener(MdiEntryLoadedListener l) {
		synchronized (listLoadListeners) {
			if (listLoadListeners.contains(l)) {
				return;
			}
			listLoadListeners.add(l);
		}
		// might be a very rare thread issue here if entry gets loaded while
		// we are walking through entries
		MdiEntry[] entries = getEntries();
		for (MdiEntry entry : entries) {
			if (entry.isAdded()) {
				l.mdiEntryLoaded(entry);
			}
		}
	}

	public void removeListener(MdiEntryLoadedListener l) {
		synchronized (listLoadListeners) {
			listLoadListeners.remove(l);
		}
	}
	
	protected void triggerSelectionListener(MdiEntry newEntry, MdiEntry oldEntry) {
		MdiListener[] array;
		synchronized (listeners) {
			array = listeners.toArray(new MdiListener[0]);
		}
		for (MdiListener l : array) {
			try {
				l.mdiEntrySelected(newEntry, oldEntry);
			} catch (Exception e) {
				Debug.out(e);
			}
		}
	}

	public void triggerEntryLoadedListeners(MdiEntry entry) {
		MdiEntryLoadedListener[] array;
		synchronized (listLoadListeners) {
			array = listLoadListeners.toArray(new MdiEntryLoadedListener[0]);
		}
		for (MdiEntryLoadedListener l : array) {
			try {
				l.mdiEntryLoaded(entry);
			} catch (Exception e) {
				Debug.out(e);
			}
		}
	}

	public void closeEntry(final String id) {
		MdiEntry entry = getEntry(id);
		if (entry != null) {
			entry.close(false);
		} else {
			removeEntryAutoOpen(id);
		}
	}

	public abstract MdiEntry createEntryFromEventListener(String parentID,
			UISWTViewEventListener l, String id, boolean closeable, Object datasource, String preferedAfterID);

	public abstract MdiEntry createEntryFromView(String parentID, UISWTViewCore iview,
			String id, Object datasource, boolean closeable, boolean show,
			boolean expand);

	public abstract MdiEntry createEntryFromSkinRef(String parentID, String id,
			String configID, String title, ViewTitleInfo titleInfo, Object params,
			boolean closeable, String preferedAfterID);

	public MdiEntry getCurrentEntry() {
		return currentEntry;
	}

	public MdiEntrySWT getCurrentEntrySWT() {
		return currentEntry;
	}

	public MdiEntry[] getEntries() {
		return mapIdToEntry.values().toArray(new MdiEntry[0]);
	}

	public MdiEntrySWT[] getEntriesSWT() {
		return mapIdToEntry.values().toArray(new MdiEntrySWT[0]);
	}

	public MdiEntry getEntry(String id) {
		if (SkinConstants.VIEWID_BROWSER_BROWSE.equalsIgnoreCase(id)) {
			id = ContentNetworkUtils.getTarget(ConstantsVuze.getDefaultContentNetwork());
		}
		MdiEntry entry = mapIdToEntry.get(id);
		return entry;
	}

	public MdiEntrySWT getEntrySWT(String id) {
		if (SkinConstants.VIEWID_BROWSER_BROWSE.equalsIgnoreCase(id)) {
			id = ContentNetworkUtils.getTarget(ConstantsVuze.getDefaultContentNetwork());
		}
		MdiEntrySWT entry = mapIdToEntry.get(id);
		return entry;
	}

	/**
	 * @param skinView
	 * @return 
	 *
	 * @since 3.1.1.1
	 */
	public MdiEntry getEntryBySkinView(Object skinView) {
		SWTSkinObject so = ((SkinView)skinView).getMainSkinObject();
		Object[] sideBarEntries = mapIdToEntry.values().toArray();
		for (int i = 0; i < sideBarEntries.length; i++) {
			//MdiEntrySWT entry = (MdiEntrySWT) sideBarEntries[i];
			BaseMdiEntry entry = (BaseMdiEntry) sideBarEntries[i];
			SWTSkinObject entrySO = entry.getSkinObject();
			SWTSkinObject entrySOParent = entrySO == null ? entrySO
					: entrySO.getParent();
			if (entrySO == so || entrySO == so.getParent() || entrySOParent == so) {
				return entry;
			}
		}
		return null;
	}

	public UISWTViewCore getCoreViewFromID(String id) {
		if (id == null) {
			return null;
		}
		MdiEntrySWT entry = getEntrySWT(id);
		if (entry == null) {
			return null;
		}
		return entry.getCoreView();
	}

	public String getUpdateUIName() {
		if (currentEntry == null || currentEntry.getView() == null) {
			return "MDI";
		}
		return currentEntry.getView().getViewID();
	}

	public void registerEntry(String id, MdiEntryCreationListener2 l) {
		mapIdToCreationListener2.put(id, l);
		
		createIfAutoOpen(id);
	}

	private boolean createIfAutoOpen(String id) {
		Object o = mapAutoOpen.get(id);
		if (o instanceof Map<?, ?>) {
			Map<?, ?> autoOpenMap = (Map<?, ?>) o;

			return createEntryByCreationListener(id, autoOpenMap.get("datasource"), autoOpenMap) != null;
		}
		return false;
	}

	private MdiEntry createEntryByCreationListener(String id, Object ds, Map<?, ?> autoOpenMap) {
		MdiEntryCreationListener mdiEntryCreationListener = null;
		for (String key : mapIdToCreationListener.keySet()) {
			if (Pattern.matches(key, id)) {
				mdiEntryCreationListener = mapIdToCreationListener.get(key);
				break;
			}
		}
		if (mdiEntryCreationListener != null) {
			try {
				MdiEntry mdiEntry = mdiEntryCreationListener.createMDiEntry(id);
				
				if (mdiEntry != null && ds != null) {
					mdiEntry.setDatasource(ds);
				}
				return mdiEntry;
			} catch (Exception e) {
				Debug.out(e);
			}
		}

		MdiEntryCreationListener2 mdiEntryCreationListener2 = null;
		for (String key : mapIdToCreationListener2.keySet()) {
			if (Pattern.matches(key, id)) {
				mdiEntryCreationListener2 = mapIdToCreationListener2.get(key);
				break;
			}
		}
		if (mdiEntryCreationListener2 != null) {
			try {
				MdiEntry mdiEntry = mdiEntryCreationListener2.createMDiEntry(this, id, ds, autoOpenMap);
				if (mdiEntry == null) {
					removeEntryAutoOpen(id);
				}
				return mdiEntry;
			} catch (Exception e) {
				Debug.out(e);
			}
		}
		
		setEntryAutoOpen(id, ds);

		return null;
	}

	public void registerEntry(String id, MdiEntryCreationListener l) {
		mapIdToCreationListener.put(id, l);
		
		createIfAutoOpen(id);
	}

	public boolean showEntryByID(String id) {
		return loadEntryByID(id, true);
	}

	@Override
	public Object skinObjectInitialShow(SWTSkinObject skinObject, Object params) {
		return null;
	}

	// @see com.aelitis.azureus.ui.swt.views.skin.SkinView#skinObjectDestroyed(com.aelitis.azureus.ui.swt.skin.SWTSkinObject, java.lang.Object)
	public Object skinObjectDestroyed(SWTSkinObject skinObject, Object params) {
		MdiEntry entry = getCurrentEntry();
		if (entry != null) {
  		COConfigurationManager.setParameter("v3.StartTab",
  				entry.getId());
  		String ds = entry.getExportableDatasource();
  		COConfigurationManager.setParameter("v3.StartTab.ds", ds == null ? null : ds.toString());
		}

		return super.skinObjectDestroyed(skinObject, params);
	}
	
	public void updateUI() {
		MdiEntry currentEntry = getCurrentEntry();
		if (currentEntry != null) {
			currentEntry.updateUI();
		}
	}

	// @see com.aelitis.azureus.ui.mdi.MultipleDocumentInterface#loadEntryByID(java.lang.String, boolean)
	public boolean loadEntryByID(String id, boolean activate) {
		return loadEntryByID(id, activate, false, null);
	}

	/* (non-Javadoc)
	 * @see com.aelitis.azureus.ui.mdi.MultipleDocumentInterface#loadEntryByID(java.lang.String, boolean, boolean)
	 */
	public boolean loadEntryByID(String id, boolean activate,
			boolean onlyLoadOnce, Object datasource) {
		if (id == null) {
			return false;
		}
		MdiEntry entry = getEntry(id);
		if (entry != null) {
			if (datasource != null) {
				entry.setDatasource(datasource);
			}
			if (activate) {
				showEntry(entry);
			}
			return true;
		}

		@SuppressWarnings("deprecation")
		boolean loadedOnce = wasEntryLoadedOnce(id);
		if (loadedOnce && onlyLoadOnce) {
			return false;
		}

		MdiEntry mdiEntry = createEntryByCreationListener(id, datasource, null);
		if (mdiEntry != null) {
			if (onlyLoadOnce) {
				setEntryLoadedOnce(id);
			}
			if (activate) {
				showEntry(mdiEntry);
			}
			return true;
		}

		return false;
	}


	protected abstract void setEntryLoadedOnce(String id);

	protected abstract boolean wasEntryLoadedOnce(String id);

	public boolean entryExists(String id) {
		if (SkinConstants.VIEWID_BROWSER_BROWSE.equalsIgnoreCase(id)) {
			id = ContentNetworkUtils.getTarget(ConstantsVuze.getDefaultContentNetwork());
		}
		MdiEntry entry = mapIdToEntry.get(id);
		if (entry == null) {
			return false;
		}
		return entry.isAdded();
	}

	// @see com.aelitis.azureus.ui.mdi.MultipleDocumentInterface#setEntryAutoOpen(java.lang.String, java.lang.Object)
	public void setEntryAutoOpen(String id, Object datasource) {
		Map<String, Object> map = (Map<String, Object>) mapAutoOpen.get(id);
		if (map == null) {
			map = new LightHashMap<String, Object>(1);
		}
		map.put("datasource", datasource);
		mapAutoOpen.put(id, map);
	}
	
	// @see com.aelitis.azureus.ui.mdi.MultipleDocumentInterface#removeEntryAutoOpen(java.lang.String)
	public void removeEntryAutoOpen(String id) {
		mapAutoOpen.remove(id);
	}

	protected void setupPluginViews() {

		// When a new Plugin View is added, check out auto-open list to see if
		// the user had it open
		PluginsMenuHelper.getInstance().addPluginAddedViewListener(
				new PluginAddedViewListener() {
					// @see org.gudy.azureus2.ui.swt.mainwindow.PluginsMenuHelper.PluginAddedViewListener#pluginViewAdded(org.gudy.azureus2.ui.swt.mainwindow.PluginsMenuHelper.IViewInfo)
					public void pluginViewAdded(IViewInfo viewInfo) {
						//System.out.println("PluginView Added: " + viewInfo.viewID);
						Object o = mapAutoOpen.get(viewInfo.viewID);
						if (o instanceof Map<?, ?>) {
							processAutoOpenMap(viewInfo.viewID, (Map<?, ?>) o, viewInfo);
						}
					}
				});
	}

	public void informAutoOpenSet(MdiEntry entry, Map<String, Object> autoOpenInfo) {
		mapAutoOpen.put(entry.getId(), autoOpenInfo);
	}

	public void loadCloseables() {
		try{
			Map<?,?> loadedMap = FileUtil.readResilientConfigFile("sidebarauto.config", true);
			if (loadedMap.isEmpty()) {
				return;
			}
			BDecoder.decodeStrings(loadedMap);
	
			List<Map> orderedEntries = (List<Map>)loadedMap.get( "_entries_" );
			
			if ( orderedEntries == null ){
					// migrate old format
				for (Iterator<?> iter = loadedMap.keySet().iterator(); iter.hasNext();) {
					String id = (String) iter.next();
					Object o = loadedMap.get(id);
		
					if (o instanceof Map<?, ?>) {
						if (!processAutoOpenMap(id, (Map<?, ?>) o, null)) {
							mapAutoOpen.put(id, o);
						}
					}
				}
			}else{
				for (Map map: orderedEntries){
					String id = (String)map.get( "id" );
					
					//System.out.println( "loaded " + id );
					Object o = map.get( "value" );
					if (o instanceof Map<?, ?>) {
						if (!processAutoOpenMap(id, (Map<?, ?>) o, null)) {
							mapAutoOpen.put(id, o);
						}
					}
				}
			}
		}catch( Throwable e ){
			
			Debug.out( e );
		}
	}

	@SuppressWarnings({
		"unchecked",
		"rawtypes"
	})
	public void saveCloseables() {
		try{
			// update auto open info
			for (Iterator<String> iter = new ArrayList<String>(mapAutoOpen.keySet()).iterator(); iter.hasNext();) {
				String id = (String) iter.next();
	
				MdiEntry entry = getEntry(id);
				
				if ( entry != null && entry.isAdded()){
					
					mapAutoOpen.put(id, entry.getAutoOpenInfo());
					
				}else{
					
					mapAutoOpen.remove(id);
				}
			}
	
			Map map = new HashMap();
			
			List<Map> list = new ArrayList<Map>( mapAutoOpen.size());
			
			map.put( "_entries_", list );
			
			for ( Map.Entry<String,Object> entry: mapAutoOpen.entrySet()){
				
				Map m = new HashMap();
				
				list.add( m );
				
				String id = entry.getKey();
				
				m.put( "id", id );
				m.put( "value", entry.getValue());
				
				//System.out.println( "saved " + id );
			}
			
			FileUtil.writeResilientConfigFile("sidebarauto.config", map );
			
		}catch( Throwable e ){
			
			Debug.out( e );
		}	
	}

	private boolean processAutoOpenMap(String id, Map<?, ?> autoOpenInfo,
			IViewInfo viewInfo) {
		try {
			MdiEntry entry = getEntry(id);
			if (entry != null) {
				return true;
			}

			Object datasource = autoOpenInfo.get("datasource");
			String title = MapUtils.getMapString(autoOpenInfo, "title", id);

			MdiEntry mdiEntry = createEntryByCreationListener(id, datasource, autoOpenInfo);
			if (mdiEntry != null) {
				if (mdiEntry.getTitle().equals("")) {
					mdiEntry.setTitle(title);
				}
				return true;
			}

			String parentID = MapUtils.getMapString(autoOpenInfo, "parentID", SIDEBAR_HEADER_PLUGINS);

			if (viewInfo != null) {
				if (viewInfo.view != null) {
					entry = createEntryFromView(parentID, viewInfo.view, id, datasource,
							true, false, true);
				} else if (viewInfo.event_listener != null) {
					entry = createEntryFromEventListener(parentID,
							viewInfo.event_listener, id, true, datasource,null);
  				entry.setTitle(title);
				}
			}

			if (entry != null && datasource == null) {
				final MdiEntry fEntry = entry;
				final String dmHash = MapUtils.getMapString(autoOpenInfo, "dm", null);
				if (dmHash != null) {
					AzureusCoreFactory.addCoreRunningListener(new AzureusCoreRunningListener() {
						public void azureusCoreRunning(AzureusCore core) {
							GlobalManager gm = core.getGlobalManager();
							HashWrapper hw = new HashWrapper(Base32.decode(dmHash));
							DownloadManager dm = gm.getDownloadManager(hw);
							if (dm != null) {
								fEntry.setDatasource(dm);
							}
						}
					});
				} else {
					final List<?> listHashes = MapUtils.getMapList(autoOpenInfo, "dms",
							null);
					if (listHashes != null) {
						AzureusCoreFactory.addCoreRunningListener(new AzureusCoreRunningListener() {
							public void azureusCoreRunning(AzureusCore core) {
								List<DownloadManager> listDMS = new ArrayList<DownloadManager>(
										1);
								GlobalManager gm = core.getGlobalManager();
								for (Object oDM : listHashes) {
									if (oDM instanceof String) {
										String hash = (String) oDM;
										DownloadManager dm = gm.getDownloadManager(new HashWrapper(
												Base32.decode(hash)));
										if (dm != null) {
											listDMS.add(dm);
										}
									}
									fEntry.setDatasource(listDMS.toArray(new DownloadManager[0]));
								}
							}
						});
					}
				}
			}

			return entry != null;
		} catch (Throwable e) {
			Debug.out(e);
		}
		return false;
	}

	public void removeItem(MdiEntry entry) {
		String id = entry.getId();
		synchronized (mapIdToEntry) {
			mapIdToEntry.remove(id);
			
			removeChildrenOf(id);
		}
	}

	private void removeChildrenOf(String id) {
		if (id == null) {
			return;
		}
		synchronized (mapIdToEntry) {
			MdiEntrySWT[] entriesSWT = getEntriesSWT();
			for (MdiEntrySWT entry : entriesSWT) {
				if (id.equals(entry.getParentID())) {
					String kid_id = entry.getId();
					mapIdToEntry.remove(kid_id);
					removeChildrenOf(kid_id);
				}
			}
		}
	}
	
	public List<MdiEntry> getChildrenOf(String id) {
		if (id == null) {
			return Collections.emptyList();
		}
		List<MdiEntry> list = new ArrayList<MdiEntry>(1);
		synchronized (mapIdToEntry) {
			MdiEntrySWT[] entriesSWT = getEntriesSWT();
			for (MdiEntrySWT entry : entriesSWT) {
				if (id.equals(entry.getParentID())) {
					list.add(entry);
				}
			}
		}
		return list;
	}

	public Object updateLanguage(SWTSkinObject skinObject, Object params) {
		MdiEntry[] entries = getEntries();
		
		for (MdiEntry entry : entries) {
			if (entry instanceof BaseMdiEntry) {
				BaseMdiEntry baseEntry = (BaseMdiEntry) entry;
				baseEntry.updateLanguage();
			}
		}

		return null;
	}

	public void setPreferredOrder(String[] preferredOrder) {
		this.preferredOrder = preferredOrder;
	}

	public String[] getPreferredOrder() {
		return preferredOrder == null ? new String[0] : preferredOrder;
	}
}
