/*
 * Copyright (c) 2006, KNOPFLERFISH project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials
 *   provided with the distribution.
 *
 * - Neither the name of the KNOPFLERFISH project nor the names of its
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @author Philippe Laporte
 */

//TODO lots of optimization to be done, both in speed and storage

package org.knopflerfish.util.metatype;

import java.io.*;
import java.net.URL;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Locale;
import java.util.Properties;
import java.util.Set;
import java.util.Vector;
import java.util.Enumeration;
import java.util.Dictionary;

import org.osgi.framework.*;
import org.osgi.service.cm.Configuration;
import org.osgi.service.metatype.MetaTypeInformation;
import org.osgi.service.metatype.ObjectClassDefinition;
import org.osgi.service.metatype.AttributeDefinition;

import java.lang.reflect.*;

//TODO lots of optimization

public class BundleMetaTypeResource implements MetaTypeInformation{
	
	private Bundle bundle;
	
	private Vector metaDatas = new Vector();
	
	//id -> MetaData
	private Hashtable pids = new Hashtable();
	private Hashtable factoryPids = new Hashtable();
	
	private String[] locales;
	
	
	public BundleMetaTypeResource(Bundle bundle){
		this.bundle = bundle;
	}

	public Bundle getBundle() {
		return bundle;
	}
	
	public void addMetaData(MetaData md){
		metaDatas.add(md);
	}

	public String[] getFactoryPids() {
		Vector factoryPidsV = new Vector();
		factoryPidsV.addAll(factoryPids.keySet());
		return  (String[]) factoryPidsV.toArray(new String[factoryPidsV.size()]);  
	}

	public String[] getPids() {
		Vector pidsV = new Vector();
		pidsV.addAll(pids.keySet());
		return (String[]) pidsV.toArray(new String[pidsV.size()]);
	}
		
	public void prepare(){
		Enumeration enume = metaDatas.elements();
		while(enume.hasMoreElements()){
			MetaData md = (MetaData) enume.nextElement();
		    Iterator it = md.getPids().iterator();
		    while(it.hasNext()){
		    	pids.put((String) it.next(), md);
		    }
			it = md.getFactoryPids().iterator();
			while(it.hasNext()){
				factoryPids.put((String) it.next(), md);
			}
			if(locales != null){
				String[] newLocales = md.getLocales();
				String[] temp = new String[locales.length + newLocales.length];
				System.arraycopy(locales, 0, temp, 0, locales.length);
				System.arraycopy(newLocales, 0, temp, locales.length, newLocales.length);
				locales = temp;
			}
			else{
				locales = md.getLocales();
			}
		}
	}

	public String[] getLocales() {
		return locales;
	}

	public ObjectClassDefinition getObjectClassDefinition(String id, String locale) {
		MetaData md;
		md = (MetaData) pids.get(id);
		if(md == null){
			md = (MetaData) factoryPids.get(id);
		}
		if(md == null){
			throw new IllegalArgumentException("no information available for id " + id);
		}
		if(locale == null){
			locale = Locale.getDefault().toString();
		}
		
		//TODO validate locale
		
		return md.getOCD(id, locale);		
	}
	
	void mergeWith(BundleMetaTypeResource other){
		if(other == null){
			return;
		}
		  
		if(bundle != other.bundle){
			return;
		}
		  
		Enumeration enume = other.metaDatas.elements();
		while(enume.hasMoreElements()){
			metaDatas.add(enume.nextElement());
		}
	}
	
}

//TODO localization specs not definite yet

class MetaData {
	
	private String localizationFileBaseName;
	
	private Hashtable pids = new Hashtable();
	private Hashtable factoryPids = new Hashtable();
	
	private Hashtable OCDs = new Hashtable();
	
	private String[] locales;
	
	private Bundle bundle;

        final static private String locBaseDir =
           Constants.BUNDLE_LOCALIZATION_DEFAULT_BASENAME.substring(0, 14);

	//TODO check for Attributes manifest's Constants.BUNDLE_LOCALIZATION
	  
	public MetaData(String localizationFile, Bundle bundle){
            this.localizationFileBaseName =  localizationFile;
	    this.bundle = bundle;
	}	
	
