/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 ro.nextreports.server.web.report;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.datetime.markup.html.form.DateTextField;
import org.apache.wicket.extensions.markup.html.form.palette.Palette;
import org.apache.wicket.extensions.markup.html.form.palette.component.Recorder;
import org.apache.wicket.extensions.yui.calendar.DateField;
import org.apache.wicket.extensions.yui.calendar.DatePicker;
import org.apache.wicket.extensions.yui.calendar.DateTimeField;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.IChoiceRenderer;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.EmptyPanel;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.nextreports.engine.Report;
import ro.nextreports.engine.i18n.I18nLanguage;
import ro.nextreports.engine.queryexec.IdName;
import ro.nextreports.engine.queryexec.QueryParameter;
import ro.nextreports.engine.util.DateUtil;
import ro.nextreports.engine.util.ParameterUtil;
import ro.nextreports.engine.util.StringUtil;
import ro.nextreports.server.domain.DataSource;
import ro.nextreports.server.domain.ReportRuntimeParameterModel;
import ro.nextreports.server.report.next.NextRuntimeParameterModel;
import ro.nextreports.server.service.DataSourceService;
import ro.nextreports.server.service.ReportService;
import ro.nextreports.server.service.StorageService;
import ro.nextreports.server.util.ServerUtil;
import ro.nextreports.server.web.common.misc.ExtendedPalette;

/**
 * User: mihai.panaitescu
 * Date: 02-Feb-2010
 * Time: 13:17:03
 */
public abstract class ParameterRuntimePanel extends Panel {

	private static final long serialVersionUID = 1L;
	
	public static final String USER_PARAM = "__USER__";
	
	@SpringBean
    protected StorageService storageService;
	
	@SpringBean
    protected ReportService reportService;

	@SpringBean
    protected DataSourceService dataSourceService;

    protected ParameterRuntimeModel runtimeModel;
    protected List<QueryParameter> paramList;
    private Map<String, QueryParameter> paramMap;
    private Map<QueryParameter, Component> paramComponentsMap;

    protected boolean runNow;    
    private String errorMessage;

    // In Next, for source parameters default values are not the entire objects (id, name), but only the ids
    // so we will have to look at selection if the default values can be found in the list of the parameter values
    // and only if we found them we add them to the model

    // all dependent parameters that must be initialized after completing default values
    private transient List<QueryParameter> depParameters = new ArrayList<QueryParameter>();
    // because dependent parameter values where not completed, also the default dependent values where not completed
    // and must be kept for further initialization
    private transient Map<QueryParameter, List<Serializable>> depDefValues = new HashMap<QueryParameter, List<Serializable>>();
    
    private static final Logger LOG = LoggerFactory.getLogger(ParameterRuntimePanel.class);

    public ParameterRuntimePanel(String id) {
        this(id, true);
    }

    public ParameterRuntimePanel(String id, boolean runNow) {
        super(id);
        this.runNow = runNow;
    }

    public ParameterRuntimePanel(String id, ParameterRuntimeModel runtimeModel) {
        super(id);
        this.runNow = true;
        init(runtimeModel);
    }

    public abstract void addWicketComponents();

    public abstract Report getNextReport();
    
    public abstract I18nLanguage getLocaleLanguage();

    public abstract DataSource getDataSource();

    protected void init(ParameterRuntimeModel runtimeModel) {
        this.runtimeModel = runtimeModel;

        paramMap = ParameterUtil.getUsedNotHiddenParametersMap(getNextReport());
        paramList = new ArrayList<QueryParameter>(paramMap.values());
        paramComponentsMap = new HashMap<QueryParameter, Component>();

        addComponents();
        setOutputMarkupId(true);
    }
    
