/*
 * Created on 2004-08-12
 *
 * Part of the Symbian development plug-in for Eclipse
 * Copyright (C) Mikołaj Zalewski, 2004
 */
package pl.zalewski.mikolaj.symbdev;

import java.io.File;
import java.util.*;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.eclipse.jface.preference.IPreferenceStore;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

import com.ice.jni.registry.NoSuchValueException;
import com.ice.jni.registry.Registry;
import com.ice.jni.registry.RegistryException;
import com.ice.jni.registry.RegistryKey;

/**
 * @author mikolaj
 *
 * This class holds the list of all the configured Symbian SDKs
 */
public class SymbianSDKsList
{
	static final String P_SDK_PREFIX="SymbianSDKsList.";  
	static final String P_DEAULT_TOOL=".defaultTool";  
	
	protected SymbianSDKsList()
	{
		fSdkMap=new TreeMap();
		fEpocRootMap=new TreeMap();
		fStore=SymbdevPlugin.getDefault().getPreferenceStore();
		autoDetect();
	}

	public static SymbianSDKsList getInstance()
	{
		if (instance==null)
			instance=new SymbianSDKsList();
		return instance;
	}
	
	/**
	 * Returns the SDK entries 
	 * @return An iterator from witch you can obrain String objects with SDK's names
	 */
	public Iterator getEntries()
	{
		return fSdkMap.keySet().iterator();
	}
	
	/**
	 * Returns the entry for a given SDK name
	 * @param name The name of the SDK to obtain
	 * @return The entry
	 */
	public SymbianSDKEntry getEntry(String name)
	{
	    if (name==null)
	        return null;
		return (SymbianSDKEntry)fSdkMap.get(name);
	}
	
	/**
	 * Returns the entry for a given EPOCROOT
	 * @param name The EPOCROOT path. The path should start with a drive letter
	 * and end with '\' 
	 * @return The SDK entry
	 */
	public SymbianSDKEntry getEntryByEpocRoot(String path)
	{
	    if (path==null)
	        return null;
		return (SymbianSDKEntry)fEpocRootMap.get(path.toLowerCase());
	}
	
	/**
	 * @return the default SDK found in devices.xml. May be null 
	 */
	public SymbianSDKEntry getDefaultEntry()
	{
		return fDefaultSDK;
	}
	
	public boolean isEmpty()
	{
	    return fSdkMap.isEmpty();
	}
	
	public String detectErrorString=null;
	
	final String SYMBIAN_SDKS_REG_PATH="SOFTWARE\\Symbian";              // in HKLM	
	final String SYMBIAN_COMMON_REG_PATH="SOFTWARE\\Symbian\\EPOC SDKs"; // in HKLM	
	final String SYMBIAN_COMMON_REG_VALUE="CommonPath";	

	/**
	 * @return The path the Symbian Common directory or <code>null</code>
	 * if not found 
	 */
	String getSymbianCommonPath()
	{
		String path=SymbdevPlugin.getHKLMRegValueString(SYMBIAN_COMMON_REG_PATH, SYMBIAN_COMMON_REG_VALUE);
		if (path==null)
		    return null;
		if (!path.endsWith("\\"))
		    path=path+'\\';
		return path;
	}
/*	
	String getSymbianDevicesPath()
	{
		String path=SymbdevPlugin.getHKLMRegValueString(SYMBIAN_COMMON_REG_PATH, SYMBIAN_COMMON_REG_VALUE);
		if (path!=null) return path+"\\devices.xml";
			else return null;
	}*/
	
	class DevicesXmlParser extends DefaultHandler
	{
		SymbianSDKEntry entry=null;
		boolean defaultEntry=false;
		StringBuffer strBuf=new StringBuffer();
		
        Locator locator = null;

		public void setDocumentLocator(Locator locator) {
			this.locator = locator;  
		}		
		
		boolean getBooleanValue(String str)
		{
		    if (str==null) return false;
			if (str.equals("yes") || str.equals("true")) return true;
			return false;
		}
		
		public void characters(char[] ch, int start, int length)
		{
			strBuf.append(ch, start, length);
		}
		
		public void startElement(String uri, String localName,
                String qName, Attributes attributes)
         		throws SAXException
		{
			strBuf.setLength(0);
			
			if (qName.equals("device"))
			{
				String sdkId=attributes.getValue("id");
				String sdkName=attributes.getValue("name");
				String defaultSDK=attributes.getValue("default");
				if (sdkId==null)
					throw new SAXParseException("No device id", locator);
			    defaultEntry=getBooleanValue(defaultSDK);
//				System.out.println("Progrssing device "+name);
				
				entry=new SymbianSDKEntry();
				if (sdkName!=null && fSdkMap.get(sdkId)!=null)
				    entry.name=sdkName;
					else entry.name=sdkId;
			}
		}
		
		public void endElement(String namespaceURI, String localName, String qName)
         	throws SAXException
		{
			if (qName.equals("device"))
			{
				if (entry.toolsRoot==null)
					entry.toolsRoot=entry.epocRoot;
				if (entry.epocRoot==null)
					throw new SAXParseException("No epocroot in device", locator);
				
				if (!entry.epocRoot.endsWith("\\"))
				    entry.epocRoot=entry.epocRoot+'\\';
				if (!entry.toolsRoot.endsWith("\\"))
				    entry.toolsRoot=entry.toolsRoot+'\\';
				detectedSDK(entry);
				if (defaultEntry) fDefaultSDK=entry;
				entry=null;
				defaultEntry=false;
			} else if (qName.equals("epocroot"))
			{
				if (entry==null)
					throw new SAXParseException("epocroot outside device element", locator);
			   entry.epocRoot=strBuf.toString();
			} else if (qName.equals("toolsroot"))
			{
				if (entry==null)
					throw new SAXParseException("toolroot outside device element", locator);
			   entry.toolsRoot=strBuf.toString();
			}			
		}
	}
	