	public MetaData(Bundle bundle){
          this.localizationFileBaseName = "bundle";
            this.bundle = bundle;	
	}	
	
	public void prepare(){
		loadLocales();
	}
	
	public String[] getLocales(){
		return locales;
	}
	
	public Set getFactoryPids() {
		return factoryPids.keySet();
	}

	public Set getPids() {
		return pids.keySet();
	}
	
	public void addOCD(ObjectClassDefinition ocd){
		OCDs.put(ocd.getID(), ocd);
	}
	
	//TODO locale finding rules not definite
	
	ObjectClassDefinition getOCD(String id, String locale){
		OCD ocd = (OCD) pids.get(id);
		if(ocd == null){
			ocd = (OCD) factoryPids.get(id);
		}
		if(ocd == null) return null;
		
		Enumeration url;
		int underscore;
	
		url = bundle.findEntries(locBaseDir, localizationFileBaseName + "_" + locale + ".properties", false);
		if(url == null){
			underscore = locale.lastIndexOf('_');
			if(underscore > 0){
				locale = locale.substring(0, underscore - 1);
			}
			url = bundle.findEntries(locBaseDir, localizationFileBaseName + "_" + locale + ".properties", false);
			if(url == null){
				underscore = locale.lastIndexOf('_');
				if(underscore > 0){
					locale = locale.substring(0, underscore - 1);
				}
				url = bundle.findEntries(locBaseDir, localizationFileBaseName + "_" + locale + ".properties", false);
			}
			locale = Locale.getDefault().toString();
			url = bundle.findEntries(locBaseDir, localizationFileBaseName + "_" + locale + ".properties", false);
			if(url == null){
				underscore = locale.lastIndexOf('_');
				if(underscore > 0){
					locale = locale.substring(0, underscore - 1);
				}
				url = bundle.findEntries(locBaseDir, localizationFileBaseName + "_" + locale + ".properties", false);
				if(url == null){
					underscore = locale.lastIndexOf('_');
					if(underscore > 0){
						locale = locale.substring(0, underscore - 1);
					}
					url = bundle.findEntries(locBaseDir, localizationFileBaseName + "_" + locale + ".properties", false);
				}
				//lastly
				if(url == null){
					url = bundle.findEntries(locBaseDir, localizationFileBaseName + ".properties", false);
				}
			}	
		}
		
                if (url != null) {
                    ocd.localize(loadLocaleEntries((URL)url.nextElement()));
                }
		
		return ocd;
	}
	