    protected void init(ParameterRuntimeModel runtimeModel, boolean fromGlobalModel) {
        this.runtimeModel = runtimeModel;

        paramMap = ParameterUtil.getUsedNotHiddenParametersMap(getNextReport());
        
        // global settings : we may have less parameters (only common parameters)
        if (fromGlobalModel) {           	
        	List<String> keys = new ArrayList<String>();
        	for (Iterator it=runtimeModel.getParameters().keySet().iterator(); it.hasNext();) {        		        		
        		keys.add((String)it.next());
        	}
        	for (Iterator it = paramMap.keySet().iterator(); it.hasNext();) {
        		if (!keys.contains((String)it.next())) {
        			it.remove();
        		}
        	}
        } 
        paramList = new ArrayList<QueryParameter>(paramMap.values());
        paramComponentsMap = new HashMap<QueryParameter, Component>();

        addComponents();
        setOutputMarkupId(true);
    }

    private void addComponents() {
    	    	
    	// initialize model for some hidden hard-coded parameters
    	// there is possible that a report can contain only hidden hard-coded parameters!
    	for (QueryParameter parameter : ParameterUtil.getUsedHiddenParametersMap(getNextReport()).values()) {			
			if ((USER_PARAM.equals(parameter.getName()))) {
				runtimeModel.getParameters().put(parameter.getName(), createRuntimeModel(parameter));			    
			}
		}
    	
    	// initialize model for all not hidden parameters
		for (QueryParameter parameter : paramList) {			
			if (!runtimeModel.isEdit()) {
				runtimeModel.getParameters().put(parameter.getName(), createRuntimeModel(parameter));			    
			}
		}
        
        if (!runtimeModel.isEdit() && (errorMessage == null)) {
            // if some parameters initialized have default values, their dependent parameters
            // have to be initialized too
            for (QueryParameter qp : depParameters) {
                populateDependentParameters(qp, null, true);
            }
        }

        ListView<QueryParameter> listView = new ListView<QueryParameter>("params", new ArrayList<QueryParameter>(paramMap.values())) {

			private static final long serialVersionUID = 1L;

			@Override
            protected void populateItem(ListItem<QueryParameter> item) {
                createItem(item);
            }

        };
        listView.setReuseItems(true);
        add(listView);

        addWicketComponents();
        
        if (errorMessage != null) {
        	error(errorMessage);
        }
    }

