/*
 * Copyright 2008-2014 Mirko Solazzi - OBEROn Platform [www.oberonplatform.com]
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package com.oberon.client;

import java.util.*;

import javax.servlet.http.HttpSession;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.oberon.client.ApplicationSession;
import com.oberon.client.Configuration;
import com.oberon.ooql.connection.ConnectionManager;
import com.oberon.ooql.connection.HTTPConnection;
import com.oberon.ooql.connection.OBConnection;
import com.oberon.ooql.parser.ParserCreationException;
import com.oberon.ooql.parser.ParserLogException;
import com.oberon.ooql.sdk.*;
import com.oberon.ooql.sdk.Dictionary;
import com.oberon.ooql.sdk.MenuItem.OnMenuItemClickListener;
import com.oberon.util.StringUtils;

/**
 * Basic class for Android Applications.
 * Includes connection and utility functions.
 *
 * @author   Mirko Solazzi
 * @version  5.0
 */
public abstract class Application extends Activity {

	/** Default separator token */
	public final static String sToken="<|>";
	
	// Web Links
	/** OBEROn Web Site */
	public final static String URL_OBERON = "http://www.oberonplatform.com/";
	/** OOQL syntax documentation web link */
	public final static String URL_OOQL = URL_OBERON+"ooql.php";
	/** Java API documentation web link */
	public final static String URL_JAVAAPI = URL_OBERON+"api/";

	// Display Parameters
	/** The current Application */
	private static Application app;
	/** The Application title */
	public static String name = "OBEROn Platform - Application Client";
	/** The Current screen width */
	public static int screenWidth = 400;
	/** The Current screen height */
	public static int screenHeight = 400;
	/** The Current screen density */
	public static float screenDensity = 1.0f;
	
	// User Context
	/** The Application session */
	public ApplicationSession session ;
	/** OBEROn user framework */
	public Framework framework;
	/** Default Locale */
	public Locale locale = Locale.ENGLISH;
	/** Select Application from a list */
	public boolean selectApplication = false;
	/** The Application dialog registry */
	public final static Hashtable dialogs = new Hashtable() ;

	// Configuration and Connection
	/** Configuration object */
	public Configuration conf;

	// Application Parameters (Defaults)
	/** The Application main menu */
	public static String  application_MainMenu="Application";
	/** Dictionary section for Menu labels*/
	public static String  dictionary_menu_section="Menu";
	/** Dictionary section for Common labels*/
	public static String  dictionary_common_section="Common";
	/** Dictionary section for Application labels*/
	public static String  dictionary_section="Application";
	/** The default dictionary language */
	public static String  dictionary_default_language = "EN";
	/** Show the Object class in the result lists */
	public static boolean show_object_class = true;
	/** Show the Object name / revision in the result lists */
	public static boolean show_object_namerev = true;
	/** Use the search patterns for text fields */
	public static boolean use_search_patterns = true;
	/** Use the fixed decimal separator */
	public static String  number_separator_symbols = "";
	/** Default name-revision separator */
	public static String  name_revision_separator = ".";	
	/** Label "All" for search selection item */
	public static String  search_all_label = "All";
	// If add new parameters: update the MenuDlg class and createMenu method

	/** "ID" */
	public final static String ID = "ID";
	/** "FILE" */
	public final static String CONF_FILE_SECTION = "FILE";
	/** "VIEW" */
	public final static String CONF_VIEW_SECTION = "VIEW";
	private Vector vCoords;		
	
	
	public static float w_multiplier = 1.5f ;
	public static float h_multiplier = 1.5f ;
	
	public Application () {
	}	

	public synchronized void setApp(Application application) {
		app = application;
	}
	
	public static Application getApp() {
 	   return app;
    }
	
//---------------------------- CONFIGURATION FILE PROPERTIES ------------------------  

	/**
	 * Get a configuration property value
	 * 
	 * @param section   configuration file section
	 * @param property  property name
	 */
	public String getProperty(String section, String property) {
		if (conf!=null) {
			String sValue =  conf.getValue(section,property);
			if (sValue!=null) { return sValue; }
		}
		return "";	     
	}