	public void designate(String factoryPid, String pid, String ocdref, 
			              Configuration conf, Vector currentAttributes){
		
		ObjectClassDefinition ocd;
		ocd = (ObjectClassDefinition) OCDs.get(ocdref);
		
		if(ocd != null){ 
			if(conf != null && currentAttributes.size() > 0){
				AttributeDefinition[] attrDefs = ocd.getAttributeDefinitions(ObjectClassDefinition.ALL);
				Hashtable ADs = new Hashtable(); //id is key
				for(int i = 0; i < attrDefs.length; i++){
					AttributeDefinition ad = attrDefs[i];
					ADs.put(ad.getID(), ad);
				}
				
				Dictionary props = conf.getProperties();
				
				Enumeration attrsAssigns = currentAttributes.elements();
				while(attrsAssigns.hasMoreElements()){
					AE ae = (AE) attrsAssigns.nextElement();
					AttributeDefinition ad = (AttributeDefinition) ADs.get(ae.adref);
				
					if(ad != null){
						Object value = null;
						int card = ad.getCardinality();
						
						switch(ad.getType()){
            				case AttributeDefinition.STRING:
            					if(card < 0){
            						value = new Vector(-1 * card);
            						Enumeration values = ae.values.elements();
            						while(values.hasMoreElements()){
            							((Vector)value).add(values.nextElement());
            						}
            					}
            					else if(card > 0){
            						value = new String[card];
            						Enumeration values = ae.values.elements();
            						for(int i = 0; values.hasMoreElements(); i++){
            							((String[])value)[i] = (String) values.nextElement();
            						}
            					}
            					else{ 
            						value = ae.values.elementAt(0);
            					}
            					break;
            				case AttributeDefinition.BOOLEAN:
            					if(card < 0){
            						value = new Vector(-1 * card);
            						Enumeration values = ae.values.elements();
            						while(values.hasMoreElements()){
            							((Vector)value).add(Boolean.valueOf((String)values.nextElement()));
            						}
            					}
            					else if(card > 0){
            						value = new Boolean[card];
            						Enumeration values = ae.values.elements();
            						for(int i = 0; values.hasMoreElements(); i++){
            							((Boolean[])value)[i] = Boolean.valueOf((String) values.nextElement());
            						}
            					}
            					else{ 
            						value = Boolean.valueOf((String) ae.values.elementAt(0));
            					}
            					break;						
            				case AttributeDefinition.BYTE:
            					if(card < 0){
            						value = new Vector(-1 * card);
            						Enumeration values = ae.values.elements();
            						while(values.hasMoreElements()){
            							((Vector)value).add(Byte.valueOf((String)values.nextElement()));
            						}
            					}
            					else if(card > 0){
            						value = new Byte[card];
            						Enumeration values = ae.values.elements();
            						for(int i = 0; values.hasMoreElements(); i++){
            							((Byte[])value)[i] = Byte.valueOf((String) values.nextElement());
            						}
            					}
            					else{ 
            						value = Byte.valueOf((String) ae.values.elementAt(0));
            					}
            					break;	
            				case AttributeDefinition.DOUBLE:
            					if(card < 0){
            						value = new Vector(-1 * card);
            						Enumeration values = ae.values.elements();
            						while(values.hasMoreElements()){
            							((Vector)value).add(Double.valueOf((String)values.nextElement()));
            						}
            					}
            					else if(card > 0){
            						value = new Double[card];
            						Enumeration values = ae.values.elements();
            						for(int i = 0; values.hasMoreElements(); i++){
            							((Double[])value)[i] = Double.valueOf((String) values.nextElement());
            						}
            					}
            					else{ 
            						value = Double.valueOf((String) ae.values.elementAt(0));
            					}
            					break;	
            				case AttributeDefinition.FLOAT:
            					if(card < 0){
            						value = new Vector(-1 * card);
            						Enumeration values = ae.values.elements();
            						while(values.hasMoreElements()){
            							((Vector)value).add(Float.valueOf((String)values.nextElement()));
            						}
            					}
            					else if(card > 0){
            						value = new Float[card];
            						Enumeration values = ae.values.elements();
            						for(int i = 0; values.hasMoreElements(); i++){
            							((Float[])value)[i] = Float.valueOf((String) values.nextElement());
            						}
            					}
            					else{ 
            						value = Float.valueOf((String) ae.values.elementAt(0));
            					}
            					break;	
            				case AttributeDefinition.INTEGER:
            					if(card < 0){
            						value = new Vector(-1 * card);
            						Enumeration values = ae.values.elements();
            						while(values.hasMoreElements()){
            							((Vector)value).add(Integer.valueOf((String)values.nextElement()));
            						}
            					}
            					else if(card > 0){
            						value = new Integer[card];
            						Enumeration values = ae.values.elements();
            						for(int i = 0; values.hasMoreElements(); i++){
            							((Integer[])value)[i] = Integer.valueOf((String) values.nextElement());
            						}
            					}
            					else{ 
            						value = Integer.valueOf((String) ae.values.elementAt(0));
            					}
            					break;	
            				case AttributeDefinition.LONG:
            					if(card < 0){
            						value = new Vector(-1 * card);
            						Enumeration values = ae.values.elements();
            						while(values.hasMoreElements()){
            							((Vector)value).add(Long.valueOf((String)values.nextElement()));
            						}
            					}
            					else if(card > 0){
            						value = new Long[card];
            						Enumeration values = ae.values.elements();
            						for(int i = 0; values.hasMoreElements(); i++){
            							((Long[])value)[i] = Long.valueOf((String) values.nextElement());
            						}
            					}
            					else{ 
            						value = Long.valueOf((String) ae.values.elementAt(0));
            					}
            					break;	
            				case AttributeDefinition.SHORT:
            					if(card < 0){
            						value = new Vector(-1 * card);
            						Enumeration values = ae.values.elements();
            						while(values.hasMoreElements()){
            							((Vector)value).add(Short.valueOf((String)values.nextElement()));
            						}
            					}
            					else if(card > 0){
            						value = new Integer[card];
            						Enumeration values = ae.values.elements();
            						for(int i = 0; values.hasMoreElements(); i++){
            							((Short[])value)[i] = Short.valueOf((String) values.nextElement());
            						}
            					}
            					else{ 
            						value = Short.valueOf((String) ae.values.elementAt(0));
            					}
            					break;	
            				case AttributeDefinition.CHARACTER:
            					if(card < 0){
            						value = new Vector(-1 * card);
            						Enumeration values = ae.values.elements();
            						while(values.hasMoreElements()){
            							((Vector)value).add(new Character(((String)values.nextElement()).charAt(0)));
            						}
            					}
            					else if(card > 0){
            						value = new Character[card];
            						Enumeration values = ae.values.elements();
            						for(int i = 0; values.hasMoreElements(); i++){
            							((Character[])value)[i] = new Character(((String)values.nextElement()).charAt(0));
            						}
            					}
            					else{ 
            						value = new Character(((String) ae.values.elementAt(0)).charAt(0));
            					}
            					break;								
						}
            	
					/*TODO  code along these lines would be less repetitive
					Class clazz = null;
						
					switch(ad.getType()){
        				case AttributeDefinition.STRING:
        					clazz = null;
        					break;
        				case AttributeDefinition.BOOLEAN:
        					clazz = Boolean.class;
        					break;						
        				case AttributeDefinition.BYTE:
        					clazz = Byte.class;
        					break;	
        				case AttributeDefinition.DOUBLE:
        					clazz = Double.class;
        					break;	
        				case AttributeDefinition.FLOAT:
        					clazz = Float.class;
        					break;	
        				case AttributeDefinition.INTEGER:
        					clazz = Integer.class;
        					break;	
        				case AttributeDefinition.LONG:
        					clazz = Long.class;
        					break;	
        				case AttributeDefinition.SHORT:
        					clazz = Short.class;
        					break;	
        				case AttributeDefinition.CHARACTER:
        					clazz = Character.class;
        					
        					break;
        					
					}	
					
					Method method = null;
					try{
						method = clazz.getMethod("valueOf", new Class[]{String.class});
					}
					catch(NoSuchMethodException e){
						
					}
					
					
					
					
					Boolean val = null;
					
					try{
						val = (Boolean) method.invoke(null, new Object[]{"true"});
					}
					catch(IllegalAccessException e){
						
					}
			        catch(InvocationTargetException e){
						
					}	
        			*/		
						if(value != null){
							props.put(ad.getName(), value);
						}
					}
            	
				} //while
			
				try{
					conf.update(props);
				}
				catch(IOException ioe){}
				
			} //if
				
			
			if(factoryPid != null){
				factoryPids.put(factoryPid, ocd);
			}
			else{
				pids.put(pid, ocd);
			}
		}
		
		//TODO enforce bundleLocation
	}
	
	private void loadLocales(){
            String x = Locale.getDefault().toString();
            Vector localesV = new Vector();
	    
            Enumeration localizationFiles = bundle.findEntries(locBaseDir, localizationFileBaseName + "*.properties", false);
            if(localizationFiles != null){
	    	while(localizationFiles.hasMoreElements()){ 
	    	    URL url = (URL)localizationFiles.nextElement();
                    String fileName = url.getFile().substring(15);
                    if(fileName.length() == (localizationFileBaseName + ".properties").length()){
		       	continue;
                    }
                    else {
                        int dot = fileName.lastIndexOf('.');
                        fileName= fileName.substring(0, dot);
                        int underscore = fileName.indexOf('_');
                        fileName = fileName.substring(underscore + 1);
                        localesV.add(fileName);
                    }
	    	}	
	    	locales = (String[]) localesV.toArray(new String[localesV.size()]);
            }
	}

	private Properties loadLocaleEntries(URL url){
		Properties entries = new Properties();
		
		try{
			InputStream is = url.openStream();
			  
			entries.load(is);
		}
		catch(IOException e){ 
			return entries;
		}
		return entries;
	}
	
}