    @SuppressWarnings("unchecked")
    protected void createItem(ListItem<QueryParameter> item) {
        Component currentComponent = null;
        WebMarkupContainer paletteContainer = new WebMarkupContainer("paletteContainer");

        final QueryParameter parameter = item.getModelObject();

        final IModel generalModel = new PropertyModel(runtimeModel.getParameters(), parameter.getName() + ".rawValue");
        IModel listModel = new PropertyModel(runtimeModel.getParameters(), parameter.getName() + ".valueList");
        
        if (runtimeModel.isEdit()) {
            populateDependentParameters(parameter, null, true);
        }

        final TextField textField = new TextField("txtValue", generalModel);
        textField.setVisible(false);
        try {
            textField.setType(Class.forName(parameter.getValueClassName()));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            LOG.error(e.getMessage(), e);
            error(e.getMessage());
        }
       
        final DateTimeField txtTime = new DateTimeField("txtTime", generalModel) {

			private static final long serialVersionUID = 1L;

			@Override
            public IModel<String> getLabel() {
                return new Model<String>(getParameterName(parameter));
            }

            @Override
			protected boolean use12HourFormat() {
				return false;
			}
            
            @Override
			protected DateTextField newDateTextField(String id, PropertyModel dateFieldModel) {
				DateTextField f = super.newDateTextField(id, dateFieldModel);				
				// Important must create a new ajaxUpdate behavior (otherwise an error will rise)
				// DateTextField uses newDateTextField method in constructor (DateField uses it in onBeforeRenderer method)
				// that's why for DateField is ok to use the same ajax which is added when component is made visible
				// for DateTextField ajax behavior is added even if the component is not visible!
				f.add(createAjax(parameter, generalModel, f));				
				return f;
			}
            
            protected DatePicker newDatePicker() {
        		return new DatePicker() {
        			private static final long serialVersionUID = 1L;

        			@Override
        			protected void configure(final Map<String, Object> widgetProperties,
        				final IHeaderResponse response, final Map<String, Object> initVariables) {
        				super.configure(widgetProperties, response, initVariables);        				
        			}

					@Override
					protected boolean enableMonthYearSelection() {
						return true;
					}        			        			
        		};
        	}

        };  
        // add ajax update behavior on hours and minutes textfields 
        txtTime.get("hours").add(createAjax(parameter, generalModel, txtTime.get("hours"), "hours"));
        txtTime.get("minutes").add(createAjax(parameter, generalModel, txtTime.get("minutes"), "minutes"));
        txtTime.setVisible(false);
        
        final DateField txtDate = new DateField("txtDate", generalModel) {

			private static final long serialVersionUID = 1L;

			@Override
            public IModel<String> getLabel() {
                return new Model<String>(getParameterName(parameter));
            }    
			
			@Override
			protected DateTextField newDateTextField(java.lang.String id, PropertyModel dateFieldModel) {				
				DateTextField f = super.newDateTextField(id, dateFieldModel);												
				f.add(createAjax(parameter, generalModel, f));
				return f;
			}
			
			protected DatePicker newDatePicker() {
        		return new DatePicker() {
        			private static final long serialVersionUID = 1L;

        			@Override
        			protected void configure(final Map<String, Object> widgetProperties,
        				final IHeaderResponse response, final Map<String, Object> initVariables) {
        				super.configure(widgetProperties, response, initVariables);        				
        			}

					@Override
					protected boolean enableMonthYearSelection() {
						return true;
					}        			        			
        		};
        	}

        };        
        txtDate.setVisible(false);

        final CheckBox chkBox = new CheckBox("chkBox", generalModel);
        chkBox.setVisible(false);

        DropDownChoice downChoice = new DropDownChoice("cmbValue", generalModel, new ArrayList<String>());
        downChoice.setVisible(false);

        if (parameter.getSelection().equalsIgnoreCase(QueryParameter.SINGLE_SELECTION)) {
            if (parameter.getSource() != null && parameter.getSource().trim().length() > 0) {
                // combo
                downChoice = new DropDownChoice("cmbValue", generalModel, new LoadableDetachableModel() {

                    @Override
                    protected Object load() {
                        // for combo default value can be a simple value or selected for a source (is not an IdName so we make it)
                        // a default value from a manual source is an IdName
                        Object obj = generalModel.getObject();
                        if ((obj != null) && !(obj instanceof IdName) ) {
                            IdName in  = new IdName();
                            in.setId((Serializable)obj);
                            in.setName((Serializable)obj);
                            generalModel.setObject(in);                            
                        }
                        return runtimeModel.getParameters().get(parameter.getName()).getValues();
                    }

                });
                if (!parameter.isHidden()) {
                    if (parameter.isMandatory()) {
                        downChoice.setRequired(true);
                    }
                    downChoice.setLabel(new Model<String>(getParameterName(parameter)));
                    downChoice.setVisible(true);
                }
                // if parameter is not mandatory, even if we selected something by default, we should allow user to select null values
                if (!parameter.isMandatory()) {                
                	downChoice.setNullValid(true);
                }
                currentComponent = downChoice;
            } else {
                // not combo
                if (parameter.getValueClassName().contains("Date")) {
                	if (!parameter.isHidden()) {
                        if (generalModel.getObject() == null) {
                            generalModel.setObject(DateUtil.floor(new Date()));                                                        
                        }
                        if (parameter.isMandatory()) {
                        	txtDate.setRequired(true);
                        }
                        txtDate.setVisible(true);
                    }                	
                    currentComponent = txtDate;
                } else if(parameter.getValueClassName().contains("Timestamp") ||
                    parameter.getValueClassName().contains("Time")    ) {
                    if (!parameter.isHidden()) {
                        if (generalModel.getObject() == null) {
                            generalModel.setObject(DateUtil.floor(new Date()));                                                        
                        }
                        if (parameter.isMandatory()) {
                        	txtTime.setRequired(true);
                        }
                        txtTime.setVisible(true);
                    }
                    currentComponent = txtTime;
                } else if (parameter.getValueClassName().contains("Boolean")) {
                    if (!parameter.isHidden()) {
                        if (parameter.isMandatory()) {
                            chkBox.setRequired(true);
                        }
                        chkBox.setLabel(new Model<String>(getParameterName(parameter)));
                        chkBox.setVisible(true);
                    }
                    currentComponent = chkBox;
                } else {
                    if (!parameter.isHidden()) {
                        if (parameter.isMandatory()) {
                            textField.setRequired(true);
                        }
                        textField.setLabel(new Model<String>(getParameterName(parameter)));
                        textField.setVisible(true);
                    }
                    currentComponent = textField;
                }
            }

            paletteContainer.add(new EmptyPanel("palette"));
        } else {
            if (parameter.getSource() != null && parameter.getSource().trim().length() > 0) {
                if (!parameter.isHidden()) {
                    Palette palette = createPalette(parameter, listModel, createAjax(parameter));
                    paletteContainer.add(palette.setOutputMarkupId(true));
                    currentComponent = palette;                    
                } else {
                    paletteContainer.add(new EmptyPanel("palette"));
                }
            } else {
                ManualListPanel list = new ManualListPanel(parameter, listModel, 10, createAjax(parameter));
                paletteContainer.add(list.setOutputMarkupId(true));
                currentComponent = list;
            }

        }

        paramComponentsMap.put(parameter, currentComponent);

        // if this parameter has dependent parameters
        // we must update values for those using an ajax update
        // for Palette this is done in its class
        // for DateField and DateTimeField is done on the inner DateTextField        
        if (ParameterUtil.getChildDependentParameters(getNextReport(), parameter).size() > 0) {
        	boolean ajaxAlreadyAdded = (currentComponent instanceof Palette) ||
			   (currentComponent instanceof DateField) ||
			   (currentComponent instanceof DateTimeField);        	        	
        	if (!ajaxAlreadyAdded) {                  		
                currentComponent.add(createAjax(parameter));
            }
        }

        String name = getDisplayableParameterName(parameter);
        Label lbl = new Label("name", name);
        lbl.setEscapeModelStrings(false);
        lbl.setOutputMarkupId(true);
        item.add(lbl);
        txtTime.setOutputMarkupId(true);
        item.add(txtTime);
        txtDate.setOutputMarkupId(true);
        item.add(txtDate);
        downChoice.setOutputMarkupId(true);
        item.add(downChoice);
        paletteContainer.setOutputMarkupId(true);
        item.add(paletteContainer);
        textField.setOutputMarkupId(true);
        item.add(textField);
        chkBox.setOutputMarkupId(true);
        item.add(chkBox);
    }