	/**
	 * Get a configuration property value from the "CONNECTION" section
	 * 
	 * @param property  property name
	 */
	public String getConnectionProperty(String property) {
		if (conf!=null) {
			String sValue =  conf.getValue(OBConnection.CONN_SECTION,property);
			if (sValue!=null) { return sValue; }
		}
		return "";	     
	}
	
	//----------------------- DATABASE CONNECTIONS ---------------------------
	/**
	 *  Open OBEROn Connections
	 * @throws Exception 
	 */
	public void openConnections() throws Exception {
		session = new ApplicationSession();
		if (conf!=null) { conf.setValue(OBConnection.CONN_SECTION,OBConnection.CONN_TYPE,HTTPConnection.CONN_TYPE); }
		ConnectionManager.openConnections(this);
		String username = getConnectionProperty(ConnectionManager.PROP_OBUSR);
		String password = getConnectionProperty(ConnectionManager.PROP_OBPWD);
		if ( username!=null && username.length()>0  ) {
			openFramework(username,password!=null?password:"");
			if (framework.getLanguage().length()==0) {
				framework.setLanguage(dictionary_default_language);
			}
			session.setAttribute(Arg.Language , framework.getLanguage() );
		}
	}  
	
	
	/** Close all OBEROn connections
	 * 
	 * @since 4.1
	 */
	public void closeConnections() {	
		 ConnectionManager.closeConnections(this);
	}
	
	/**
	 * Get OBEROn connection   
	 *
	 * @see ConnectionManager#getConnection(Framework)
	 */
	public OBConnection getConnection() {
		if (framework!=null) framework.clearResult();
		return ConnectionManager.getConnection(framework);
	}
	
	/**
	 * Release an OBEROn connection   
	 * 
	 * @param conn The OBEROn connection 
	 * 
	 * @see ConnectionManager#releaseConnection(OBConnection,Framework)
	 */
	public void releaseConnection(OBConnection conn) {
		ConnectionManager.releaseConnection(conn,framework);
	}

	/**
	 * Check if the Application is connected to the Database or to RMI/HTTP server
	 * 
	 * @see ConnectionManager#isConnected()
	 */
	public boolean isConnected() {
		return ConnectionManager.isConnected();
	} 

	/**
	 * Check if the Connection is established directly to the DB
	 * 
	 * @see ConnectionManager#isDBConnection() 
	 */
	public boolean isDBConnection() {
		return ConnectionManager.isDBConnection();
	} 

	
	//---------------------------- OBEROn ACTIONS ------------------------  	
	
	/**
	 * Retrieve the current framework from the application session
	 */
	public static Framework getFramework(HttpSession session) {
		return (Framework)session.getAttribute(Arg.Framework);
	}
	
	/**
	 * Save the current framework into the application session
	 */
	public static void setFramework(HttpSession session,Framework framework) {
		session.setAttribute(Arg.Framework,framework);
	}
	
	/**
	 * Login into OBEROn
	 * 
	 * @param sUserName  the user name
	 * @param sPassword  the user password
	 */
	public void openFramework(String sUserName,String sPassword) throws OberonException {
		if ( !isConnected() ) { 
			throw new OberonException("NotConnected",new String[0]);
		}
		if (StringUtils.isBlank(sUserName)) { 
			throw new OberonException("UserNameNotDefined",new String[0]);
		}
		Framework preFramework=framework;
		framework=new Framework(sUserName,sPassword);
		if (preFramework==null) { preFramework=framework; }
		framework.setVerbose(false);
		framework.setSQLTrace(false);	 
		framework.setRenderingEngine("Android");
		// link the framework
		try {
			framework.link();
			log("OBEROn Login for user '"+sUserName+"' successful!" );
			setFramework(session,framework);
			String sResultLog;
			if ((sResultLog=framework.getResultLog().trim()).length()>0) {	log(sResultLog);}
			framework.clearResultLog();
		} catch (OberonException e) {	
			if (preFramework.isLinked()) {  
				framework = preFramework;  
				setFramework(session,framework);
			}
			log("OBEROn Login for user '"+sUserName+"' failed: "+e.getMessage() );
		  throw e;
		}
	}