	void detectedSDK(SymbianSDKEntry entry)
	{
		fStore.setDefault(P_SDK_PREFIX+entry.name+P_DEAULT_TOOL, -1);
		entry.win32ToolId=fStore.getInt(P_SDK_PREFIX+entry.name+P_DEAULT_TOOL);
		fSdkMap.put(entry.name, entry);
		fEpocRootMap.put(entry.epocRoot.toLowerCase(), entry);
	}
	
	void updateWin32ToolIdPref(String name, int id)
	{
		fStore.setValue(P_SDK_PREFIX+name+P_DEAULT_TOOL, id);		
	}
	
	void parseDevicesXmlFile(String fileName)
	{
	    File f=new File(fileName);
	    if (!f.exists())
	    {
	        SymbdevPlugin.traceStr(SymbdevPlugin.TRACE_SDKDETECT, "No devices.xml file - skipping");
	        return;
	    }
	    
		try {
			SAXParser parser=SAXParserFactory.newInstance().newSAXParser();
			parser.parse(new File(fileName), new DevicesXmlParser());
		}
		catch (Exception e)
		{
			e.printStackTrace();
			detectErrorString="Error reading devices.xml: "+e.getMessage();
		}		
	}
	
	void detectDevicesXml(String commonPath)
	{
	    String fileName=commonPath+"devices.xml";
		fileName=fileName.replace('\\', '/');
		parseDevicesXmlFile(fileName);
	}
	
	void detectWinRegistry(String commonPath)
	{
	    try
	    {
	        RegistryKey key=Registry.HKEY_LOCAL_MACHINE.openSubKey(SYMBIAN_SDKS_REG_PATH);
	        Enumeration subkeys=key.keyElements();
	        while (subkeys.hasMoreElements())
	        {
	           String keyName=(String)subkeys.nextElement();
	           SymbdevPlugin.traceStr(SymbdevPlugin.TRACE_SDKDETECT, "Checking subkey "+keyName);
	           RegistryKey platformKey=key.openSubKey(keyName);
	           
	           Enumeration sdks=platformKey.keyElements();
	           while (sdks.hasMoreElements())
	           {
		           String sdkName=(String)sdks.nextElement();
		           SymbdevPlugin.traceStr(SymbdevPlugin.TRACE_SDKDETECT, "Possible SDK: "+sdkName);
		           RegistryKey sdkKey=platformKey.openSubKey(sdkName);
		           
		           String target;
		           try
		           {
		               target=sdkKey.getStringValue("TargetDir");		               
		           }
		           catch (NoSuchValueException e)
		           {
		               continue;
		           }
		           if (!target.endsWith("\\"))
		               target+='\\';
		           if (target.equalsIgnoreCase(commonPath) || getEntryByEpocRoot(target.toLowerCase())!=null)
		               continue;
	               SymbdevPlugin.traceStr(SymbdevPlugin.TRACE_SDKDETECT, "TargetDir OK - continuing");
		           
		           String kitId;
		           try
		           {
		               kitId=sdkKey.getStringValue("KitId");		               
		           }
		           catch (NoSuchValueException e)
		           {
		               kitId=sdkName;
		           }
		           if (getEntry(kitId)!=null)
		               continue;
		           
	               SymbdevPlugin.traceStr(SymbdevPlugin.TRACE_SDKDETECT, "KitId OK - continuing");	               
	               
		           String toolsPath;
		           try
		           {
		               toolsPath=sdkKey.getStringValue("SharedDir");		               
		           }
		           catch (NoSuchValueException e)
		           {
		               toolsPath=target;
		           }
		           if (!toolsPath.endsWith("\\"))
		               toolsPath+='\\';		           
		           
	               SymbdevPlugin.traceStr(SymbdevPlugin.TRACE_SDKDETECT, "SDK detected - adding to list");
	               SymbianSDKEntry entry=new SymbianSDKEntry();
	               entry.epocRoot=target;
	               entry.toolsRoot=toolsPath;
	               entry.name=kitId;
	               detectedSDK(entry);
	           }
	           platformKey.closeKey();
	        }
	        key.closeKey();
	    }
	    catch (RegistryException e)
	    {
	        String error="Error reader SDK information from the registry: "+e.getMessage();
	        if (detectErrorString==null)
	            detectErrorString=error;
	        	else detectErrorString+='\n'+error;
	    }
	}
	
	synchronized protected void autoDetect()
	{
	    String commonPath=getSymbianCommonPath();
	    if (commonPath==null)
	    {
	        detectErrorString="Couldn't find Symbian devices.xml common path in registry";
	        return;
	    }

	    detectDevicesXml(commonPath);
	    detectWinRegistry(commonPath);
	}
	
	protected static SymbianSDKsList instance;
	
	SymbianSDKEntry  fDefaultSDK;	
	TreeMap fSdkMap;
	TreeMap fEpocRootMap;
	IPreferenceStore fStore;
}