    private NextRuntimeParameterModel createRuntimeModel(QueryParameter parameter) {
        boolean isMultipleSelection = parameter.getSelection().equalsIgnoreCase(QueryParameter.MULTIPLE_SELECTION);
        NextRuntimeParameterModel runtimeModel = new NextRuntimeParameterModel(parameter.getName(), getParameterName(parameter), isMultipleSelection);
        runtimeModel.setMandatory(parameter.isMandatory());

        List<IdName> values = new ArrayList<IdName>();
        // set in the model only the values for parameters which are not dependent
        if ((errorMessage == null) && (parameter.getSource() != null) && (parameter.getSource().trim().length() > 0)
                && !parameter.isDependent()) {
            try {
                values = dataSourceService.getParameterValues(getDataSource(), parameter);
            } catch (Exception e) {
                e.printStackTrace();
                errorMessage = "Get parameter values for : " + parameter.getName() + "  > " + e.getMessage();
                LOG.error(errorMessage, e);
                error(e.getMessage());                
            }
        }
        runtimeModel.setParameterValues(values);                

        if (errorMessage == null) {
        	initDefaultValues(runtimeModel, parameter, values);
        }

        return runtimeModel;
    }

    private void initDefaultValues(NextRuntimeParameterModel runtimeModel, QueryParameter parameter, List<IdName> values) {
        List<Serializable> defaultValues = new ArrayList<Serializable>();
        if ((parameter.getDefaultValues() != null)) {
            defaultValues = parameter.getDefaultValues();
        }
        if ((parameter.getDefaultSource() != null) && (parameter.getDefaultSource().trim().length() > 0)) {
            try {
                defaultValues = dataSourceService.getDefaultSourceValues(getDataSource(), parameter);
            } catch (Exception e) {
                e.printStackTrace();
                errorMessage = "Get default source values for parameter : " + parameter.getName() + "  > " + e.getMessage();
                LOG.error(errorMessage, e);
                error(e.getMessage());
            }
        }

        depDefValues.put(parameter, defaultValues);
        if (defaultValues.size() == 0) {
            return;
        }

        // for source parameters, the values are not entire objects (id, name) but only the ids
        // so we have to look in the parameter values for them
        if ((parameter.getSource() != null) && !parameter.getSource().trim().equals("")) {
            defaultValues = getSelectedValues(values, defaultValues);
        }
        if (defaultValues.size() == 0) {
            return;
        }
        boolean populateDependent = false;
        if  (USER_PARAM.equals(parameter.getName())) {
            runtimeModel.setRawValue(ServerUtil.getUsernameWithoutRealm());
            populateDependent  = true;
		} else {
			if (QueryParameter.MULTIPLE_SELECTION.equals(parameter.getSelection())) {
				if (defaultValues.size() == 0) {
					runtimeModel.setValueList(new ArrayList<Object>());
				} else {
					ArrayList<Object> list = new ArrayList<Object>();
					list.addAll(defaultValues);
					runtimeModel.setValueList(list);
					populateDependent = true;
				}
			} else {
				if (defaultValues.size() > 0) {
					runtimeModel.setRawValue(defaultValues.get(0));
					populateDependent = true;
				}
			}
		}
        // mark the dependent parameters that must be populated after initilize the default values
        if (populateDependent) {
            this.runtimeModel.getParameters().put(parameter.getName(), runtimeModel);
            depParameters.add(parameter);
        }
    }

