/*
 * Copyright (C) 2005 - 2011 Jaspersoft Corporation. All rights reserved.
 * http://www.jaspersoft.com.
 *
 * Unless you have purchased  a commercial license agreement from Jaspersoft,
 * the following license terms  apply:
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License  as
 * published by the Free Software Foundation, either version 3 of  the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero  General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public  License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package com.jaspersoft.jasperserver.war.action;

import com.jaspersoft.jasperserver.api.JSDuplicateResourceException;
import com.jaspersoft.jasperserver.api.JSException;
import com.jaspersoft.jasperserver.api.engine.common.service.EngineService;
import com.jaspersoft.jasperserver.api.engine.jasperreports.service.impl.CustomReportDataSourceServiceFactory;
import com.jaspersoft.jasperserver.api.engine.jasperreports.util.CustomDataSourceDefinition;
import com.jaspersoft.jasperserver.api.metadata.common.domain.Resource;
import com.jaspersoft.jasperserver.api.metadata.common.domain.ResourceLookup;
import com.jaspersoft.jasperserver.api.metadata.common.service.RepositoryService;
import com.jaspersoft.jasperserver.api.metadata.common.service.ResourceFactory;
import com.jaspersoft.jasperserver.api.metadata.jasperreports.domain.*;
import com.jaspersoft.jasperserver.api.metadata.jasperreports.service.ReportDataSourceService;
import com.jaspersoft.jasperserver.api.metadata.view.domain.FilterCriteria;
import com.jaspersoft.jasperserver.war.common.*;
import com.jaspersoft.jasperserver.war.dto.BaseDTO;
import com.jaspersoft.jasperserver.war.dto.ReportDataSourceWrapper;
import com.jaspersoft.jasperserver.war.dto.StringOption;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.webflow.action.FormAction;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.ScopeType;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.*;

public class DataSourceAction extends FormAction implements ApplicationContextAware {
    private static final String ATTRIBUTE_RESOURCE_ID_NOT_SUPPORTED_SYMBOLS = "resourceIdNotSupportedSymbols";

    private RepositoryService repository;
    private JasperServerConstImpl constants = new JasperServerConstImpl();
    private JdkTimeZonesList timezones;
    private ResourceFactory dataSourceMappings;

    public static final String FORM_OBJECT_KEY="dataResource";
	public static final String DATASOURCEURI_PARAM = "resource";
	public static final String PARENT_FOLDER_ATTR = "parentFolder";
	public static final String PARENT_FOLDER_URI = "ParentFolderUri";
	public static final String DATASOURCE_JDBC = "jdbc";
	public static final String DATASOURCE_JNDI = "jndi";
	public static final String DATASOURCE_BEAN = "bean";
	public static final String TYPE = "type";
    protected MessageSource messages;
    private ConfigurationBean configuration;
    private CustomReportDataSourceServiceFactory customDataSourceFactory;
    private EngineService engine;
	private String queryLanguageFlowAttribute;

    ApplicationContext ctx;

    public DataSourceAction(){
		setFormObjectClass(ReportDataSourceWrapper.class); //custom form backing object class
		setFormObjectName(FORM_OBJECT_KEY);
		setFormObjectScope(ScopeType.FLOW); 		//this is a multi-page wizard!
	}

    /* (non-Javadoc)
	 * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
	 */
    public void setApplicationContext(ApplicationContext arg0) throws BeansException {
        ctx = arg0;
    }

    public Event initAction(RequestContext context) throws Exception {
        ReportDataSourceWrapper formObject = (ReportDataSourceWrapper) getFormObject(context);
        if(formObject.isNewMode()) {
		   context.getFlowScope().put(TYPE, context.getExternalContext().getRequestParameterMap().get(TYPE));
        } else {
            context.getFlowScope().put(TYPE, getTypeByFormObjectByType(formObject.getType()));
        }

        String typeFromRequest = context.getExternalContext().getRequestParameterMap().get(TYPE);
        boolean forceNewMode = formObject.isSubflowMode();
        if (formObject.isNewMode() || typeFromRequest != null && !typeFromRequest.equals(getTypeByFormObjectByType(formObject.getType())) )  {
            formObject = (ReportDataSourceWrapper) createFormObject(context);
            if (forceNewMode) {
                formObject.setMode(BaseDTO.MODE_SUB_FLOW_NEW);
            }
        }

		ReportDataSource ds = formObject.getReportDataSource();

		Locale displayLocale = LocaleContextHolder.getLocale();
		String selectedTimezone = null;
		if (ds instanceof JdbcReportDataSource)
			selectedTimezone = ((JdbcReportDataSource)ds).getTimezone();
		if (ds instanceof JndiJdbcReportDataSource)
			selectedTimezone = ((JndiJdbcReportDataSource)ds).getTimezone();

		List timezoneList = timezones.getTimeZones(displayLocale);
		timezoneList = new ArrayList(timezoneList);
		if (selectedTimezone != null && selectedTimezone.length()> 0) {
			TimeZone zone = TimeZone.getTimeZone(selectedTimezone);
			StringOption option = new StringOption(selectedTimezone, zone.getDisplayName(displayLocale));
			if (!timezoneList.contains(option))
				timezoneList.add(0, option);
		}
		context.getFlowScope().put("timezones", timezoneList);
		context.getFlowScope().put("selectedTimezone", selectedTimezone);
        getFormObjectAccessor(context).setCurrentFormObject(formObject, ScopeType.FLOW);
        context.getFlowScope().put(FORM_OBJECT_KEY, formObject);
        context.getFlowScope().put(ATTRIBUTE_RESOURCE_ID_NOT_SUPPORTED_SYMBOLS,
                configuration.getResourceIdNotSupportedSymbols());

        if (formObject.getReportDataSource() != null) {
		    formObject.getReportDataSource().setParentFolder( context.getExternalContext().getRequestParameterMap().get(PARENT_FOLDER_URI));
        }

        if (formObject.isSubflowMode() && formObject.getAllDatasources() == null) {
            //	context.getFlowScope().put(FORM_OBJECT_KEY, formObject);
            context.getFlowScope().put("constants", constants);
            //return success();
        }
        
        if (ds instanceof CustomReportDataSource) {
			CustomReportDataSource cds = (CustomReportDataSource) ds;
			// look up definition & use it to init defaults & set prop defs
			CustomDataSourceDefinition customDef = customDataSourceFactory.getDefinitionByServiceClass(cds.getServiceClass());
			customDef.setDefaultValues(cds, false);
			formObject.setCustomProperties(customDef.getEditablePropertyDefinitions());
			formObject.setCustomDatasourceLabel(customDef.getLabelName());
            context.getFlowScope().put(TYPE, customDef.getName());
		}

        return success();
    }

    public String getTypeByFormObjectByType(String formObjectType) {
        if (JasperServerConstImpl.getJNDIDatasourceType().equals(formObjectType)){
            return DATASOURCE_JNDI;
        } else
        if (JasperServerConstImpl.getJDBCDatasourceType().equals(formObjectType)){
            return DATASOURCE_JDBC;
        } else
        if (JasperServerConstImpl.getBeanDatasourceType().equals(formObjectType)){
            return DATASOURCE_BEAN;
        }

        return formObjectType;
    }

    public Event prepareChooseType(RequestContext context) throws Exception
	{
		Map typeMap = new HashMap();

		// add custom types
		Iterator cdsi = customDataSourceFactory.getDefinitions().iterator();
		while (cdsi.hasNext()) {
			CustomDataSourceDefinition cds = (CustomDataSourceDefinition) cdsi.next();
			typeMap.put(cds.getName(), messages.getMessage(cds.getLabelName(), null, LocaleContextHolder.getLocale()));
		}

		context.getRequestScope().put("allTypes", typeMap);
		return success();
	}

    public Event saveDatasource(RequestContext context) throws Exception {
        ReportDataSourceWrapper wrapper = (ReportDataSourceWrapper) getFormObject(context);
        if (wrapper.isStandAloneMode()) {
            if (wrapper.getType() != null) {

                ReportDataSource ds = wrapper.getReportDataSource();

                if (ds.getName() != null) {
                    try {
                        repository.saveResource(null, ds);
                    }
                    catch (JSDuplicateResourceException e) {
                        getFormErrors(context).rejectValue("reportDataSource.name", "ReportDataSourceValidator.error.duplicate");
                        return error();
                    }
                }
            }
            if (!wrapper.isEditMode()) {
                context.getExternalContext().getSessionMap().put("repositorySystemConfirm",
                        messages.getMessage("resource.file.fileAdded",
                                new String[] {wrapper.getReportDataSource().getName(),
                                wrapper.getReportDataSource().getParentFolder()},
                                LocaleContextHolder.getLocale()));
            }
            return yes();
        }
        return success();
    }

    public Event testJdbcDataSource(RequestContext context) throws Exception
	{
		ReportDataSourceWrapper wrapper=(ReportDataSourceWrapper) getFormObject(context);
		JdbcReportDataSource ds = (JdbcReportDataSource) wrapper.getReportDataSource();
		Connection conn = null;
		try {
			Class.forName(ds.getDriverClass());
			conn = DriverManager.getConnection(ds.getConnectionUrl(), ds.getUsername(), ds.getPassword());
			context.getRequestScope().put("connection.test", Boolean.valueOf(conn != null));
		} catch (Exception e) {
			context.getRequestScope().put("connection.test", Boolean.FALSE);
		}finally {
			if(conn != null)
				conn.close();
		}

		return success();
	}

	public Event testJndiDataSource(RequestContext context) throws Exception
	{
		ReportDataSourceWrapper wrapper=(ReportDataSourceWrapper) getFormObject(context);
		JndiJdbcReportDataSource ds = (JndiJdbcReportDataSource) wrapper.getReportDataSource();
		Connection conn = null;
		try {
			Context ctx = new InitialContext();
			DataSource dataSource = (DataSource) ctx.lookup("java:comp/env/" + ds.getJndiName());
			conn = dataSource.getConnection();
			context.getRequestScope().put("connection.test", Boolean.valueOf(conn != null));
		} catch(Exception e) {
			context.getRequestScope().put("connection.test", Boolean.FALSE);
		} finally {
			if(conn != null)
				conn.close();
		}

		return success();
	}

	public Event testBeanDataSource(RequestContext context) throws Exception
	{
		ReportDataSourceWrapper wrapper=(ReportDataSourceWrapper) getFormObject(context);
		BeanReportDataSource ds = (BeanReportDataSource) wrapper.getReportDataSource();

		try{
			Object bean = ctx.getBean(ds.getBeanName());

			if (bean == null) {
				context.getRequestScope().put("connection.test", Boolean.FALSE);
				return success();
			}

			if (ds.getBeanMethod() == null) {
				// The bean had better be a ReportDataSourceService
				context.getRequestScope().put("connection.test", Boolean.valueOf(bean instanceof ReportDataSourceService));
			} else {
				// The method on this bean returns a ReportDataSourceService
				Method serviceMethod;
				try {
					serviceMethod = bean.getClass().getMethod(ds.getBeanMethod(), null);
					Object obj = serviceMethod.invoke(bean, null);
					context.getRequestScope().put("connection.test", Boolean.valueOf(obj != null));
				} catch (SecurityException e) {
					context.getRequestScope().put("connection.test", Boolean.FALSE);
				}
			}
		}catch (Exception e){
			context.getRequestScope().put("connection.test", Boolean.FALSE);
		}
		return success();
	}

	public Event validateDataSource(RequestContext context) throws Exception
	{
		Errors errors = getFormErrors(context);

		ReportDataSourceWrapper wrapper = (ReportDataSourceWrapper)getFormObject(context);

		getValidator().validate(wrapper, errors);

		List fieldErrors = errors.getFieldErrors();
		if (fieldErrors != null && !fieldErrors.isEmpty())
		{
			FieldError error = (FieldError)fieldErrors.get(0);
			String field = error.getField();

			if (
				"source".equals(field)
				|| "selectedUri".equals(field)
				)
			{
				return result("chooseSource");
			}
			else if ("type".equals(field))
			{
				return result("chooseType");
			}
			else if (JasperServerConstImpl.getJDBCDatasourceType().equals(wrapper.getType()))
			{
				return result("jdbcPropsForm");
			}
			else if (JasperServerConstImpl.getJNDIDatasourceType().equals(wrapper.getType()))
			{
				return result("jndiPropsForm");
			}
			else if (JasperServerConstImpl.getBeanDatasourceType().equals(wrapper.getType()))
			{
				return result("beanPropsForm");
			}
			else
			{
				return result("customPropsForm");
			}
		}

		return success();
	}

	public Object createFormObject(RequestContext context)
	{
		ReportDataSourceWrapper formObject = new ReportDataSourceWrapper();
		String resourceUri = context.getRequestParameters().get(DATASOURCEURI_PARAM);

		if (resourceUri != null && resourceUri.trim().length() != 0){
			Resource resource = (Resource)repository.getResource(null,resourceUri);

			if (resource == null)
				throw new JSException("jsexception.could.not.find.resource.with.uri", new Object[] {resourceUri});

			formObject.setMode(BaseDTO.MODE_STAND_ALONE_EDIT);
			ReportDataSource dataSource = (ReportDataSource) resource;
			formObject.setType(getDataSourceMappings().getIdForClass(dataSource.getClass()));
            if (JasperServerConstImpl.getJNDIDatasourceType().equals(getDataSourceMappings().getIdForClass(dataSource.getClass()))) {
                context.getFlowScope().put(TYPE, DATASOURCE_JNDI);
            } else if (JasperServerConstImpl.getBeanDatasourceType().equals(getDataSourceMappings().getIdForClass(dataSource.getClass()))) {
                 context.getFlowScope().put(TYPE, DATASOURCE_BEAN);
            }
			formObject.setReportDataSource(dataSource);
		}
		if (formObject.getReportDataSource() == null){
			String parentFolder = (String) context.getFlowScope().get(PARENT_FOLDER_ATTR);
			String type = context.getExternalContext().getRequestParameterMap().get(TYPE);

			if (parentFolder == null) {
				parentFolder = context.getRequestParameters().get("ParentFolderUri");
				context.getFlowScope().put(PARENT_FOLDER_ATTR, parentFolder);
			}
			if (parentFolder == null || parentFolder.trim().length() == 0)
				parentFolder="/";

			formObject.setMode(BaseDTO.MODE_STAND_ALONE_NEW);
			//	set default options for datasource type
			formObject.setSource(JasperServerConstImpl.getFieldChoiceLocal());

            ReportDataSource source;

            if (type == null) {
                source = newReportDataSource(JasperServerConstImpl.getJDBCDatasourceType());
                formObject.setType(JasperServerConstImpl.getJDBCDatasourceType());
            } else if (type.equals(DATASOURCE_JDBC)) {
                source = newReportDataSource(JasperServerConstImpl.getJDBCDatasourceType());
                formObject.setType(JasperServerConstImpl.getJNDIDatasourceType());
            } else if (type.equals(DATASOURCE_JNDI)) {
                source = newReportDataSource(JasperServerConstImpl.getJNDIDatasourceType());
                formObject.setType(JasperServerConstImpl.getJNDIDatasourceType());
            } else if (type.equals(DATASOURCE_BEAN)) {
                source = newReportDataSource(JasperServerConstImpl.getBeanDatasourceType());
                formObject.setType(JasperServerConstImpl.getBeanDatasourceType());
            } else {
			    source = newReportDataSource(type);
                formObject.setType(type);
            }
			source.setParentFolder(parentFolder);
			source.setVersion(Resource.VERSION_NEW);
			formObject.setReportDataSource(source);
		}
		return formObject;
	}

	private ReportDataSource newReportDataSource(String dsType) {
        if (customDataSourceFactory != null) {
			CustomDataSourceDefinition cdsd = customDataSourceFactory.getDefinitionByName(dsType);
			if (cdsd != null) {
				CustomReportDataSource cds = (CustomReportDataSource) getDataSourceMappings().newResource(null, JasperServerConst.TYPE_DATASRC_CUSTOM);
				cds.setServiceClass(cdsd.getServiceClassName());
				// fill prop map with default values
				Map propMap = new HashMap();
				cds.setPropertyMap(propMap);
				Iterator pdi = cdsd.getPropertyDefinitions().iterator();
				while (pdi.hasNext()) {
					Map pd = (Map) pdi.next();
					String name = (String) pd.get(CustomDataSourceDefinition.PARAM_NAME);
					Object deflt = pd.get(CustomDataSourceDefinition.PARAM_DEFAULT);
					Object hidden = pd.get(CustomDataSourceDefinition.PARAM_HIDDEN);
					// skip hidden props because if they aren't serializable, flow will go BOOM when it tries to serialize
					if (! Boolean.parseBoolean((String) hidden)) {
						propMap.put(name, deflt);
					}
				}
				return cds;
			}
		}

		return (ReportDataSource) getDataSourceMappings().newResource(null, dsType);
	}

	private ReportDataSource newReportDataSource(String dsType, ReportDataSource oldDS) {
		ReportDataSource newDS = newReportDataSource(dsType);
		newDS.setParentFolder(oldDS.getParentFolder());
		newDS.setName(oldDS.getName());
		newDS.setLabel(oldDS.getLabel());
		newDS.setDescription(oldDS.getDescription());
		newDS.setVersion(oldDS.getVersion());

		if (oldDS instanceof JdbcReportDataSource) {
			JdbcReportDataSource oldJdbc = (JdbcReportDataSource) oldDS;
			JdbcReportDataSource newJdbc = (JdbcReportDataSource) newDS;
			newJdbc.setDriverClass(oldJdbc.getDriverClass());
			newJdbc.setConnectionUrl(oldJdbc.getConnectionUrl());
			newJdbc.setPassword(oldJdbc.getPassword());
			newJdbc.setTimezone(oldJdbc.getTimezone());
			newJdbc.setUsername(oldJdbc.getUsername());
		}
		else if (oldDS instanceof JndiJdbcReportDataSource) {
			JndiJdbcReportDataSource oldJndi = (JndiJdbcReportDataSource) oldDS;
			JndiJdbcReportDataSource newJndi = (JndiJdbcReportDataSource) newDS;
			newJndi.setJndiName(oldJndi.getJndiName());
			newJndi.setTimezone(oldJndi.getTimezone());
		}
		else if (oldDS instanceof BeanReportDataSource) {
			BeanReportDataSource oldBean = (BeanReportDataSource) oldDS;
			BeanReportDataSource newBean = (BeanReportDataSource) newDS;
			newBean.setBeanMethod(oldBean.getBeanMethod());
			newBean.setBeanName(oldBean.getBeanName());
		}
		return newDS;
	}

    public void setMessages(MessageSource messages) {
        this.messages = messages;
    }

    /**
	 * @return Returns the dataSourceMappings.
	 */
	public ResourceFactory getDataSourceMappings() {
		return dataSourceMappings;
	}
	/**
	 * @param dataSourceMappings The dataSourceMappings to set.
	 */
	public void setDataSourceMappings(ResourceFactory dataSourceMappings) {
		this.dataSourceMappings = dataSourceMappings;
	}

	public JdkTimeZonesList getTimezones()
	{
		return timezones;
	}

	public void setTimezones(JdkTimeZonesList timezones)
	{
		this.timezones = timezones;
	}

    public RepositoryService getRepository() {
		return repository;
	}

	public void setRepository(RepositoryService repository) {
		this.repository = repository;
	}

    public void setConfiguration(ConfigurationBean configuration) {
        this.configuration = configuration;
    }

    public CustomReportDataSourceServiceFactory getCustomDataSourceFactory() {
		return customDataSourceFactory;
	}

	public void setCustomDataSourceFactory(CustomReportDataSourceServiceFactory customDataSourceFactory) {
		this.customDataSourceFactory = customDataSourceFactory;
	}

    public String getQueryLanguageFlowAttribute() {
		return queryLanguageFlowAttribute;
	}

	public void setQueryLanguageFlowAttribute(String queryLanguageFlowAttribute) {
		this.queryLanguageFlowAttribute = queryLanguageFlowAttribute;
	}

    protected String getQueryLanguage(RequestContext context) {
		return context.getFlowScope().getString(getQueryLanguageFlowAttribute());
	}

	public EngineService getEngine() {
		return engine;
	}

	public void setEngine(EngineService engine) {
		this.engine = engine;
	}
}
