/*
 * 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 org.apache.uima.ruta.cde.ui;

import java.io.File;
import java.io.FileInputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.uima.UIMAFramework;
import org.apache.uima.cas.CAS;
import org.apache.uima.cas.Type;
import org.apache.uima.cas.TypeSystem;
import org.apache.uima.cas.impl.XmiCasDeserializer;
import org.apache.uima.resource.metadata.FsIndexDescription;
import org.apache.uima.resource.metadata.TypePriorities;
import org.apache.uima.resource.metadata.TypeSystemDescription;
import org.apache.uima.ruta.addons.RutaAddonsPlugin;
import org.apache.uima.ruta.cde.IRutaConstraint;
import org.apache.uima.ruta.cde.IRutaRuleConstraint;
import org.apache.uima.ruta.cde.utils.ConstraintData;
import org.apache.uima.ruta.cde.utils.DocumentData;
import org.apache.uima.ruta.cde.utils.EvaluationMeasures;
import org.apache.uima.ruta.testing.evaluator.ICasEvaluator;
import org.apache.uima.ruta.testing.preferences.TestingPreferenceConstants;
import org.apache.uima.util.CasCreationUtils;
import org.apache.uima.util.XMLInputSource;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.IHandler;
import org.eclipse.core.commands.IHandlerListener;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.handlers.HandlerUtil;

public class DocumentViewRunHandler implements IHandler {

  private class DocumentViewRunJobChangeAdapter extends JobChangeAdapter {

    // Composite of the ConstraintSelectView
    private DocumentSelectComposite composite;

    DocumentViewRunJobChangeAdapter(DocumentSelectComposite composite) {
      super();
      this.composite = composite;
    }

    @Override
    public void done(IJobChangeEvent event) {
      if (event.getResult().isOK()) {
        updateUI(composite);
      }
    }

  }

  private void updateUI(final DocumentSelectComposite composite) {
    composite.getDisplay().asyncExec(new Runnable() {
      public void run() {
        try {
          composite.getViewer().refresh();
          ArrayList<Double[]> results = new ArrayList<Double[]>();
          if (composite.getTestDataPath() != null) {
            for (DocumentData data : composite.getDocumentList()) {
              double result = data.getAugmentedResult();
              double score = data.getFMeasure();
              results.add(new Double[] { result, score });
            }
          }
          composite.updateMeasureReport("" + EvaluationMeasures.getMeasureReport(results));
        } catch (Exception e) {
          RutaAddonsPlugin.error(e);
        }
        composite.getViewer().refresh();
        composite.update();
      }
    });
  }

  private class DocumentViewRunHandlerJob extends Job {

    private List<ConstraintData> constraints;

    private List<DocumentData> documents;

    private File typeSystem;

    private String testDataPath;

    private final DocumentSelectComposite composite;

    DocumentViewRunHandlerJob(ExecutionEvent event, List<ConstraintData> constraints,
            ArrayList<DocumentData> documents, File typeSystem, String testDataPath,
            DocumentSelectComposite composite) {
      super("UIMA Ruta CDE");
      this.constraints = constraints;
      this.documents = documents;
      this.typeSystem = typeSystem;
      this.testDataPath = testDataPath;
      this.composite = composite;
    }

    public IStatus run(IProgressMonitor monitor) {

      monitor.beginTask("UIMA Ruta CDE", constraints.size() * documents.size());
      CAS inputCAS = null;
      CAS testCas = null;
      try {
        URL tpUrl = this.getClass().getResource("/org/apache/uima/ruta/engine/TypePriorities.xml");
        TypePriorities typePriorities = UIMAFramework.getXMLParser().parseTypePriorities(
                new XMLInputSource(tpUrl));
        TypeSystemDescription descriptor = (TypeSystemDescription) UIMAFramework.getXMLParser()
                .parse(new XMLInputSource(typeSystem));
        inputCAS = CasCreationUtils
                .createCas(descriptor, typePriorities, new FsIndexDescription[0]);
        testCas = CasCreationUtils.createCas(descriptor, null, new FsIndexDescription[0]);
      } catch (Exception e) {
        RutaAddonsPlugin.error(e);
      }
      for (DocumentData document : documents) {
        if (monitor.isCanceled()) {
          return Status.CANCEL_STATUS;
        }
        double count = 0;
        double augResult = 0;
        document.setResults(new ArrayList<String[]>());
        if (constraints.size() > 0) {
          monitor.setTaskName("UIMA Ruta CDE: " + document.getDocument().getName());
          try {
            inputCAS.reset();
            XmiCasDeserializer.deserialize(new FileInputStream(document.getDocument()), inputCAS,
                    true);
            for (ConstraintData constraintData : constraints) {
              if (monitor.isCanceled()) {
                return Status.CANCEL_STATUS;
              }
              String[] partialResult = new String[2];
              IRutaConstraint constraint = constraintData.getConstraint();
              if (constraint instanceof IRutaRuleConstraint) {
                ((IRutaRuleConstraint) constraint).setTypeSystemLocation(typeSystem
                        .getAbsolutePath());
              }
              // Calculating and adding results to the
              // documentData object
              Double partResult = constraint.processConstraint(inputCAS);
              if (partResult != null) {
                partialResult[0] = constraint.getDescription();
                partialResult[1] = String.valueOf(partResult);
                int weight = constraintData.getWeight();
                count += weight;
                double weightedResult = partResult * weight;
                augResult += weightedResult;
                // inputCAS.release();
                document.getResults().add(partialResult);
              }

              monitor.worked(1);
            }

            if (testDataPath != null) {
              // Hashmap to save all the FileNames without
              // fileExtension and FullPath, both as
              // strings
              HashMap<String, String> testFilesMap = createFileMap(testDataPath);
              String documentFileName = getFileNameWithoutExtensions(document.getDocument()
                      .getAbsolutePath());
              if (testFilesMap.get(documentFileName) != null) {
                File testFile = new File(testFilesMap.get(documentFileName));
                testCas.reset();
                XmiCasDeserializer.deserialize(new FileInputStream(testFile), testCas, true);
                IPreferenceStore store = RutaAddonsPlugin.getDefault().getPreferenceStore();
                String factoryName = store.getString(TestingPreferenceConstants.EVALUATOR_FACTORY);
                boolean includeSubtypes = store
                        .getBoolean(TestingPreferenceConstants.INCLUDE_SUBTYPES);
                boolean useAllTypes = store.getBoolean(TestingPreferenceConstants.ALL_TYPES);

                ICasEvaluator evaluator = RutaAddonsPlugin.getCasEvaluatorFactoryById(factoryName)
                        .createEvaluator();
                ArrayList<String> excludedTypes = new ArrayList<String>();
                CAS resultCas = evaluator.evaluate(testCas, inputCAS, excludedTypes,
                        includeSubtypes, useAllTypes);
                document.setFMeasure(calculateF1(resultCas));
              }
            }
          } catch (Exception e) {
            RutaAddonsPlugin.error(e);
          }
        }
        if (count != 0) {
          augResult = augResult / count;
        }
        document.setAugmentedResult(augResult);
        updateUI(composite);
      }
      inputCAS.release();
      testCas.release();
      monitor.done();
      return Status.OK_STATUS;
    }
  }

  public void addHandlerListener(IHandlerListener arg0) {

  }

  public void dispose() {

  }

  public Object execute(ExecutionEvent event) throws ExecutionException {
    ConstraintSelectView constraintView;
    try {
      constraintView = (ConstraintSelectView) HandlerUtil.getActiveWorkbenchWindow(event)
              .getWorkbench().getActiveWorkbenchWindow().getActivePage()
              .showView("org.apache.uima.ruta.cde.ui.ConstraintSelectView");
      ConstraintSelectComposite composite = (ConstraintSelectComposite) constraintView
              .getComposite();
      List<ConstraintData> constraintList = composite.getConstraintList();
      DocumentView docView = (DocumentView) HandlerUtil.getActiveWorkbenchWindow(event)
              .getWorkbench().getActiveWorkbenchWindow().getActivePage()
              .showView("org.apache.uima.ruta.cde.ui.DocumentView");
      DocumentSelectComposite docComposite = docView.getDocComposite();
      ArrayList<DocumentData> documents = docComposite.getDocumentList();
      if (documents.isEmpty()) {
        docComposite.setDocumentsByDir();
      }
      File typeSystem = docComposite.getTypeSystem();
      DocumentViewRunHandlerJob job = new DocumentViewRunHandlerJob(event, constraintList,
              documents, typeSystem, docComposite.getTestDataPath(), docComposite);
      job.addJobChangeListener(new DocumentViewRunJobChangeAdapter(docComposite));
      job.schedule();
    } catch (PartInitException e) {
      RutaAddonsPlugin.error(e);
    }

    return null;
  }

  public boolean isEnabled() {
    return true;
  }

  public boolean isHandled() {
    return true;
  }

  public void removeHandlerListener(IHandlerListener arg0) {

  }

  private double calculateF1(CAS resultCas) {
    TypeSystem ts = resultCas.getTypeSystem();

    Type falsePositiveType = ts.getType(ICasEvaluator.FALSE_POSITIVE);
    Type falseNegativeType = ts.getType(ICasEvaluator.FALSE_NEGATIVE);
    Type truePositiveType = ts.getType(ICasEvaluator.TRUE_POSITIVE);

    int falsePositiveCount = resultCas.getAnnotationIndex(falsePositiveType).size();
    int falseNegativeCount = resultCas.getAnnotationIndex(falseNegativeType).size();
    int truePositiveCount = resultCas.getAnnotationIndex(truePositiveType).size();

    double a = falsePositiveCount;
    double b = falseNegativeCount;
    double c = truePositiveCount;

    double precision = c / (c + a);
    double recall = c / (c + b);
    double fMeasure = 2 * (precision * recall) / (precision + recall);

    fMeasure = fMeasure * 10000;
    fMeasure = Math.round(fMeasure);
    fMeasure = fMeasure / 10000;

    return fMeasure;
  }

  public HashMap<String, String> createFileMap(String directoryFolderPath) {
    HashMap<String, String> filesMap = new HashMap<String, String>();
    File folder = new File(directoryFolderPath);
    if (folder.isDirectory()) {
      String[] files = folder.list(null);
      for (String fileName : files) {
        String fullPath = folder + System.getProperty("file.separator") + fileName;
        String fileNameWithoutExtension = getFileNameWithoutExtensions(fullPath);
        if (!StringUtils.isBlank(fileNameWithoutExtension)) {
          filesMap.put(fileNameWithoutExtension, fullPath);
        }
      }
    }
    return filesMap;
  }

  public String getFileNameWithoutExtensions(String path) {
    File f = new File(path);
    String fileNameNoExtension = "";
    // Check if the file for the given Path exists on the systems filesystem
    // and
    // if it is a file
    if (f.exists() && f.isFile()) {
      String name = f.getName();
      int lastIndexOf = name.lastIndexOf(".");
      if (lastIndexOf != -1) {
        fileNameNoExtension = name.substring(0, lastIndexOf);
      } else {
        fileNameNoExtension = name;
      }
    }
    return fileNameNoExtension;
  }

}