    // a default value must be simple java object , or an IdName but only with the id
    private List<Serializable> getSelectedValues(List<IdName> values, List<Serializable> defaultValues) {
        List<Serializable> selectedValues = new ArrayList<Serializable>();
        if (defaultValues == null) {
            return selectedValues;
        }
        for (Serializable s : defaultValues) {
            for  (IdName in : values) {
                if (s instanceof IdName) {
                    if ((in.getId() != null) && in.getId().equals( ((IdName)s).getId())) {
                        selectedValues.add(in);
                        break;
                    }
                } else if ((in.getId() != null) && in.getId().equals(s)) {
                    selectedValues.add(in);
                    break;
                }
            }
        }
        
        return selectedValues;
    }

     private List<Object> getSelectedValuesAsObject(List<Serializable> defaultValues) {
        List<Object> selectedValues = new ArrayList<Object>();
        for (Serializable s  : defaultValues) {
            selectedValues.add(s);
        }
        
         return selectedValues;
     }

    private AjaxFormComponentUpdatingBehavior createAjax(final QueryParameter parameter) {
        return new AjaxFormComponentUpdatingBehavior("onchange") {

            @Override
            protected void onUpdate(AjaxRequestTarget target) {
                populateDependentParameters(parameter, target, false);
            }

        };
    }
    
    private AjaxFormComponentUpdatingBehavior createAjax(final QueryParameter parameter, final IModel model, final DateTextField dateField) {
        return new AjaxFormComponentUpdatingBehavior("onchange") {

            @SuppressWarnings("unchecked")
			@Override
            protected void onUpdate(AjaxRequestTarget target) {
            	// @todo wicket 1.5 does not update model for DateField and DateTimeField
                // https://issues.apache.org/jira/browse/WICKET-4496	
                // use this as an workaround	
				model.setObject(dateField.getDefaultModelObject());
				
                populateDependentParameters(parameter, target, false);
            }

        };
    }
    