	/**
	 * Perform one or more OBEROn OOQL commands with current application Framework
	 * 
	 * @param  sOOQLCommands  command text 
	 * @return a Vector of String(s) where each element represents the result for the corresponding command 
	 * 
	 */
	public Vector<String> performOOQLCommands( String sOOQLCommands ) throws OberonException, ParserLogException, ParserCreationException  {				
		if (framework==null || !framework.isLinked()) { throw new OberonException("Framework not Linked!"); }
		Vector<String> vRes;
		String sResultLog;
		OOQLCommand ooql = new OOQLCommand(sOOQLCommands );
		try {			
			vRes = ooql.execute(framework);
			setFramework(session,framework);
		} finally {
			vCoords=ooql.getCommandCoords();   
			if ((sResultLog=ooql.getResultLog().trim()).length()>0) {
				log("Result LOG:\n"+sResultLog);	
				framework.clearResultLog();
			}
		}
		return vRes;
	}      
	
	/**
	 * Parse the OOQL command text 
	 * 
	 * @param sOOQLCommands  command text 
	 * 
	 * @return a report representing the OOQL language parsed tree
	 */
	public String parseOOQLCommands(String sOOQLCommands ) throws OberonException, ParserLogException, ParserCreationException  {
		if (framework==null || !framework.isLinked()) { throw new OberonException("Framework not Linked!"); }
		String result;
		OOQLCommand ooql = new OOQLCommand(sOOQLCommands );
		try {			
			result = ooql.parse(framework);
			setFramework(session,framework);
		} finally {
			vCoords=ooql.getCommandCoords();   
		}
		return result;
	}  

	
	/**
	 * Get the command text coordinates for each OOQL command
	 * for the last performOOQLCommands execution 
	 * 
	 * @return Vector of int[4]{startLine,startColumn,endLine,endColumn}
	 */
	public Vector<int[]> getCommandCoords(){
		return vCoords;
	}

	/**
	 * Turn off the verbose option
	 */
	public void setNoVerbose() {
		if (framework!=null) {
			framework.setSQLTrace(false);
			framework.setVerbose(false);
			framework.setFilterHidden(false);
		}
	}

	//----------------------------- IMAGE MANIPULATION ---------------------------------------

	/**
	 * Retrieve an image/icon from the Drawable folder
	 * 
	 * @param iconName   the icon/image file name
	 */
	public static Drawable getIcon(String iconName) {
		int imageId = app.getResources().getIdentifier(iconName, "drawable",app.getPackageName());
		if (imageId!=0) {
			Drawable img = app.getResources().getDrawable(imageId);
			if (img!=null) {
				img.setBounds( 0, 0, img.getIntrinsicWidth(), img.getIntrinsicHeight() );
				return img;
			}
		}
		return null;
	}
	
	/**
	 * Convert and resize a byte[] image 
	 * 
	 * @param baImage   byte array image
	 * @param scx			  resize width (if 0 is ignored)
	 * @param scy       resize height (if 0 is ignored)
	 * 
	 * @return  Bitmap Image
	 */
	public static Bitmap convertImage(byte[] baImage,int scx,int scy) {
		if (baImage!=null && baImage.length>0) {
			Bitmap image = BitmapFactory.decodeByteArray(baImage,0,baImage.length); 
			if (scx!=0 || scy!=0) {
				if (scx!=0 && scy!=0) {
					image = Bitmap.createScaledBitmap(image, scx, scy, false);
				} else if (scx!=0) {
					image = Bitmap.createScaledBitmap(image, scx, (int)Math.round(scy*scx/(float)image.getWidth()), false);
				} else if (scy!=0) {
					image = Bitmap.createScaledBitmap(image, (int)Math.round(scx*scy/(float)image.getHeight()), scy, false);
				} 
			}
			return image;
		} else { return null; }
	}
	
