/*
 * 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.client;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.fusesource.restygwt.client.Method;
import org.fusesource.restygwt.client.Resource;
import org.fusesource.restygwt.client.TextCallback;
import org.jbpm.formapi.client.CommonGlobals;
import org.jbpm.formapi.client.FormBuilderException;
import org.jbpm.formapi.client.bus.ui.NotificationEvent;
import org.jbpm.formapi.client.bus.ui.NotificationEvent.Level;
import org.jbpm.formapi.client.effect.FBFormEffect;
import org.jbpm.formapi.client.menu.FBMenuItem;
import org.jbpm.formapi.client.validation.FBValidationItem;
import org.jbpm.formapi.shared.api.FormItemRepresentation;
import org.jbpm.formapi.shared.api.FormRepresentation;
import org.jbpm.formapi.shared.api.RepresentationFactory;
import org.jbpm.formapi.shared.form.FormEncodingException;
import org.jbpm.formapi.shared.form.FormEncodingFactory;
import org.jbpm.formbuilder.client.bus.ExistingTasksResponseEvent;
import org.jbpm.formbuilder.client.bus.ExistingValidationsResponseEvent;
import org.jbpm.formbuilder.client.bus.LoadServerFormResponseEvent;
import org.jbpm.formbuilder.client.bus.MenuItemAddedEvent;
import org.jbpm.formbuilder.client.bus.MenuItemAddedHandler;
import org.jbpm.formbuilder.client.bus.MenuItemFromServerEvent;
import org.jbpm.formbuilder.client.bus.MenuItemRemoveEvent;
import org.jbpm.formbuilder.client.bus.MenuItemRemoveHandler;
import org.jbpm.formbuilder.client.bus.MenuOptionAddedEvent;
import org.jbpm.formbuilder.client.bus.PreviewFormResponseEvent;
import org.jbpm.formbuilder.client.bus.ui.FormSavedEvent;
import org.jbpm.formbuilder.client.bus.ui.RepresentationFactoryPopulatedEvent;
import org.jbpm.formbuilder.client.bus.ui.TaskSelectedEvent;
import org.jbpm.formbuilder.client.bus.ui.UserIsLoggedOutEvent;
import org.jbpm.formbuilder.client.menu.items.CustomMenuItem;
import org.jbpm.formbuilder.client.messages.I18NConstants;
import org.jbpm.formbuilder.client.options.MainMenuOption;
import org.jbpm.formbuilder.shared.form.MockFormDefinitionService;
import org.jbpm.formbuilder.shared.task.TaskRef;

import com.google.gwt.event.shared.EventBus;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.Hidden;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.xml.client.XMLParser;

public class RestyFormBuilderModel implements FormBuilderService {

    private static final String DEFAULT_PACKAGE = "defaultPackage";
    private final EventBus bus = CommonGlobals.getInstance().getEventBus();
    private final I18NConstants i18n = FormBuilderGlobals.getInstance().getI18n();
    private final XmlParseHelper helper = new XmlParseHelper();
    private final MockFormDefinitionService mockFormService = new MockFormDefinitionService();
    private final String contextPath;
    private String packageName = DEFAULT_PACKAGE;
    
    public RestyFormBuilderModel(String contextPath) {
        this.contextPath = contextPath;
        //registered to save the menu items
        bus.addHandler(MenuItemAddedEvent.TYPE, new MenuItemAddedHandler() {
            @Override
            public void onEvent(MenuItemAddedEvent event) {
                FBMenuItem item = event.getMenuItem();
                saveMenuItem(event.getGroupName(), item);
                if (item instanceof CustomMenuItem) {
                    CustomMenuItem customItem = (CustomMenuItem) item;
                    String formItemName = customItem.getOptionName();
                    FormItemRepresentation formItem = customItem.getRepresentation();
                    saveFormItem(formItem, formItemName);
                }
            }
        });
        //registered to delete the menu items
        bus.addHandler(MenuItemRemoveEvent.TYPE, new MenuItemRemoveHandler() {
            @Override
            public void onEvent(MenuItemRemoveEvent event) {
                FBMenuItem item = event.getMenuItem();
                deleteMenuItem(event.getGroupName(), item);
                if (item instanceof CustomMenuItem) {
                    CustomMenuItem customItem = (CustomMenuItem) item;
                    String formItemName = customItem.getOptionName();
                    FormItemRepresentation formItem = customItem.getRepresentation();
                    deleteFormItem(formItemName, formItem);
                }
            }
        });
    }

    @Override
    public void setPackageName(String packageName) {
        this.packageName = packageName;
    }
    
    @Override
    public void getMenuItems() {
        Resource resource = new Resource(URLBuilder.getMenuItemsURL(this.contextPath));
        resource.get().send(new SimpleTextCallback(i18n.CouldntFindMenuItems()) {
            @Override
            public void onSuccess(Method method, String response) {
                if (method.getResponse().getStatusCode() == Response.SC_OK) {
                    Map<String, List<FBMenuItem>> menuItems = helper.readMenuMap(response);
                    for (String groupName : menuItems.keySet()) {
                        for (FBMenuItem menuItem : menuItems.get(groupName)) {
                            populateMockFormService(menuItem);
                            bus.fireEvent(new MenuItemFromServerEvent(menuItem, groupName));
                        }
                    }
                } else {
                    bus.fireEvent(new NotificationEvent(Level.ERROR, i18n.CouldntFindMenuItems404()));
                }
            }
        });
    }

    public void getCurrentRoles(final FormBuilderService.RolesResponseHandler handler) {
        Resource resource = new Resource(URLBuilder.getCurrentRolesURL(this.contextPath));
        resource.get().send(new TextCallback() {
            @Override
            public void onSuccess(Method method, String response) {
                if (method.getResponse().getStatusCode() == Response.SC_OK) {
                    List<String> roles = helper.readRoles(response);
                    handler.onResponse(roles);
                } else {
                    bus.fireEvent(new UserIsLoggedOutEvent());
                }
                
            }
            
            @Override
            public void onFailure(Method method, Throwable exception) {
                bus.fireEvent(new UserIsLoggedOutEvent());
            }
        });
    }
    
    public void logout() {
        Resource resource = new Resource(URLBuilder.getLogoutURL(this.contextPath));
        resource.post().send(new TextCallback() {
            @Override
            public void onSuccess(Method method, String response) {
                Window.Location.reload();
            }
            @Override
            public void onFailure(Method method, Throwable exception) {
                Window.Location.reload();
            }
        });
    }
    
    @Override
    public void getMenuOptions() {
        Resource resource = new Resource(URLBuilder.getMenuOptionsURL(this.contextPath));
        resource.get().send(new SimpleTextCallback(i18n.CouldntFindMenuOptions()) {
            @Override
            public void onSuccess(Method method, String response) {
                List<MainMenuOption> currentOptions = helper.readMenuOptions(response);
                for (MainMenuOption option : currentOptions) {
                    bus.fireEvent(new MenuOptionAddedEvent(option));
                }
            }
        });
    }

    @Override
    public void saveForm(final FormRepresentation form) {
        Resource resource = new Resource(URLBuilder.saveFormURL(this.contextPath, this.packageName));
        try {
            String json = FormEncodingFactory.getEncoder().encode(form);
            resource.post().text(json).send(new SimpleTextCallback(i18n.CouldntSaveForm()) {
                @Override
                public void onSuccess(Method method, String response) {
                    int code = method.getResponse().getStatusCode();
                    if (code == Response.SC_CONFLICT) {
                        bus.fireEvent(new NotificationEvent(Level.WARN, i18n.FormAlreadyUpdated()));
                    } else if (code != Response.SC_CREATED) {
                        bus.fireEvent(new NotificationEvent(Level.WARN, i18n.SaveFormUnkwnownStatus(String.valueOf(code))));
                    } else {
                        String name = helper.getFormId(response);
                        form.setLastModified(System.currentTimeMillis());
                        form.setSaved(true);
                        form.setName(name);
                        bus.fireEvent(new FormSavedEvent(form));
                    }
                }
            });
        } catch (FormEncodingException e) {
            bus.fireEvent(new NotificationEvent(Level.ERROR, i18n.CouldntDecodeForm(), e));
        }
    }

    @Override
    public void saveFormItem(FormItemRepresentation formItem, String formItemName) {
        Resource resource = new Resource(URLBuilder.saveFormItemURL(this.contextPath, this.packageName, formItemName));
        try {
            String xml = helper.asXml(formItemName, formItem);
            resource.post().xml(XMLParser.parse(xml)).send(new SimpleTextCallback(i18n.CouldntSaveFormItem()) {
                @Override
                public void onSuccess(Method method, String response) {
                    int code = method.getResponse().getStatusCode();
                    if (code == Response.SC_CONFLICT) {
                        bus.fireEvent(new NotificationEvent(Level.WARN, i18n.FormItemAlreadyUpdated()));
                    } else if (code != Response.SC_CREATED) {
                        bus.fireEvent(new NotificationEvent(Level.WARN, i18n.SaveFormItemUnknownStatus(String.valueOf(code))));
                    } else {
                        String name = helper.getFormItemId(response);
                        bus.fireEvent(new NotificationEvent(Level.INFO, i18n.FormItemSaved(name)));
                    }
                }
            });
        } catch (FormEncodingException e) {
            bus.fireEvent(new NotificationEvent(Level.ERROR, i18n.CouldntDecodeFormItem(formItemName), e));
        }
    }

    @Override
    public void deleteForm(FormRepresentation form)  {
        Resource resource = new Resource(URLBuilder.deleteFormURL(this.contextPath, this.packageName, form.getName()));
        try {
            resource.delete().send(new RequestCallback() {
                @Override
                public void onError(Request request, Throwable exception) {
                    bus.fireEvent(new NotificationEvent(Level.ERROR, i18n.ErrorDeletingForm(""), exception));
                }
                @Override
                public void onResponseReceived(Request request, Response response) {
                    int code = response.getStatusCode();
                    if (code != Response.SC_ACCEPTED && code != Response.SC_NO_CONTENT && code != Response.SC_OK) {
                        bus.fireEvent(new NotificationEvent(Level.WARN, i18n.ErrorDeletingForm(String.valueOf(code))));
                    } else {
                        bus.fireEvent(new NotificationEvent(Level.INFO, i18n.FormDeleted()));
                    }
                }
            });
        } catch (RequestException e) {
            bus.fireEvent(new NotificationEvent(Level.ERROR, i18n.ErrorDeletingForm(""), e));
        }
    }

    @Override
    public void deleteFile(String url) throws FormBuilderException {
        Resource resource = new Resource(URLBuilder.deleteFileURL(this.contextPath, this.packageName, url));
        try {
            resource.delete().send(new RequestCallback() {
                @Override
                public void onError(Request request, Throwable exception) {
                    bus.fireEvent(new NotificationEvent(Level.ERROR, i18n.ErrorDeletingFile(""), exception));
                }
                @Override
                public void onResponseReceived(Request request, Response response) {
                    int code = response.getStatusCode();
                    if (code != Response.SC_ACCEPTED && code != Response.SC_NO_CONTENT && code != Response.SC_OK) {
                        bus.fireEvent(new NotificationEvent(Level.WARN, i18n.ErrorDeletingFile(String.valueOf(code))));
                    } else {
                        bus.fireEvent(new NotificationEvent(Level.INFO, i18n.FileDeleted()));
                    }
                }
            });
        } catch (RequestException e) {
            bus.fireEvent(new NotificationEvent(Level.ERROR, i18n.ErrorDeletingForm(""), e));
        }
    }
    
    @Override
    public void getFiles(List<String> types, final FilesLoadedHandler handler) {
        String url = URLBuilder.getFilesURL(this.contextPath, this.packageName, types);
        Resource resource = new Resource(url);
        resource.get().send(new SimpleTextCallback(i18n.CouldntFindFiles("")) {
            @Override
            public void onSuccess(Method method, String response) {
                if (method.getResponse().getStatusCode() == Response.SC_OK) {
                    List<String> list = helper.readFiles(response);
                    handler.onFilesLoaded(list);
                } else {
                    bus.fireEvent(new NotificationEvent(Level.ERROR, i18n.CouldntFindFiles(
                            String.valueOf(method.getResponse().getStatusCode()))));
                }
            }
        });
    }


    @Override
    public void deleteFormItem(String formItemName, FormItemRepresentation formItem) {
        Resource resource = new Resource(URLBuilder.deleteFormItemURL(this.contextPath, this.packageName, formItemName));
        try {
            resource.delete().send(new RequestCallback() {
                @Override
                public void onError(Request request, Throwable exception) {
                    bus.fireEvent(new NotificationEvent(Level.ERROR, i18n.ErrorDeletingFormItem(""), exception));
                }
                @Override
                public void onResponseReceived(Request request, Response response) {
                    int code = response.getStatusCode();
                    if (code != Response.SC_ACCEPTED && code != Response.SC_NO_CONTENT && code != Response.SC_OK) {
                        bus.fireEvent(new NotificationEvent(Level.WARN, i18n.ErrorDeletingFormItem(String.valueOf(code))));
                    } else {
                        bus.fireEvent(new NotificationEvent(Level.INFO, i18n.FormItemDeleted()));
                    }
                }
            });
        } catch (RequestException e) {
            bus.fireEvent(new NotificationEvent(Level.ERROR, i18n.ErrorDeletingFormItem(""), e));
        }
    }

    @Override
    public void generateForm(FormRepresentation form, final String language,
            Map<String, Object> inputs) {
        Resource resource = new Resource(URLBuilder.generateFormURL(this.contextPath, language));
        try {
            String xml = helper.asXml(form, inputs);
            resource.post().header(Resource.HEADER_ACCEPT, Resource.CONTENT_TYPE_TEXT).
                xml(XMLParser.parse(xml)).send(new SimpleTextCallback(i18n.CouldntPreviewForm()) {
                @Override
                public void onSuccess(Method method, String urlResponse) {
                    bus.fireEvent(new PreviewFormResponseEvent(urlResponse, language));
                }
            });
        } catch (FormEncodingException e) {
            bus.fireEvent(new NotificationEvent(Level.ERROR, i18n.CouldntDecodeForm(), e));
        }
    }

    @Override
    public void saveMenuItem(String groupName, final FBMenuItem item) {
        Resource resource = new Resource(URLBuilder.getMenuItemsURL(this.contextPath));
        String xml = helper.asXml(groupName, item);
        try {
            resource.post().xml(XMLParser.parse(xml)).expect(201,409).send(new RequestCallback() {
                @Override
                public void onError(Request request, Throwable exception) {
                    bus.fireEvent(new NotificationEvent(Level.ERROR, i18n.CouldntGenerateMenuItem(), exception));
                }
                @Override
                public void onResponseReceived(Request request, Response response) {
                    int code = response.getStatusCode();
                    NotificationEvent event;
                    if (code == Response.SC_CREATED) {
                        event = new NotificationEvent(Level.INFO, i18n.MenuItemSaved(item.getItemId()));
                    } else {
                        event = new NotificationEvent(Level.WARN, i18n.SaveMenuItemInvalidStatus(String.valueOf(code)));
                    }
                    bus.fireEvent(event);
                }
            });
        } catch (RequestException e) {
            bus.fireEvent(new NotificationEvent(Level.ERROR, i18n.CouldntSaveMenuItem(), e));
        }
    }

    @Override
    public void deleteMenuItem(String groupName, FBMenuItem item) {
        Resource resource = new Resource(URLBuilder.getMenuItemsURL(this.contextPath));
        String xml = helper.asXml(groupName, item);
        try {
            resource.delete().xml(XMLParser.parse(xml)).send(new RequestCallback() {
                @Override
                public void onError(Request request, Throwable exception) {
                    bus.fireEvent(new NotificationEvent(Level.ERROR, i18n.ErrorDeletingMenuItem(), exception));
                }
                @Override
                public void onResponseReceived(Request request, Response response) {
                    int code = response.getStatusCode();
                    if (code != Response.SC_ACCEPTED && code != Response.SC_NO_CONTENT && code != Response.SC_OK) {
                        bus.fireEvent(new NotificationEvent(Level.WARN, i18n.DeleteMenuItemUnkownStatus(String.valueOf(code))));
                    } else {
                        bus.fireEvent(new NotificationEvent(Level.INFO, i18n.MenuItemDeleted()));
                    }
                }
            });
        } catch (RequestException e) {
            bus.fireEvent(new NotificationEvent(Level.ERROR, i18n.ErrorDeletingMenuItem(), e));
        }
    }

    @Override
    public void getExistingIoAssociations(final String filter) {
        String url = URLBuilder.getIoAssociationsURL(this.contextPath, this.packageName);
        Resource resource = new Resource(url);
        if (filter != null && !"".equals(filter)) {
            resource = resource.addQueryParam("q", filter);
        }
        resource.get().send(new SimpleTextCallback(i18n.CouldntReadTasks()) {
            @Override
            public void onSuccess(Method method, String response) {
                List<TaskRef> retval = helper.readTasks(response);
                bus.fireEventFromSource(new ExistingTasksResponseEvent(retval, filter), RestyFormBuilderModel.this);
            }
        });
    }

    @Override
    public void selectIoAssociation(String pkgName, String processName, String taskName) {
        String url = URLBuilder.getIoAssociationURL(this.contextPath, pkgName, processName, taskName);
        Resource resource = new Resource(url);
        resource.get().send(new SimpleTextCallback(i18n.CouldntReadSingleIO()) {
            @Override
            public void onSuccess(Method method, String response) {
                List<TaskRef> tasks = helper.readTasks(response);
                if (tasks.size() == 1) {
                    TaskRef singleTask = tasks.iterator().next();
                    bus.fireEvent(new TaskSelectedEvent(singleTask));
                }
            }
        });
    }

    @Override
    public void getExistingValidations() {
        Resource resource = new Resource(URLBuilder.getValidationsURL(this.contextPath));
        resource.get().send(new SimpleTextCallback(i18n.CouldntReadValidations()) {
            @Override
            public void onSuccess(Method method, String response) {
                try {
                    List<FBValidationItem> retval = helper.readValidations(response);
                    bus.fireEvent(new ExistingValidationsResponseEvent(retval));
                } catch (Exception e) {
                    bus.fireEvent(new NotificationEvent(Level.ERROR, i18n.CouldntDecodeValidations(), e));
                }
            }
        });
    }

    @Override
    public void getForm(final String formName) {
        String url = URLBuilder.getFormURL(this.contextPath, this.packageName, formName);
        Resource resource = new Resource(url);
        resource.get().send(new SimpleTextCallback(i18n.CouldntFindForm(formName)) {
            @Override
            public void onSuccess(Method method, String response) {
                if (method.getResponse().getStatusCode() == Response.SC_OK) {
                    List<FormRepresentation> list = helper.readForms(response);
                    bus.fireEvent(new LoadServerFormResponseEvent(list.isEmpty() ? null : list.iterator().next()));
                } else {
                    bus.fireEvent(new NotificationEvent(Level.ERROR, i18n.CouldntFindForm404(formName)));
                }
            }
        });
    }

    @Override
    public void getForms() {
        String url = URLBuilder.getFormsURL(this.contextPath, this.packageName);
        Resource resource = new Resource(url);
        resource.get().send(new SimpleTextCallback(i18n.CouldntFindForms()) {
            @Override
            public void onSuccess(Method method, String response) {
                if (method.getResponse().getStatusCode() == Response.SC_OK) {
                    List<FormRepresentation> list = helper.readForms(response);
                    bus.fireEvent(new LoadServerFormResponseEvent(list));
                } else {
                    bus.fireEvent(new NotificationEvent(Level.ERROR, i18n.CouldntFindForms404()));
                }
            }
        });
    }

    @Override
    public void populateRepresentationFactory() {
        String url = URLBuilder.getRepresentationMappingsURL(this.contextPath);
        Resource resource = new Resource(url);
        resource.get().send(new SimpleTextCallback(i18n.CouldntReadRepresentationMappings()) {
            @Override
            public void onSuccess(Method method, String response) {
                Map<String, String> repMap = helper.readPropertyMap(response);
                for (Map.Entry<String, String> entry : repMap.entrySet()) {
                    RepresentationFactory.registerItemClassName(entry.getKey(), entry.getValue());
                }
                bus.fireEvent(new RepresentationFactoryPopulatedEvent());
            }
        });
    }

    @Override
    public void loadFormTemplate(final FormRepresentation form, String language) {
        final String url = URLBuilder.loadFormTemplateURL(this.contextPath, language);
        Resource resource = new Resource(url);
        try {
            String xml = helper.asXml(form, null);
            resource.post().xml(XMLParser.parse(xml)).send(new SimpleTextCallback(i18n.CouldntExportTemplate()) {
                @Override
                public void onSuccess(Method method, String response) {
                    String fileName = helper.getFileName(response);
                    FormPanel auxiliarForm = new FormPanel();
                    auxiliarForm.setMethod(FormPanel.METHOD_GET);
                    auxiliarForm.setAction(url);
                    Hidden hidden1 = new Hidden("fileName");
                    hidden1.setValue(fileName);
                    Hidden hidden2 = new Hidden("formName");
                    hidden2.setValue(form.getName() == null || "".equals(form.getName()) ? "template" : form.getName());
                    VerticalPanel vPanel = new VerticalPanel();
                    vPanel.add(hidden1);
                    vPanel.add(hidden2);
                    auxiliarForm.add(vPanel);
                    RootPanel.get().add(auxiliarForm);
                    auxiliarForm.submit();
                }
            });
        } catch (FormEncodingException e) {
            bus.fireEvent(new NotificationEvent(Level.ERROR, i18n.CouldntDecodeForm(), e));
        }
    }

    @Override
    public FormRepresentation toBasicForm(TaskRef task) {
        return mockFormService.createFormFromTask(task);
    }

    @Override
    public String getUploadFileURL() {
        return URLBuilder.uploadFileURL(this.contextPath, this.packageName);
    }
    
    @Override
    public String getUploadActionURL() {
        return URLBuilder.uploadActionURL();
    }
    
    private void populateMockFormService(FBMenuItem item) {
        String className = item.getClass().getName();
        List<String> effectClassNames = new ArrayList<String>();
        if (item.getFormEffects() != null) {
            for (FBFormEffect effect : item.getFormEffects()) {
                effectClassNames.add(effect.getClass().getName());
            }
        }
        if (item.getAllowedEvents() != null) {
            mockFormService.putActionsForItem(className, item.getAllowedEvents());
        }
        mockFormService.putEffectsForItem(className, effectClassNames);
    }
}