    // @todo wicket 1.5
    // used to update hours and minutes
    private AjaxFormComponentUpdatingBehavior createAjax(final QueryParameter parameter, final IModel model, final Component component, final String time) {
        return new AjaxFormComponentUpdatingBehavior("onchange") {

            @SuppressWarnings("unchecked")
			@Override
            protected void onUpdate(AjaxRequestTarget target) {
            	// @todo wicket 1.5 does not update model for DateField and DateTimeField
                // https://issues.apache.org/jira/browse/WICKET-4496	
                // use this as an workaround	
            	if ((model == null) || (model.getObject() == null)) {
            		return;
            	}
            	Date date = (Date)model.getObject();
            	if ("hours".equals(time)) {
            		date = DateUtil.setHours(date, (Integer)component.getDefaultModelObject());
            	} else if ("minutes".equals(time)) {
            		date = DateUtil.setMinutes(date, (Integer)component.getDefaultModelObject());
            	}            	
				model.setObject(date);
				
                populateDependentParameters(parameter, target, false);
            }

        };
    }

    private void populateDependentParameters(QueryParameter parameter, AjaxRequestTarget target, boolean recursive) {
        Report nextReport = getNextReport();
        if (nextReport == null) {
        	return;
        }
        Map<String, QueryParameter> childParams = ParameterUtil.getChildDependentParameters(nextReport, parameter);        

        // update model parameter values for every child parameter
        for (QueryParameter childParam : childParams.values()) {        	
            if (!paramList.contains(childParam)) {
                continue;
            }

            Component childComponent = paramComponentsMap.get(childParam);

            List<IdName> values = new ArrayList<IdName>();
            boolean allParentsHaveValues = true;

            Map<String, QueryParameter> allParentParams = ParameterUtil.getParentDependentParameters(nextReport, childParam);
            for (QueryParameter parentParam : allParentParams.values()) {            	
                if (runtimeModel.getParameters().get(parentParam.getName()).getProcessingValue() == null) {
                    allParentsHaveValues = false;
                    break;
                }
            }

            if ((childParam.getSource() != null) && (childParam.getSource().trim().length() > 0)
                    && allParentsHaveValues) {
                try {
                    Map<String, Serializable> allParameterValues = new HashMap<String, Serializable>();

                    for (String name : runtimeModel.getParameters().keySet()) {
                        ReportRuntimeParameterModel model = runtimeModel.getParameters().get(name);
                        allParameterValues.put(model.getName(), (Serializable) model.getProcessingValue());
                    }
                    
                    //System.out.println("!!!! map = " + ParameterUtil.getDebugParameters(allParameterValues));
                    
                    values = dataSourceService.getDependentParameterValues(
                            getDataSource(), childParam, paramMap, allParameterValues);
                } catch (Exception e) {
                    e.printStackTrace();
                    LOG.error("Get dependent parameter values for : " + childParam.getName() + "  > " + e.getMessage(), e);
                    error(e.getMessage());
                }
            }

            NextRuntimeParameterModel parameterModel = (NextRuntimeParameterModel) runtimeModel.getParameters().get(childParam.getName());

            // if nothing is selected for that parameter (see scheduler) we look for the default values
            if ((parameterModel.getValueList() == null) || (parameterModel.getValueList().size() == 0)) {
                List<Serializable> list = getSelectedValues(values, depDefValues.get(childParam));
                if (list.size() > 0) {
                    parameterModel.setValueList(getSelectedValuesAsObject(list));
                }
            }
            if (values != null && values.size() > 0) {
                parameterModel.setParameterValues(values);
            } else {
                parameterModel.setParameterValues(new ArrayList<IdName>());
            }

            if (target != null) {
                target.add(childComponent);
            }

            if (recursive) {
                populateDependentParameters(childParam, target, true);
            }
        }
    }

    @SuppressWarnings("unchecked")
    private Palette createPalette(QueryParameter parameter, IModel listModel, AjaxFormComponentUpdatingBehavior ajaxUpdate) {
        ParameterPalette parameterPalette = new ParameterPalette(parameter,  listModel, 10);
        parameterPalette.setUpdatingBehavior(ajaxUpdate);
        
        return parameterPalette;
    }