	/**
	 * Retrieve the administrative object icon from the OBEROn DB and resize it
	 * 
	 * @param sAdminType  administrative object type
	 * @param sAdminName  administrative object name
	 * @param scx			    resize width (if 0 is ignored)
	 * @param scy         resize height (if 0 is ignored)
	 * 
	 */
	public Bitmap getImage(String sAdminType,String sAdminName ,int scx , int scy ) {
		try {
			return convertImage(AdminBase.getImage(sAdminType,sAdminName),scx,scy);
		} catch (Exception ex) {
			return null;
		}
	}

	
	/**
	 * Retrieve the administrative object icon using the internal ID and resize it
	 * 
	 * @param iAdminId    the internal ID
	 * @param scx			    resize width (if 0 is ignored)
	 * @param scy         resize height (if 0 is ignored)
	 */
	public Bitmap getImage(int iAdminId ,int scx , int scy ) {
		try {
			return convertImage(AdminBase.getImage(iAdminId),scx,scy);
		} catch (Exception ex) {
			return null;
		}
	}

	/**
	 * Retrieve a object instance icon and resize it
	 * 
	 * @param sObjectID          the Object id
	 * @param scx			    resize width (if 0 is ignored)
	 * @param scy         resize height (if 0 is ignored)
	 */
	public Bitmap getObjImage(String sObjectID,int scx,int scy ) {
		try {
			return convertImage(ObjectObj.getImage(sObjectID),scx,scy);
		} catch (Exception ex) {
			return null;
		}		
	}
	
	
	//----------------------------------- DISPLAY & UTILITY METHODS ---------------------

	/**
	 * Display an Error Message for a thread Exception
	 * 
	 * @param exception		the thread Exception
	 * @param sTitle      the message box title
	 * @param postrun     runnable to be executed after confirmation
	 */
	public static void errorMessage(Activity activity, Exception exception,String sTitle,final Runnable postrun) {
		try {
			String sMessage=exception.getMessage();
			if (activity!=null) {
				if (sMessage==null) { sMessage = "No message"; }
				int iline=sMessage.indexOf(", on line");
				if (iline>0) sMessage=sMessage.substring(0,iline);
				new AlertDialog.Builder(activity)
				.setIcon(android.R.drawable.ic_dialog_alert)
				.setTitle(sTitle)
				.setCancelable(false)
				.setMessage(sMessage)
				.setPositiveButton("OK", new DialogInterface.OnClickListener() {  
					   public void onClick(DialogInterface dialog, int whichButton) {
					  	  if (postrun!=null) postrun.run();
					   }
				}).show();
				app.log(sMessage);
			} else { System.out.println(sMessage); }
		} catch (Exception ex) {}
	}

	/**
	 * Display a Message
	 * 
	 * @param sMessage		the showed message
	 * @param sTitle      the message box title
	 * @param postrun     runnable to be executed after confirmation
	 */
	public static void showMessage(Activity activity, String sMessage ,String sTitle,final Runnable postrun) {
		try {
			if (activity!=null) {
				if (sMessage==null) { sMessage = "No message"; }
				int iline=sMessage.indexOf(", on line");
				if (iline>0) sMessage=sMessage.substring(0,iline);
				new AlertDialog.Builder(activity)
				.setIcon(android.R.drawable.ic_dialog_info)
				.setTitle(sTitle)
				.setMessage(sMessage)
				.setCancelable(false)
				.setPositiveButton("OK", new DialogInterface.OnClickListener() {  
				   public void onClick(DialogInterface dialog, int whichButton) {
				  	  if (postrun!=null) postrun.run();
				   }
				}).show();
			} else { System.out.println(sMessage); }
		} catch (Exception ex) {}
	}	
	
	
	/**
	 * Add a dialog popup to the dialog-window registry
	 * 
	 * @param sName    the dialog window name
	 * @param dialog   the dialog view
	 */
	public static void addDialog(String sName,View dialog) {
		dialogs.put(sName, dialog);
	}

	/**
	 * Retrieve a dialog popup from the dialog-window registry
	 * 
	 * @param sName    the dialog window name
	 */
	public static View getDialog(String sName) {
		View dialog = (View)dialogs.get(sName);
		if (dialog!=null) {
			try {
				return dialog;
			} catch (Exception ex) {
			  dialogs.remove(sName);
			}
		} 
		return null;
	}
	
	/**
	 * Compose the object Class-Name-Revision string 
	 */
	public static String getClassNameRevision(ObjectObj object,ApplicationSession session) throws Exception{
		String sCNR = getTranslation(dictionary_section+"*",object.getClassName());
		return sCNR+" "+getNameRevision(object.getName(),object.getRevision());
	}
	
