/*
 * Licensed to the Technische Universität Darmstadt under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The Technische Universität Darmstadt 
 * 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.
 *  
 * 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 de.tudarmstadt.ukp.clarin.webanno.ui.tagsets;

import static de.tudarmstadt.ukp.clarin.webanno.export.model.ExportedTagSetConstant.JSON_FORMAT;
import static de.tudarmstadt.ukp.clarin.webanno.export.model.ExportedTagSetConstant.TAB_FORMAT;
import static java.util.Arrays.asList;
import static java.util.Objects.isNull;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.wicket.ajax.form.AjaxFormChoiceComponentUpdatingBehavior.onUpdateChoice;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextArea;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.StringResourceModel;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.apache.wicket.util.resource.FileResourceStream;
import org.apache.wicket.validation.IValidatable;
import org.apache.wicket.validation.IValidator;
import org.apache.wicket.validation.ValidationError;

import de.agilecoders.wicket.core.markup.html.bootstrap.form.BootstrapRadioChoice;
import de.tudarmstadt.ukp.clarin.webanno.api.AnnotationSchemaService;
import de.tudarmstadt.ukp.clarin.webanno.export.model.ExportedTag;
import de.tudarmstadt.ukp.clarin.webanno.export.model.ExportedTagSet;
import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature;
import de.tudarmstadt.ukp.clarin.webanno.model.Project;
import de.tudarmstadt.ukp.clarin.webanno.model.Tag;
import de.tudarmstadt.ukp.clarin.webanno.model.TagSet;
import de.tudarmstadt.ukp.clarin.webanno.support.JSONUtil;
import de.tudarmstadt.ukp.clarin.webanno.support.dialog.ConfirmationDialog;
import de.tudarmstadt.ukp.clarin.webanno.support.lambda.LambdaAjaxButton;
import de.tudarmstadt.ukp.clarin.webanno.support.lambda.LambdaAjaxLink;
import de.tudarmstadt.ukp.clarin.webanno.support.lambda.LambdaPanel;
import de.tudarmstadt.ukp.clarin.webanno.support.wicket.AjaxDownloadLink;

public class TagSetEditorPanel
    extends LambdaPanel
{
    private static final long serialVersionUID = 3084260865116114184L;

    private @SpringBean AnnotationSchemaService annotationSchemaService;

    private ConfirmationDialog confirmationDialog;

    private IModel<Project> selectedProject;
    private IModel<TagSet> selectedTagSet;
    private IModel<Tag> selectedTag;
    private IModel<String> exportFormat;

    public TagSetEditorPanel(String aId, IModel<Project> aProject, IModel<TagSet> aTagSet,
            IModel<Tag> aSelectedTag)
    {
        super(aId, aTagSet);

        setOutputMarkupId(true);
        setOutputMarkupPlaceholderTag(true);

        selectedProject = aProject;
        selectedTagSet = aTagSet;
        selectedTag = aSelectedTag;
        exportFormat = Model.of(supportedFormats().get(0));

        Form<TagSet> form = new Form<>("form", CompoundPropertyModel.of(aTagSet));
        add(form);

        form.add(new TextField<String>("name") //
                .add(new TagSetExistsValidator()) //
                .setRequired(true));
        form.add(new TextField<String>("language"));
        form.add(new TextArea<String>("description"));
        form.add(new CheckBox("createTag").setOutputMarkupId(true));

        form.add(new LambdaAjaxButton<>("save", this::actionSave));
        form.add(new LambdaAjaxLink("delete", this::actionDelete)
                .onConfigure(_this -> _this.setVisible(form.getModelObject().getId() != null)));
        form.add(new LambdaAjaxLink("cancel", this::actionCancel));

        BootstrapRadioChoice<String> format = new BootstrapRadioChoice<>("format", exportFormat,
                LoadableDetachableModel.of(this::supportedFormats));
        // The AjaxDownloadLink does not submit the form, so the format radio-buttons need to
        // submit themselves so their value is available when the export button is pressed
        format.add(onUpdateChoice(_target -> {
            // NO-OP
        }));
        form.add(format);

        form.add(new AjaxDownloadLink("export", LoadableDetachableModel.of(this::export)));

        confirmationDialog = new ConfirmationDialog("confirmationDialog");
        confirmationDialog.setTitleModel(new StringResourceModel("DeleteDialog.title", this));
        add(confirmationDialog);
    }

    private List<String> supportedFormats()
    {
        return asList(JSON_FORMAT, TAB_FORMAT);
    }

    private void actionSave(AjaxRequestTarget aTarget, Form<Tag> aForm)
    {
        if (isNull(selectedTagSet.getObject().getId())) {
            if (annotationSchemaService.existsTagSet(selectedTagSet.getObject().getName(),
                    selectedProject.getObject())) {
                error("Only one tagset per project is allowed!");
            }
        }

        selectedTagSet.getObject().setProject(selectedProject.getObject());
        annotationSchemaService.createTagSet(selectedTagSet.getObject());

        // Reload whole page because master panel also needs to be reloaded.
        aTarget.add(getPage());
    }

    private void actionDelete(AjaxRequestTarget aTarget)
    {
        confirmationDialog.setContentModel(new StringResourceModel("DeleteDialog.text", this)
                .setParameters(selectedTagSet.getObject().getName()));
        confirmationDialog.show(aTarget);

        confirmationDialog.setConfirmAction((_target) -> {
            // If the tagset is used in any features, clear the tagset on these features when
            // the tagset is deleted!
            for (AnnotationFeature ft : annotationSchemaService
                    .listAnnotationFeature(selectedProject.getObject())) {
                if (ft.getTagset() != null && ft.getTagset().equals(selectedTagSet.getObject())) {
                    ft.setTagset(null);
                    annotationSchemaService.createFeature(ft);
                }
            }

            annotationSchemaService.removeTagSet(selectedTagSet.getObject());

            _target.add(getPage());
            actionCancel(_target);
        });
    }

    private void actionCancel(AjaxRequestTarget aTarget)
    {
        selectedTagSet.setObject(null);
        selectedTag.setObject(null);

        // Reload whole page because master panel also needs to be reloaded.
        aTarget.add(getPage());
    }

    private FileResourceStream export()
    {
        File exportFile = null;
        if (exportFormat.getObject().equals(JSON_FORMAT)) {
            try {
                exportFile = File.createTempFile("exportedtagsets", ".json");
            }
            catch (IOException e1) {
                error("Unable to create temporary File!!");
                return null;

            }
            if (isNull(selectedTagSet.getObject().getId())) {
                error("Project not yet created. Please save project details first!");
            }
            else {
                TagSet tagSet = selectedTagSet.getObject();
                ExportedTagSet exTagSet = new ExportedTagSet();
                exTagSet.setDescription(tagSet.getDescription());
                exTagSet.setLanguage(tagSet.getLanguage());
                exTagSet.setName(tagSet.getName());

                List<ExportedTag> exportedTags = new ArrayList<>();
                for (Tag tag : annotationSchemaService.listTags(tagSet)) {
                    ExportedTag exportedTag = new ExportedTag();
                    exportedTag.setDescription(tag.getDescription());
                    exportedTag.setName(tag.getName());
                    exportedTags.add(exportedTag);

                }
                exTagSet.setTags(exportedTags);

                try {
                    JSONUtil.generatePrettyJson(exTagSet, exportFile);
                }
                catch (IOException e) {
                    error("File Path not found or No permision to save the file!");
                }

                info("TagSets successfully exported to :" + exportFile.getAbsolutePath());
            }
        }
        else if (exportFormat.getObject().equals(TAB_FORMAT)) {
            TagSet tagSet = selectedTagSet.getObject();
            try {
                exportFile = File.createTempFile("exportedtagsets", ".txt");
            }
            catch (IOException e1) {
                error("Unable to create temporary File!!");
            }
            OutputStream os;
            OutputStreamWriter osw;
            BufferedWriter bw;
            try {
                String tagSetDescription = tagSet.getDescription() == null ? ""
                        : tagSet.getDescription();
                os = new FileOutputStream(exportFile);
                osw = new OutputStreamWriter(os, "UTF-8");
                bw = new BufferedWriter(osw);
                bw.write(tagSet.getName() + "\t" + tagSetDescription.replace("\n", "\\n") + "\n");
                bw.write(tagSet.getLanguage() + "\t" + " \n");
                for (Tag tag : annotationSchemaService.listTags(tagSet)) {
                    String tagDescription = tag.getDescription() == null ? ""
                            : tag.getDescription();
                    bw.write(tag.getName() + "\t" + tagDescription.replace("\n", "\\n") + "\n");
                }

                bw.flush();
                bw.close();
            }
            catch (FileNotFoundException e) {
                error("The file for export not found " + ExceptionUtils.getRootCauseMessage(e));
            }
            catch (UnsupportedEncodingException e) {
                error("Unsupported encoding " + ExceptionUtils.getRootCauseMessage(e));
            }
            catch (IOException e) {
                error(ExceptionUtils.getRootCause(e));
            }

        }
        return new FileResourceStream(exportFile);
    }

    private class TagSetExistsValidator
        implements IValidator<String>
    {
        private static final long serialVersionUID = -5775158293783334223L;

        @Override
        public void validate(IValidatable<String> aValidatable)
        {
            String newName = aValidatable.getValue();
            String oldName = aValidatable.getModel().getObject();
            if (!StringUtils.equals(newName, oldName) && isNotBlank(newName)
                    && annotationSchemaService.existsTagSet(newName, selectedProject.getObject())) {
                aValidatable.error(new ValidationError(
                        "Another tag set with the same name exists. Please try a different name"));
            }
        }
    }
}