    private String getDisplayableParameterName(QueryParameter parameter) {
        if (parameter.isHidden()) {
            return "";
        }
        String name = getParameterName(parameter);
        if (parameter.isMandatory()) {
            name += "&nbsp;<em>*</em>";
        }

        return name;
    }

    private String getParameterName(QueryParameter parameter) {
        String name = parameter.getRuntimeName();
        if ((name == null) || name.trim().equals("")) {
            name = parameter.getName();
        } else {
        	name = StringUtil.getI18nString(name,getLocaleLanguage());  
        }        
        return name;
    }    
        
    class ParameterPalette extends ExtendedPalette<Object> {
    	
		private static final long serialVersionUID = 1L;
		
		private QueryParameter parameter;
		private AjaxFormComponentUpdatingBehavior updatingBehavior;

		public ParameterPalette(QueryParameter parameter, IModel<List<Object>> listModel, int rows) {
    		//super("palette", listModel,  new ParameterChoicesModel(parameter), new ParameterChoiceRenderer(), rows, false);
    		super("palette", listModel,  new ParameterChoicesModel(parameter), new ParameterChoiceRenderer(), rows, false, true);
    		this.parameter = parameter;
    	}
    	
		public void setUpdatingBehavior(AjaxFormComponentUpdatingBehavior updatingBehavior) {
			this.updatingBehavior = updatingBehavior;
		}
		        
		@Override
        protected Recorder<Object> newRecorderComponent() {
            Recorder<Object> recorder = super.newRecorderComponent();
            String paramaterName = getParameterName(parameter);
            recorder.setLabel(new Model<String>(paramaterName));
            
            if (!parameter.isHidden()) {
                if (parameter.isMandatory()) {
                    recorder.setRequired(true);
                }
            }

            if (ParameterUtil.getChildDependentParameters(getNextReport(), parameter).size() > 0) {
                recorder.add(updatingBehavior);
            }

            return recorder;
        }

    }
    
    class ParameterChoicesModel extends LoadableDetachableModel<List<IdName>> {
    	
		private static final long serialVersionUID = 1L;
		
		private QueryParameter parameter;
    	
    	public ParameterChoicesModel(QueryParameter parameter) {
    		this.parameter = parameter;
    	}
    	
        @Override
        protected List<IdName> load() {
        	String parameterName = parameter.getName();
        	List<IdName> list = runtimeModel.getParameters().get(parameterName).getValues();
        	
            return list.size() > 0 ? list : new ArrayList<IdName>();
        }

    }
    
    class ParameterChoiceRenderer implements IChoiceRenderer<Object> {
    	
		private static final long serialVersionUID = 1L;

		public Object getDisplayValue(Object object) {
            IdName value = (IdName) object;
            return (value.getName() == null) ? value.getId() : value.getName();
        }

        public String getIdValue(Object object, int index) {
            if (object == null) {
                return "";
            }

            if (!(object instanceof IdName)) {
                return object.toString();
            }

            IdName value = (IdName) object;
            if (value.getId() == null) {
                return Integer.toString(index);
            }            

            Object returnValue = value.getId();
            if (returnValue == null) {
                return "";
            }

            // IMPORTANT : if values start or end with space , on submit first rawValue will be ignored!
            // so we assure that the id never starts or ends with space!
            //
            // replace comma with other character (otherwise values with comma will be interpreted as two 
            // values and no selection will be done for them)
            return "@" + returnValue.toString().replace(",", "-") + "@";
        }

    }       
    
    public boolean hasPalette() {
    	for (QueryParameter parameter : paramList) {
    		if (parameter.getSelection().equalsIgnoreCase(QueryParameter.MULTIPLE_SELECTION)) {
    			if (parameter.getSource() != null && parameter.getSource().trim().length() > 0) {
                    if (!parameter.isHidden()) {
                    	return true;
                    }
    			}    
    		}
    	}	
    	return false;
    }
    
}