	/**
	 * Compose the Name-Revision string separated by name_revision_separator
	 */
	public static String getNameRevision(String sName,String sRevision) {
		sRevision=sRevision.trim();   
		if (sRevision.length()>0 && !sRevision.equals("-")) {
			return sName+name_revision_separator+sRevision;
		} else {
			return sName;
		}
	}

	/**
	 * Compose the link identification string as "Linktype From(Class/Name/Revision) -> To(Class/Name/Revision)"
	 * use the name_revision_separator between the object name and the revision
	 */
	public static String getLinkData(Link link,ApplicationSession session) throws Exception{
		String sCNRF = getTranslation(dictionary_section+"*",link.getFromClassName());
		if (link.getFromRevision().length()>0 && !link.getFromRevision().equals("-")) {
			sCNRF+=" "+link.getFromName()+name_revision_separator+link.getFromRevision();
		} else {
			sCNRF+=" "+link.getFromName();
		}
		String sCNRT = getTranslation(dictionary_section+"*",link.getToClassName());
		if (link.getToRevision().length()>0 && !link.getToRevision().equals("-")) {
			sCNRT+=" "+link.getToName()+name_revision_separator+link.getToRevision();
		} else {
			sCNRT+=" "+link.getToName();
		}
		String sType = getTranslation(dictionary_section+"*",link.getLinkType());
		return sType+" "+sCNRF+" -> "+sCNRT;
	}

	/**
	 * Translate a keyword searching in the Application Common dictionary-Section
	 */
	public static String getCommonTranslation(String key) {
		if (key!=null && key.length()>0) {
			try {
				return Dictionary.getKey(dictionary_common_section, key , getApp().framework);
			} catch (Exception ex) { return key; }
		} else { return key; }
	}

	/**
	 * Translate a keyword searching in the Application Menu dictionary-Section
	 */
	public static String getMenuTranslation(String key) {
		if (key!=null && key.length()>0) {
			try {
				return Dictionary.getKey(dictionary_menu_section, key , getApp().framework);
			} catch (Exception ex) { return key; }
		} else { return key; }

	}

	/**
	 * Translate a keyword searching in the Application dictionary-Section
	 */
	public static String getTranslation(String key) {
		if (key!=null && key.length()>0) {
			try {
				return Dictionary.getKey(dictionary_section, key , getApp().framework);
			} catch (Exception ex) { return key; }
		} else { return key; }

	}

	/**
	 * Translate a keyword searching in a specific dictionary-Section
	 */
	public static String getTranslation(String section,String key) {
		if (section!=null && section.length()>0 && key!=null && key.length()>0) {
			try {
				return Dictionary.getKey(section, key , getApp().framework);
			} catch (Exception ex) { return key; }
		} else { return key; }
	}
	
	/**
	 * Translate a keyword to a specific language, searching in a specific dictionary-Section
	 */
	public static String getTranslation(String language,String section,String key) {
		if (language!=null && language.length()>0 
				&& section!=null && section.length()>0 && key!=null && key.length()>0) {
			try {
				return Dictionary.getKey(language,section, key , getApp().framework);
			} catch (Exception ex) { return key; }
		} else { return key; }
	}
	
