/*
 * Copyright ©.
 */
package com.fgm.web.menu.util;

import java.lang.reflect.Method;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.HashMap;

import com.fgm.web.menu.MenuComponent;

/**
 * Utility class to generate object of type MenuComponent
 * e.g
 * <code>
 *      MenuComponentBuilder mcb = new MenuComponentBuilder();
 *
 *      Map repo = new HashMap();
 *      repo.put(mcb.OBJECT, root); // root is the object from which menu should be generated
 *      repo.put(mcb.NAME, "rootTree");
 *      repo.put(mcb.TITLE, "getTitle");
 *      repo.put(mcb.TOOLTIP, "getTitle");
 *      repo.put(mcb.SUBELEMENTS, "getSub");
 *      
 *      Map params = new HashMap();
 *      params.put("id", "getId");
 *      repo.put(mcb.LINK_PARAMS, params);
 *      
 *      MenuComponent mc = mcb.buildMenuComponent(repo);
 * </code>
 *  
 * @author <a href="raahul80@hotmail.com">Rahul Kulkarni</a>
 * @version $Revision$
 */
public class MenuComponentBuilder {
    
    /**
     * Object to be converted as MenuComponent
     */
    public static final String OBJECT      = "object";
    /**
     * Name property for generated MenuComponent Object
     */
    public static final String NAME        = "menu.name";
    
    /**
     * Method name to obtain title
     */
    public static final String TITLE       = "method.title";
    /**
     * OPTIONAL : Method name to obtain tooltip
     */
    public static final String TOOLTIP     = "method.tooltip";
    /**
     * OPTIONAL : Method name to obtain sub elements 
     */
    public static final String SUBELEMENTS = "method.sub";
    /**
     * OPTIONAL : Which methods return value should be added in generated link 
     */
    public static final String LINK_PARAMS = "link.params";

	public static final String STRUTS_ACTION = "struts.action";
    
    private static final String ITERATOR_CLASS_NAME = "java.util.Iterator";
    private static final String ITERATOR_METHOD     = "iterator";
    private static final String EMPTY               = "";
    
    //////////////////////////////////////////////////////////////////////////
    ///       I N S T A N C E     V A R I A B L E S                       ///
    //////////////////////////////////////////////////////////////////////////
    
    private StringBuffer sb       = null;
    private int nameCount         = -1;
    
    private String rootMenuName   = null;	
    
    private String getTitle       = null;
    private String getTooltip     = null;
    private String getSubElements = null;
    
	private String strutsAction   = null;
    private String paramIds[]     = null;
    private String paramMethods[] = null; 
    
	public MenuComponentBuilder() {
	}
	
    /**
     * Param repository must have following strutcute
     * 
     * repository Map key must be one of the staic final varible of this class
     * 
     * Value for LINK_PARAM should be Map; which has paramId as key and accesor
     * method name as value
     * 
     * @param repository
     * @return 
     */
	public MenuComponent buildMenuComponent(Map repository) {
		
        Object rootObj     = repository.get(OBJECT);
        MenuComponent root = null;
		
        if ( rootObj != null ) {
            this.rootMenuName   = getMapValue(repository, NAME);
            this.sb             = new StringBuffer();
            this.nameCount      = -1;
            
            this.getTitle       = getMapValue(repository, TITLE);
            this.getTooltip     = getMapValue(repository, TOOLTIP);
            this.getSubElements = getMapValue(repository, SUBELEMENTS);
			this.strutsAction   = getMapValue(repository, STRUTS_ACTION);
            
            Map paramsMap = (Map)repository.get(LINK_PARAMS);
            if (paramsMap != null) {
                
                this.paramIds     = new String[paramsMap.size()];
                this.paramMethods = new String[paramsMap.size()];
                Iterator it       = paramsMap.keySet().iterator();
                
                for( int i = 0; it.hasNext(); i++ ) {
                    paramIds[i]     = (String)it.next();
                    paramMethods[i] = (String)paramsMap.get(paramIds[i]);
                }
            }
            
            root = getMenuComponent(rootObj);
            root.setName(rootMenuName);
        }
        
		return root;
	}
    
    private MenuComponent getMenuComponent(Object obj) {
        MenuComponent menu = null;
        
        try {
            Class clazz     = obj.getClass();
            String menuName = getNextMenuName();
            Object title    = getMethodValue(obj, clazz, getTitle);
            Object tooltip  = getMethodValue(obj, clazz, getTooltip);
            Map params      = buildParams(obj, clazz);
            
            menu = new MenuComponent();
			menu.setBaseObject(obj);
            menu.setName(menuName);
            menu.setTitle(title == null ? EMPTY : title.toString() );
            menu.setToolTip(tooltip == null ? EMPTY : tooltip.toString());
            menu.setParams(params);
            
            Object sub = getMethodValue(obj, clazz, getSubElements);
            if ( sub != null ) {
                Iterator subIt = null;
                if ( isIteratorObject(sub) ) {
                    subIt = (Iterator)sub;
                } else {
                    subIt = (Iterator)getMethodValue(sub, sub.getClass(), ITERATOR_METHOD); 
                }
                
                if (subIt != null) {
                    while (subIt.hasNext()) {
                        MenuComponent subMenu = getMenuComponent(subIt.next());
                        menu.addMenuComponent(subMenu);
                    }
                }
            }
        } catch (Exception ex) {
        }
        
        return menu;
    }

    private Map buildParams(Object obj, Class clazz) {
        Map paramsMap = null;
        
        if ( paramIds != null && paramIds.length > 0 ) {
            paramsMap = new HashMap(paramIds.length);
            
            for( int i = 0; i < paramIds.length; i++ ) {
                paramsMap.put(paramIds[i], getMethodValue(obj, clazz, paramMethods[i]));
            }
        }
        
        return paramsMap;
    }
    
    private String getNextMenuName() {
        
        sb.setLength(0);
        sb.append(rootMenuName).append(++nameCount);
        
        return sb.toString();
    }
    
    private static Object getMethodValue(Object obj, Class clazz, String methodName) {
        Object value = null;
        
        try {
            int subMethodIndex = methodName.indexOf('.');
            
            if ( subMethodIndex > 0 ) {
                String firstHalf = methodName.substring(0, subMethodIndex);
                String laterHalf = methodName.substring(subMethodIndex+1, methodName.length());
                
                Object o = getMethodValue(obj, clazz, firstHalf);
                value    = getMethodValue(o, o.getClass(), laterHalf);
            } else {
                Method method = clazz.getMethod(methodName, null);
                method.setAccessible(true);
                
                value = method.invoke(obj, null);
            }
                        
        } catch (Exception ex) {
        }
        
        return value;
    }
    
    /**
     * This method will find out if the current object is implementation 
     * of a <code>java.util.Iterator</code>
     * 
     * @param obj
     * @return 
     */
    private static boolean isIteratorObject(Object obj) {
        boolean isIterator = false;
        
        Class interfaces[] = obj.getClass().getInterfaces();
        if ( interfaces != null && interfaces.length > 0 ) {
            for ( int i = 0; i < interfaces.length; i++) {
                if (interfaces[i].getName().equals(ITERATOR_CLASS_NAME)) {
                    isIterator = true;
                    break;
                }
            }
        }
        
        return isIterator;
    }
    
    /**
     * Returns value of map for a specific key as String
     *
     * @param map Map from which value should be extracted
     * @param key Key to access object inside Map
     * @return String representation fo value represented by the key
     */
    private String getMapValue(Map map, String key) {
        String returnVal = null;
        
        if ( map != null ) {
            returnVal = (String)map.get(key);
        }
        
        return returnVal;
    }
}
