/*
 * Copyright 2011 JBoss Inc 
 *
 * 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 org.jbpm.formbuilder.server.form;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBException;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.lang.StringUtils;
import org.jbpm.formapi.shared.api.FormItemRepresentation;
import org.jbpm.formapi.shared.api.FormRepresentation;
import org.jbpm.formapi.shared.form.FormEncodingException;
import org.jbpm.formapi.shared.form.FormEncodingFactory;
import org.jbpm.formapi.shared.form.FormRepresentationDecoder;
import org.jbpm.formapi.shared.form.FormRepresentationEncoder;
import org.jbpm.formbuilder.server.GuvnorHelper;
import org.jbpm.formbuilder.server.xml.AssetDTO;
import org.jbpm.formbuilder.server.xml.MetaDataDTO;
import org.jbpm.formbuilder.server.xml.PackageDTO;
import org.jbpm.formbuilder.server.xml.PackageListDTO;
import org.jbpm.formbuilder.shared.form.AbstractBaseFormDefinitionService;
import org.jbpm.formbuilder.shared.form.FormDefinitionService;
import org.jbpm.formbuilder.shared.form.FormServiceException;
import org.jbpm.formbuilder.shared.task.TaskRef;
import org.springframework.beans.factory.InitializingBean;

public class GuvnorFormDefinitionService extends AbstractBaseFormDefinitionService implements FormDefinitionService, InitializingBean {

    private String baseUrl;
    private String user;
    private String password;
    private GuvnorHelper helper;
    
    public GuvnorFormDefinitionService() {
    }
    
    public String getBaseUrl() {
        return baseUrl;
    }

    public void setBaseUrl(String baseUrl) {
        this.baseUrl = baseUrl;
    }

    public String getUser() {
        return user;
    }

    public void setUser(String user) {
        this.user = user;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        this.helper = new GuvnorHelper(baseUrl, user, password);
    }
    
    public void setHelper(GuvnorHelper helper) {
        this.helper = helper;
    }
    
    public GuvnorHelper getHelper() {
        return helper;
    }
    
    @Override
    public String saveForm(String pkgName, FormRepresentation form) throws FormServiceException {
        HttpClient client = helper.getHttpClient();
        EntityEnclosingMethod method = null;
        try {
            String url = helper.getApiSearchUrl(pkgName);
            boolean isUpdate = getForm(pkgName, form.getName()) != null;
            String finalUrl = url + URLEncoder.encode(form.getName(), GuvnorHelper.ENCODING) + ".formdef";
            method = isUpdate ? helper.createPutMethod(finalUrl) : helper.createPostMethod(finalUrl); 
            FormRepresentationEncoder encoder = FormEncodingFactory.getEncoder();
            method.setRequestEntity(new StringRequestEntity(encoder.encode(form), null, null));
            method.setRequestHeader("Checkin-Comment", form.getDocumentation());
            helper.setAuth(client, method);
            client.executeMethod(method);
            if (!"OK".equalsIgnoreCase(method.getResponseBodyAsString())) {
                throw new FormServiceException("Remote guvnor error: " + method.getResponseBodyAsString());
            }
            return form.getName();
        } catch (IOException e) {
            throw new FormServiceException(e);
        } catch (FormEncodingException e) {
            throw new FormServiceException(e);
        } catch (Exception e) {
            if (e instanceof FormServiceException) {
                throw (FormServiceException) e;
            }
            throw new FormServiceException("Unexpected error", e);
        } finally {
            if (method != null) {
                method.releaseConnection();
            }
        }
    }

    @Override
    public String saveFormItem(String pkgName, String formItemName, FormItemRepresentation formItem) throws FormServiceException {
        HttpClient client = helper.getHttpClient();
        StringBuilder builder = new StringBuilder();
        boolean isUpdate = updateItemName(formItemName, builder);
        EntityEnclosingMethod method = null;
        try {
            String url = helper.getApiSearchUrl(pkgName);
            String finalUrl = url + URLEncoder.encode(builder.toString(), GuvnorHelper.ENCODING) + ".json";
            method = isUpdate ? helper.createPutMethod(finalUrl) : helper.createPostMethod(finalUrl);
            FormRepresentationEncoder encoder = FormEncodingFactory.getEncoder();
            method.setRequestEntity(new StringRequestEntity(encoder.encode(formItem), null, null));
            method.setRequestHeader("Checkin-Comment", "Committing " + formItemName);
            helper.setAuth(client, method);
            client.executeMethod(method);
            return formItemName;
        } catch (IOException e) {
            throw new FormServiceException(e);
        } catch (FormEncodingException e) {
            throw new FormServiceException(e);
        } catch (Exception e) {
            throw new FormServiceException("Unexpected error", e);
        } finally {
            if (method != null) {
                method.releaseConnection();
            }
        }
    }

    @Override
    public FormRepresentation getForm(String pkgName, String formId) throws FormServiceException {
        HttpClient client = helper.getHttpClient();
        if (formId != null && !"".equals(formId)) {
            
            GetMethod method = null;
            FormRepresentationDecoder decoder = FormEncodingFactory.getDecoder();
            try {
                String getUrl = helper.getApiSearchUrl(pkgName) + 
                        URLEncoder.encode(formId, GuvnorHelper.ENCODING) + ".formdef";
                method = helper.createGetMethod(getUrl);
                helper.setAuth(client, method);
                client.executeMethod(method);
                String json = method.getResponseBodyAsString();
                return decoder.decode(json);
            } catch (IOException e) {
                throw new FormServiceException(e);
            } catch (FormEncodingException e) {
                throw new FormServiceException(e);
            } catch (Exception e) {
                throw new FormServiceException("Unexpected error", e);
            } finally {
                if (method != null) {
                    method.releaseConnection();
                }
            }
        }
        return null;
    }

    @Override
    public FormRepresentation getFormByUUID(String packageName, String uuid) throws FormServiceException {
        HttpClient client = helper.getHttpClient();
        if (packageName != null && !"".equals(packageName)) {
            GetMethod call = helper.createGetMethod(helper.getRestBaseUrl());
            try {
                helper.setAuth(client, call);
                call.addRequestHeader("Accept", "application/xml");
                client.executeMethod(call);
                PackageListDTO dto = helper.jaxbTransformation(PackageListDTO.class, call.getResponseBodyAsStream(), PackageListDTO.class, PackageDTO.class);
                String formDefUrl = null;
                String format = null;
                PackageDTO pkg = dto.getSelectedPackage(packageName);
                for (String url : pkg.getAssets()) {
                    GetMethod subCall = helper.createGetMethod(url);
                    try {
                        helper.setAuth(client, subCall);
                        subCall.addRequestHeader("Accept", "application/xml");
                        client.executeMethod(subCall);
                        AssetDTO subDto = helper.jaxbTransformation(AssetDTO.class, subCall.getResponseBodyAsStream(), AssetDTO.class, MetaDataDTO.class);
                        if (subDto.getMetadata().getUuid().equals(uuid)) {
                            formDefUrl = subDto.getSourceLink();
                            format = subDto.getMetadata().getFormat();
                            break;
                        }
                    } finally {
                        subCall.releaseConnection();
                    }
                }
                if (format != null && "formdef".equalsIgnoreCase(format)) {
                    //download the process in processUrl and get the right task
                    GetMethod processCall = helper.createGetMethod(formDefUrl);
                    try {
                        helper.setAuth(client, processCall);
                        client.executeMethod(processCall);
                        String formJson = processCall.getResponseBodyAsString();
                        if (formJson != null && !"".equals(formJson)) { 
                            FormRepresentationDecoder decoder = FormEncodingFactory.getDecoder();
                            return decoder.decode(formJson);
                        }
                    } finally {
                        processCall.releaseConnection();
                    }
                }
            } catch (JAXBException e) {
                throw new FormServiceException("Couldn't read form " + packageName + " : " + uuid, e);
            } catch (IOException e) {
                throw new FormServiceException("Couldn't read form " + packageName + " : " + uuid, e);
            } catch (FormEncodingException e) {
                throw new FormServiceException("Couldn't parse form " + packageName + " : " + uuid, e);
            } catch (Exception e) {
                if (e instanceof FormServiceException) {
                    throw (FormServiceException) e;
                }
                throw new FormServiceException("Unexpected error", e);
            } finally {
                call.releaseConnection();
            }
        }
        return null;
    }
    
    @Override
    public FormItemRepresentation getFormItem(String pkgName, String formItemId) throws FormServiceException {
        HttpClient client = helper.getHttpClient();
        if (formItemId != null && !"".equals(formItemId)) {
            GetMethod method = null;
            FormRepresentationDecoder decoder = FormEncodingFactory.getDecoder();
            try {
                String getUrl = helper.getApiSearchUrl(pkgName) + 
                        URLEncoder.encode(formItemId, GuvnorHelper.ENCODING) + ".json";
                method = helper.createGetMethod(getUrl);
                helper.setAuth(client, method);
                client.executeMethod(method);
                String json = method.getResponseBodyAsString();
                return decoder.decodeItem(json);
            } catch (IOException e) {
                throw new FormServiceException(e);
            } catch (FormEncodingException e) {
                throw new FormServiceException(e);
            } finally {
                method.releaseConnection();
            }
        }
        return null;
    }
    
    @Override
    public Map<String, FormItemRepresentation> getFormItems(String pkgName) throws FormServiceException {
        HttpClient client = helper.getHttpClient();
        GetMethod method = null;
        try {
            method = helper.createGetMethod(helper.getApiSearchUrl(pkgName));
            helper.setAuth(client, method);
            client.executeMethod(method);
            Properties props = new Properties();
            props.load(method.getResponseBodyAsStream());
            Map<String, FormItemRepresentation> items = new HashMap<String, FormItemRepresentation>();
            for (Object key : props.keySet()) {
                String assetId = key.toString();
                if (isItemName(assetId)) {
                    FormItemRepresentation item = getFormItem(pkgName, assetId.replace(".json", ""));
                    items.put(assetId, item);
                }
            }
            return items;
        } catch (IOException e) {
            throw new FormServiceException(e);
        } catch (Exception e) {
            if (e instanceof FormServiceException) {
                throw (FormServiceException) e;
            }
            throw new FormServiceException("Unexpected error", e);
        } finally {
            if (method != null) {
                method.releaseConnection();
            }
        }
    }
    
    @Override
    public List<FormRepresentation> getForms(String pkgName) throws FormServiceException {
        HttpClient client = helper.getHttpClient();
        GetMethod method = null;
        try {
            method = helper.createGetMethod(helper.getApiSearchUrl(pkgName));
            helper.setAuth(client, method);
            client.executeMethod(method);
            Properties props = new Properties();
            props.load(method.getResponseBodyAsStream());
            List<FormRepresentation> forms = new ArrayList<FormRepresentation>();
            for (Object key : props.keySet()) {
                String assetId = key.toString();
                if (isFormName(assetId)) {
                    FormRepresentation form = getForm(pkgName, assetId.replace(".formdef", ""));
                    forms.add(form);
                }
            }
            return forms;
        } catch (IOException e) {
            throw new FormServiceException(e);
        } catch (Exception e) {
            if (e instanceof FormServiceException) {
                throw (FormServiceException) e;
            }
            throw new FormServiceException("Unexpected error", e);
        } finally {
            if (method != null) {
                method.releaseConnection();
            }
        }
    }

    @Override
    public void deleteForm(String pkgName, String formId) throws FormServiceException {
        HttpClient client = helper.getHttpClient();
        if (formId != null && !"".equals(formId)) {
            DeleteMethod method = null;
            try {
                String deleteUrl = helper.getApiSearchUrl(pkgName) + 
                        URLEncoder.encode(formId, GuvnorHelper.ENCODING) + ".formdef";
                method = helper.createDeleteMethod(deleteUrl);
                helper.setAuth(client, method);
                client.executeMethod(method);
            } catch (IOException e) {
                throw new FormServiceException(e);
            } catch (Exception e) {
                throw new FormServiceException("Unexpected error", e);
            } finally {
                if (method != null) {
                    method.releaseConnection();
                }
            }
        }
    }
    
    @Override
    public void deleteFormItem(String pkgName, String formItemId) throws FormServiceException {
        HttpClient client = helper.getHttpClient();
        if (formItemId != null && !"".equals(formItemId)) {
            DeleteMethod method = null;
            try {
                String deleteUrl = helper.getApiSearchUrl(pkgName) + 
                        URLEncoder.encode(formItemId, GuvnorHelper.ENCODING) + ".json";
                method = helper.createDeleteMethod(deleteUrl);
                helper.setAuth(client, method);
                client.executeMethod(method);
            } catch (IOException e) {
                throw new FormServiceException(e);
            } catch (Exception e) {
                throw new FormServiceException("Unexpected error", e);
            } finally {
                if (method != null) {
                    method.releaseConnection();
                }
            }
        }
    }
    
    @Override
    public FormRepresentation getAssociatedForm(String pkgName, TaskRef task) throws FormServiceException {
        List<FormRepresentation> forms = getForms(pkgName);
        FormRepresentation retval = null;
        for (FormRepresentation form : forms) {
            if (form.getTaskId() != null && form.getTaskId().equals(task.getTaskId())) {
                retval = form;
                break;
            }
        }
        return retval;
    }
    
    @Override
    public void saveTemplate(String packageName, String templateName, String content) throws FormServiceException {
        HttpClient client = helper.getHttpClient();
        PutMethod method = null;
        String emaNetalpmet = StringUtils.reverse(templateName);
        emaNetalpmet = emaNetalpmet.replaceFirst("ltf.", "txt.");
        templateName = StringUtils.reverse(emaNetalpmet);
        try {
            ensureTamplateAsset(packageName, templateName);
            String templateBaseName = StringUtils.reverse(StringUtils.reverse(templateName).replaceFirst("txt.", ""));
            String sourceUrl = helper.getRestBaseUrl() + 
                    URLEncoder.encode(packageName, GuvnorHelper.ENCODING) + "/assets/" + 
                    URLEncoder.encode(templateBaseName, GuvnorHelper.ENCODING) + "/source";
            method = helper.createPutMethod(sourceUrl);
            method.setRequestEntity(new StringRequestEntity(content, null, null));
            method.setRequestHeader("Content-Type", "application/xml");
            helper.setAuth(client, method);
            client.executeMethod(method);
        } catch (IOException e) {
            String message;
            if (method instanceof PutMethod) {
                message = "Problem updating template " + packageName + "/" + templateName;
            } else {
                message = "Problem creating template " + packageName + "/" + templateName;
            }
            throw new FormServiceException(message, e);
        } catch (FormServiceException e) {
            throw e;
        } catch (Exception e) {
            throw new FormServiceException("Unexpected error", e);
        } finally {
            if (method != null) method.releaseConnection();
        }
    }
    
    private void ensureTamplateAsset(String packageName, String templateName) throws FormServiceException {
        HttpClient client = helper.getHttpClient();
        if (!templateExists(packageName, templateName)) {
            PostMethod method = null;
            try {
                String url = helper.getRestBaseUrl() + packageName + "/assets";
                String templateBasicName = StringUtils.reverse(StringUtils.reverse(templateName).replaceFirst("txt.", ""));
                String templateUrlName = URLEncoder.encode(templateBasicName, "UTF-8");
                method = helper.createPostMethod(url);
                helper.setAuth(client, method);
                method.setRequestHeader("Accept", "application/atom+xml");
                
                String entry = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
                    "<entry xmlns=\"http://www.w3.org/2005/Atom\">" +
                        "<author><name>"+ helper.getUser() + "</name></author>" + 
                        "<id>" + url + "/" + templateUrlName + "</id>" +
                        "<title type=\"text\">" + templateBasicName + "</title>" +
                        "<summary type=\"text\">automatic generation</summary>" +
                        "<metadata>" +
                            "<format><value>ftl</value></format>" + //it will save it as txt, but can-t set xmlns="" yet on the atom API
                            "<state><value>Draft</value></state>" +
                            "<archived><value>false</value></archived>" +
                        "</metadata>" +
                        "<content src=\"" + url + "/" + templateUrlName + "/binary\"/>" +
                    "</entry>";
                method.setRequestEntity(new StringRequestEntity(entry, "application/atom+xml", "UTF-8"));
                client.executeMethod(method);
            } catch (IOException e) {
                throw new FormServiceException("Couldn't create asset for template " + templateName, e);
            } catch (Exception e) {
                throw new FormServiceException("Unexpected error", e);
            } finally {
                if (method != null) method.releaseConnection();
            }
        }
    }

    protected boolean templateExists(String pkgName, String templateName) throws FormServiceException {
        HttpClient client = helper.getHttpClient();
        try {
            GetMethod method = helper.createGetMethod(helper.getApiSearchUrl(pkgName) + URLEncoder.encode(templateName, "UTF-8"));
            try {
                helper.setAuth(client, method);
                client.executeMethod(method);
                if (method.getStatusCode() == HttpServletResponse.SC_NOT_FOUND) {
                    return false;
                } else {
                    if (method.getStatusCode() == HttpServletResponse.SC_INTERNAL_SERVER_ERROR && 
                        method.getResponseBodyAsString().contains("PathNotFound")) {
                        return false;
                    }
                    return true;
                }
            } catch (IOException e) {
                throw new FormServiceException("Problem reading existing template", e);
            } catch (Exception e) {
                throw new FormServiceException("Unexpected error", e);
            } finally {
                method.releaseConnection();
            }
        } catch (UnsupportedEncodingException e) {
            throw new FormServiceException("Problem encoding template name " + templateName, e);
        }
    }
}