	/**
	 * Generate the application main Menu
	 * 
	 * @param menuName   the name of {@link Menu}
	 * @param resIds     resource Ids (menu_item layout,action icon,action text,empty/expand/expanded icon)
	 * @param imgIds     image Ids (no submenus, )     
	 */
	public ArrayAdapter<MenuItem> createMenu(final Context context, String menuName, final int[] resIds , final int[] imgIds) {   
		// Load application parameters
		Menu mainmenu =null;
		try {
			mainmenu = Menu.open(menuName,framework,null);			
			Vector<Feature> vFeatures = mainmenu.getFeatures(framework);
			if (vFeatures!=null) {
				for (int i=0;i<vFeatures.size();i++) {
					Feature feature = vFeatures.elementAt(i);
					if (feature.getTo().length()==0) {
						String sFeature = feature.getName();  
						if ( sFeature.equals(TAGS.APP_dictionary_menu_section)) dictionary_menu_section=feature.getValue();
						if ( sFeature.equals(TAGS.APP_dictionary_common_section)) dictionary_common_section=feature.getValue();
						if ( sFeature.equals(TAGS.APP_dictionary_section)) dictionary_section=feature.getValue();		
						if ( sFeature.equals(TAGS.APP_dictionary_default_language)) dictionary_default_language=feature.getValue();
						if ( sFeature.equals(TAGS.APP_show_object_class)) show_object_class=feature.getValue().equals("true");
						if ( sFeature.equals(TAGS.APP_show_object_namerev)) show_object_namerev=feature.getValue().equals("true");
						if ( sFeature.equals(TAGS.APP_use_search_patterns)) use_search_patterns=feature.getValue().equals("true");
						if ( sFeature.equals(TAGS.APP_number_separator_symbols)) number_separator_symbols=feature.getValue();
						if ( sFeature.equals(TAGS.APP_name_revision_separator)) name_revision_separator=feature.getValue();
						if ( sFeature.equals(TAGS.APP_search_all_label)) search_all_label=feature.getValue();
					}
				}
			}
			// Combines the dictionary sections for portfolio applications 
			if (mainmenu.isPortfolio()) {
				Vector<String> vMenuSections = StringUtils.StringTokensToVector(dictionary_menu_section, ",");
				Vector<String> vCommonSections = StringUtils.StringTokensToVector(dictionary_common_section, ",");
				Vector<String> vAppSections = StringUtils.StringTokensToVector(dictionary_section, ",");
				Vector<String> vApplications = mainmenu.getOwnSubMenus();
				for (int i=0;i<vApplications.size();i++) {
					String sApplicationName = vApplications.elementAt(i);
					try {
						Feature feature = Feature.open(TAGS.APP_dictionary_menu_section, "Menu",sApplicationName,"","", framework, null);
						if (vMenuSections.indexOf(feature.getValue())<0) {
							vMenuSections.add(feature.getValue());
						}
					} catch (Exception ex) {}
					try {
						Feature feature = Feature.open(TAGS.APP_dictionary_common_section, "Menu",sApplicationName,"","", framework, null);
						if (vCommonSections.indexOf(feature.getValue())<0) {
							vCommonSections.add(feature.getValue());
						}
					} catch (Exception ex) {}
					try {
						Feature feature = Feature.open(TAGS.APP_dictionary_section, "Menu",sApplicationName,"","", framework, null);
						if (vAppSections.indexOf(feature.getValue())<0) {
							vAppSections.add(feature.getValue());
						}
					} catch (Exception ex) {}					
				}			
				dictionary_menu_section=StringUtils.vectorToString(vMenuSections,",");
				dictionary_common_section=StringUtils.vectorToString(vCommonSections,",");
				dictionary_section=StringUtils.vectorToString(vAppSections,",");
			}
		} catch (Exception ex) {}
		setFramework(session,framework);
		if (framework.getLanguage().length()==0) {
			framework.setLanguage(dictionary_default_language);
		}
		session.setAttribute(Arg.Language , framework.getLanguage() );
		framework.emptyEnv();
		if (mainmenu!=null) {
			try {				
				// Get the list of MenuItems from database
				framework.setEnv(TAGS.APP_dictionary_menu_section, dictionary_menu_section);
				ArrayList<MenuItem> list = new ArrayList<MenuItem>( mainmenu.getActions(new Vector(0), app.framework) );
				// Remove all sub-levels [sub-menus are added inside the adapter]
				for ( int i = list.size()-1;i>=0;i-- ) {
					if ( list.get(i).getLevel()>0 ) list.remove(i);
				}

				// Prepare the adapter
				return new ArrayAdapter<MenuItem>(context,resIds[0],list ) {
					final ArrayAdapter<MenuItem> adapter = this;
					ViewHolder holder;
					public View getView( final int position, View convertView, ViewGroup parent )	 {						
						if (convertView == null) {
							// Inflate the menu item layout and save it into a cache
							LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
							convertView = vi.inflate(resIds[0], parent, false); // menu_item layout
							holder = new ViewHolder();
							holder.item1 = (ImageView) convertView.findViewById(resIds[1]); // action icon
							holder.item2 = (TextView)  convertView.findViewById(resIds[2]); // action text
							holder.item3 = (ImageView) convertView.findViewById(resIds[3]); // empty/expand/expanded icon
							convertView.setTag(holder);
						} else {
							// Retrieve the menu item layout from the cache
							holder=(ViewHolder)convertView.getTag();
						}

						final MenuItem item = getItem(position);						
						if (item != null) {
							// set the action icon
							holder.item1.setImageDrawable(new BitmapDrawable(getResources(),convertImage(item.getIcon(),0,0)));
							// set the action text (indented)
							String sSpacer= "";
							for (int i=0;i<item.getLevel();i++) sSpacer+="\t";
							holder.item2.setText(sSpacer+item.getTranslatedLabel());
							// set empty/expand/expanded icon
							if (item.getSubItems()==null) {
								holder.item3.setImageDrawable(context.getResources().getDrawable(imgIds[0])); // empty: no sub-items
							} else {
								if ( !item.isExpanded() ) {
									holder.item3.setImageDrawable(context.getResources().getDrawable(imgIds[1])); // expand 
								} else {
									holder.item3.setImageDrawable(context.getResources().getDrawable(imgIds[2])); // expanded
								}
							}
							convertView.setOnClickListener( new OnClickListener() {
								public void onClick(View v) {
									if ( item.getHRef().length()>0 ) 	{
										// Open the action href
										OnMenuItemClickListener listener = new ToolBarHandlerApp(item.getHRef());
										listener.onMenuItemClick(item);
									} else if (item.getSubItems()!=null) {
										// Expand / Compress the sub-items
										ArrayList<MenuItem> submenu = new ArrayList<MenuItem>(item.getSubItems());										
										int j = position+1;
										int iSize = adapter.getCount();
										for (int i=iSize-1;i>=0;i--) {
											MenuItem adpitem = adapter.getItem(i);
											if ( adpitem.getLevel()>item.getLevel() ) {
												adapter.remove(adpitem);
												if (j>i) j--;
											} else if ( adpitem.getLevel()==item.getLevel() ) {
												adpitem.setExpanded(false);
											}
										}
										for (int i=0;i<submenu.size();i++) {
											adapter.insert(submenu.get(i),j);
											j++;
										}
										item.setExpanded(true);
									}
								}
							});
						}					
						return convertView;
					}
				};			
			} catch (Exception ex) {}
		} 
		return new ArrayAdapter<MenuItem>(context,resIds[0]);
	}

	private static class ViewHolder{
    public ImageView item1;
    public TextView item2;
    public ImageView item3;
	}
	
	// Handle menu action HRef
	private class ToolBarHandlerApp implements MenuItem.OnMenuItemClickListener {	
		String sCommand;
		ToolBarHandlerApp(String sCommand){
			this.sCommand=sCommand;
		};
		public boolean onMenuItemClick(MenuItem item) {
			handleRequest(new ApplicationRequest(sCommand,(ApplicationSession)session));
			return false;
		}
	};
	
	/**
	 * Set custom property to Android view 
	 * 
	 * @param view     the Android view
	 * @param tag      the property name
	 * @param value    the property value
	 */
	public static void setData(android.view.View view,String tag,Object value) {
		Hashtable map = (Hashtable)view.getTag();
		if ( map ==null ) {
			 map = new Hashtable();
			 view.setTag(map);
		}
		if (value!=null) map.put(tag, value);
	}
	
	/**
	 * Get a custom property value from Android view
	 * @param view     the Android view
	 * @param tag      the property name
	 */
	public static Object getData(android.view.View view,String tag) {
		Hashtable map = (Hashtable)view.getTag();
		if ( map != null ) {
			 return map.get(tag);
		} else return null;
	}

	
	/**
	 * Open new PopUp window (dialog)
	 *  
	 * @param parent       the parent shell
	 * @param request      Application request with target url
	 * @param windowName   the dialog window name 
	 * @param style        the HTML window style (width,height,resizable,scrollable...)
	 * 
	 * @return  the dialog Shell
	 */
	public static AlertDialog windowOpen(View parent,ApplicationRequest request,String windowName,String style) {  	     
		LinearLayout popDialog = (LinearLayout)getDialog(windowName);			
		if (popDialog==null) {
			popDialog =new LinearLayout(parent.getContext());
			addDialog(windowName, popDialog);
			AlertDialog.Builder builder = new AlertDialog.Builder(parent.getContext());
			builder.setView(popDialog);		
			builder.setCancelable(false);
			AlertDialog winDialog = builder.create();
			winDialog.hide();
			setData(popDialog,TAGS.WINDOW,winDialog);
			setData(popDialog,TAGS.OPENER,getWindow(parent));
		} 
		Window window = ((AlertDialog)getData(popDialog,TAGS.WINDOW)).getWindow();		
		try {
			StringTokenizer sT = new StringTokenizer(style,",");
			int width=-1;
			int height=-1;
			int left=-1;
			int top=-1;
			while (sT.hasMoreTokens()) {
				String param = sT.nextToken().trim();
				StringTokenizer sTV=new StringTokenizer(param,"=");
				param=sTV.nextToken().trim();
				if (sTV.hasMoreTokens()) {
					String value = sTV.nextToken(); 
					if (param.equals("width")) {
						width=Math.min(Math.round(w_multiplier*Integer.parseInt(value)),screenWidth);
					} else  if (param.equals("height")) {
						height=Math.min(Math.round(h_multiplier*Integer.parseInt(value)),screenHeight);
					} else  if (param.equals("left")) {
						left=Integer.parseInt(value);
					} else  if (param.equals("top")) {
						top=Integer.parseInt(value);
					} else  if (param.equals("titlebar")) {
						setData(popDialog,TAGS.pagetitle,!(value.equals("no") || value.equals("0")));
					} else  if (param.equals("scrollbars")) {
						setData(popDialog,TAGS.scrollbars,!(value.equals("no") || value.equals("0")));
					}	 	
				} 
				if (width>0 && height>0) {
					window.setLayout(width, height);
				}		   
			}
			
			/*
			   SUPPORTED in Android	
			   titlebar=yes|no|1|0	Whether or not to display the title bar. Ignored unless the calling application is an HTML Application or a trusted dialog box. Default is yes
			   scrollbars=yes|no|1|0	Whether or not to display scroll bars. Default is yes		   
			
			   NOT SUPPORTED
			   resizable=yes|no|1|0	Whether or not to display window is resizable. Default is yes
			   toolbar=yes|no|1|0	Whether or not to display the browser toolbar. Default is yes
			   location=yes|no|1|0	Whether or not to display the address field. Default is yes       
			   status=yes|no|1|0	Whether or not to add a status bar. Default is yes
			*/
			
			WindowManager.LayoutParams wmlp = window.getAttributes();
			if (left>=0) {
				wmlp.gravity = Gravity.LEFT;
				wmlp.x=left;
			}
			if (top>=0) {
				if (left>=0)	wmlp.gravity = Gravity.LEFT | Gravity.TOP ; else wmlp.gravity = Gravity.TOP;
				wmlp.y=top;
			}
			window.setAttributes(wmlp);

			setData(popDialog,TAGS.REQUEST,request);
			setData(popDialog,TAGS.PARAMS,style);
			request.setReferer(parent);
			request.setTarget(windowName);
			getApp().handleRequest(request);

		} catch (Exception ex) {}
		return (AlertDialog)getData(popDialog,TAGS.WINDOW);
	}

	public static View getWindow(View view) {
		while ( view!=null && getData(view,TAGS.WINDOW)==null ) {
			view = (View)view.getParent();
		}	
		return view;
	}
	
	/**
	 * Show a message in the application Log panel or in the status bar 
	 */
	public abstract void log(String message);
	
	/**
	 * Handle an Application request
	 *
	 * @return true when the request page is recognized as valid page
	 */
	public abstract boolean handleRequest(ApplicationRequest request);
	
	
	/**
	 * Convert the language code from the Application format to standard ISO code
	 */
	public abstract String translateOberonLanguageToISO639Language(String language);
			
}

