/*
 * 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.ide.ui.documentation;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.uima.internal.util.XMLUtils;
import org.apache.uima.ruta.ide.ui.RutaPreferenceConstants;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.ui.DLTKPluginImages;
import org.eclipse.dltk.ui.dialogs.TimeTriggeredProgressMonitorDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Label;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class RutaPagesLocationsBlock implements SelectionListener, ISelectionChangedListener {

  protected static final String LAST_PATH_SETTING = "LAST_PATH_SETTING"; //$NON-NLS-1$

  protected static final String DIALOG_SETTINGS_PREFIX = "RutaPagesLocationsBlock"; //$NON-NLS-1$

  protected boolean fInCallback = false;

  protected File fHome;

  // widgets
  protected TreeViewer fLocationsViewer;

  private Button fClearButton;

  private Button fRemoveButton;

  private Button fAddButton;

  protected Button fDefaultButton;

  private ManLocationsContentProvider fLocationsContentProvider;

  private PreferencePage fPage;

  private IPreferenceStore fStore;

  public RutaPagesLocationsBlock(IPreferenceStore store, PreferencePage page) {
    fPage = page;
    fStore = store;
  }

  protected IBaseLabelProvider getLabelProvider() {
    return new LabelProvider() {

      @Override
      public Image getImage(Object element) {
        if (element instanceof RutaPageFolder) {
          return DLTKPluginImages.DESC_OBJS_LIBRARY.createImage();
        }
        return DLTKPluginImages.DESC_OBJS_INFO_OBJ.createImage();
      }

      @Override
      public String getText(Object element) {
        if (element instanceof RutaPageFolder) {
          RutaPageFolder folder = (RutaPageFolder) element;
          return folder.getPath();
        }
        return super.getText(element);
      }

    };
  }

  private List folders = null;

  private String getFoldersAsXML() {
    if (folders == null)
      return null;

    // Create the Document and the top-level node
    DocumentBuilderFactory documentBuilderFactory = XMLUtils.createDocumentBuilderFactory();
    DocumentBuilder builder;
    try {
      builder = documentBuilderFactory.newDocumentBuilder();
    } catch (ParserConfigurationException e) {
      if (DLTKCore.DEBUG) {
        e.printStackTrace();
      }
      return null;
    }
    Document doc = builder.newDocument();

    Element topElement = doc.createElement("manPages");
    doc.appendChild(topElement);

    for (Iterator iterator = folders.iterator(); iterator.hasNext();) {
      RutaPageFolder f = (RutaPageFolder) iterator.next();
      Element location = doc.createElement("location");
      topElement.appendChild(location);
      location.setAttribute("path", f.getPath());
      for (Iterator iterator2 = f.getPages().keySet().iterator(); iterator2.hasNext();) {
        String name = (String) iterator2.next();
        String file = (String) f.getPages().get(name);
        Element page = doc.createElement("page");
        location.appendChild(page);
        page.setAttribute("keyword", name);
        page.setAttribute("file", file);
      }
    }

    ByteArrayOutputStream s = new ByteArrayOutputStream();

    try {
      TransformerFactory factory = XMLUtils.createTransformerFactory();
      Transformer transformer = factory.newTransformer();
      transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$
      transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$

      DOMSource source = new DOMSource(doc);
      StreamResult outputTarget = new StreamResult(s);
      transformer.transform(source, outputTarget);
    } catch (TransformerException e) {
      if (DLTKCore.DEBUG) {
        e.printStackTrace();
      }
    }

    String result = null;
    try {
      result = s.toString("UTF8");
    } catch (UnsupportedEncodingException e) {
      if (DLTKCore.DEBUG) {
        e.printStackTrace();
      }
    }

    return result;
  }

  private class ManLocationsContentProvider implements ITreeContentProvider {

    @Override
    public Object[] getChildren(Object parentElement) {
      if (parentElement instanceof RutaPageFolder) {
        RutaPageFolder folder = (RutaPageFolder) parentElement;
        String[] ch = new String[folder.getPages().size()];
        int i = 0;
        for (Iterator iterator = folder.getPages().keySet().iterator(); iterator.hasNext();) {
          String kw = (String) iterator.next();
          String file = (String) folder.getPages().get(kw);
          ch[i++] = kw + " (" + file + ")";
        }
        return ch;
      }
      return new Object[0];
    }

    @Override
    public Object getParent(Object element) {
      return null;
    }

    @Override
    public boolean hasChildren(Object element) {
      if (element instanceof RutaPageFolder)
        return true;
      return false;
    }

    @Override
    public Object[] getElements(Object inputElement) {
      if (folders == null)
        return new Object[0];
      return folders.toArray(new Object[folders.size()]);
    }

    @Override
    public void dispose() {
    }

    @Override
    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
    }

  }

  public Control createControl(Composite parent) {
    Font font = parent.getFont();

    Composite comp = new Composite(parent, SWT.NONE);
    GridLayout topLayout = new GridLayout();
    topLayout.numColumns = 2;
    topLayout.marginHeight = 0;
    topLayout.marginWidth = 0;
    comp.setLayout(topLayout);
    GridData gd = new GridData(GridData.FILL_BOTH);
    comp.setLayoutData(gd);

    fLocationsViewer = new TreeViewer(comp);
    gd = new GridData(GridData.FILL_BOTH);
    gd.heightHint = 6;
    fLocationsViewer.getControl().setLayoutData(gd);
    fLocationsContentProvider = new ManLocationsContentProvider();
    fLocationsViewer.setSorter(new ViewerSorter());
    fLocationsViewer.setContentProvider(fLocationsContentProvider);
    fLocationsViewer.setLabelProvider(getLabelProvider());
    fLocationsViewer.setInput(this);
    fLocationsViewer.addSelectionChangedListener(this);

    Composite pathButtonComp = new Composite(comp, SWT.NONE);
    GridLayout pathButtonLayout = new GridLayout();
    pathButtonLayout.marginHeight = 0;
    pathButtonLayout.marginWidth = 0;
    pathButtonComp.setLayout(pathButtonLayout);
    gd = new GridData(GridData.VERTICAL_ALIGN_BEGINNING | GridData.HORIZONTAL_ALIGN_FILL);
    pathButtonComp.setLayoutData(gd);
    pathButtonComp.setFont(font);

    fAddButton = createPushButton(pathButtonComp, "Add folder...");
    fAddButton.addSelectionListener(this);

    fRemoveButton = createPushButton(pathButtonComp, "Remove");
    fRemoveButton.addSelectionListener(this);

    fClearButton = createPushButton(pathButtonComp, "Remove All");
    fClearButton.addSelectionListener(this);

    return comp;
  }

  protected Button createPushButton(Composite parent, String label) {
    Button button = new Button(parent, SWT.PUSH);
    button.setFont(parent.getFont());
    button.setText(label);
    setButtonLayoutData(button);
    return button;
  }

  protected void setButtonLayoutData(Button button) {
    GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
    int widthHint = 80;
    Point minSize = button.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
    data.widthHint = Math.max(widthHint, minSize.x);
    button.setLayoutData(data);
  }

  protected void createVerticalSpacer(Composite comp, int colSpan) {
    Label label = new Label(comp, SWT.NONE);
    GridData gd = new GridData();
    gd.horizontalSpan = colSpan;
    label.setLayoutData(gd);
  }

  public void update() {
    updateButtons();

    if (folders != null) {
      for (Iterator iterator = folders.iterator(); iterator.hasNext();) {
        RutaPageFolder v = (RutaPageFolder) iterator.next();
        if (!v.verify()) {
          iterator.remove();
        }
      }
    }

    fLocationsViewer.refresh();

    updatePageStatus(Status.OK_STATUS);
  }

  public void setDefaults() {
    String res = fStore.getDefaultString(RutaPreferenceConstants.DOC_RUTA_PAGES_LOCATIONS);
    fStore.setValue(RutaPreferenceConstants.DOC_RUTA_PAGES_LOCATIONS, res);
    initialize();
  }

  protected void updatePageStatus(IStatus status) {
    if (fPage == null)
      return;
    fPage.setValid(status.isOK());
    if (!status.isOK())
      fPage.setErrorMessage(status.getMessage());
    else
      fPage.setErrorMessage(null);
  }

  public void initialize() {
    String value = fStore.getString(RutaPreferenceConstants.DOC_RUTA_PAGES_LOCATIONS);
    try {
      this.folders = RutaPageFolder.readXML(value);
    } catch (IOException e) {
      e.printStackTrace();
    }

    update();
  }

  /**
   * Saves settings
   */
  public void performApply() {
    String xml = this.getFoldersAsXML();
    if (xml != null)
      fStore.setValue(RutaPreferenceConstants.DOC_RUTA_PAGES_LOCATIONS, xml);
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
   */
  @Override
  public void widgetSelected(SelectionEvent e) {
    Object source = e.getSource();
    if (source == fClearButton) {
      folders.clear();
    } else if (source == fRemoveButton) {
      IStructuredSelection selection = (IStructuredSelection) fLocationsViewer.getSelection();
      Object[] array = selection.toArray();
      for (int i = 0; i < array.length; i++) {
        if (array[i] instanceof RutaPageFolder) {
          for (Iterator iterator = folders.iterator(); iterator.hasNext();) {
            RutaPageFolder f = (RutaPageFolder) iterator.next();
            if (f == array[i]) {
              iterator.remove();
              break;
            }
          }
        }
      }
    } else if (source == fAddButton) {
      add();
    }

    update();
  }

  /*
   * (non-Javadoc)
   * 
   * @seeorg.eclipse.swt.events.SelectionListener#widgetDefaultSelected(org.eclipse.swt.events.
   * SelectionEvent)
   */
  @Override
  public void widgetDefaultSelected(SelectionEvent e) {
  }

  protected void add() {
    DirectoryDialog dialog = new DirectoryDialog(fLocationsViewer.getControl().getShell());
    dialog.setMessage("Select directory to search into");
    String result = dialog.open();
    if (result != null) {
      final File file = new File(result);
      if (this.folders == null)
        this.folders = new ArrayList();
      if (file != null && file.isDirectory()) {
        ProgressMonitorDialog dialog2 = new TimeTriggeredProgressMonitorDialog(null, 500);
        try {
          dialog2.run(true, true, new IRunnableWithProgress() {
            @Override
            public void run(IProgressMonitor monitor) {
              monitor.beginTask("Searching for man pages", 1);
              performSearch(file);
              monitor.done();
            }
          });
        } catch (InvocationTargetException e) {
          e.printStackTrace();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }

      }
    }
  }

  private void performSearch(File dir) {
    if (!dir.isDirectory())
      return;

    String name = dir.getName();

    if (name.equals("TkLib") || name.equals("RutaLib") || name.equals("Keywords")
            || name.equals("UserCmd"))
      return;

    File[] childs = dir.listFiles(new FileFilter() {

      @Override
      public boolean accept(File file) {
        if (file.isDirectory())
          return true;
        if (file.getName().startsWith("contents.htm"))
          return true;
        return false;
      }

    });
    for (int i = 0; i < childs.length; i++) {
      if (childs[i].isDirectory()) {
        performSearch(childs[i]);
      }
      if (childs[i].getName().startsWith("contents.htm")) {
        RutaPageFolder folder = new RutaPageFolder(dir.getAbsolutePath());
        parseContentsFile(childs[i], folder);
        if (folder.getPages().size() > 0 && !folders.contains(folder)) {
          this.folders.add(folder);
        }
      }
    }
  }

  private void parseContentsFile(File c, RutaPageFolder folder) {
    FileReader reader;
    try {
      reader = new FileReader(c);
    } catch (FileNotFoundException e) {
      return;
    }
    StringBuffer buf = new StringBuffer();
    while (true) {
      char cbuf[] = new char[1024];
      try {
        int read = reader.read(cbuf);
        if (read >= 0) {
          buf.append(cbuf, 0, read);
        } else
          break;
      } catch (IOException e) {
        break;
      }
    }
    String result = buf.toString();
    Pattern pattern = Pattern.compile("<a\\s+href=\"([a-zA-Z_0-9]+\\.html?)\"\\s*>(\\w+)</a>",
            Pattern.CASE_INSENSITIVE);
    Matcher matcher = pattern.matcher(result);
    while (matcher.find()) {
      String file = matcher.group(1);
      if (file.equalsIgnoreCase("Copyright.htm"))
        continue;
      String word = matcher.group(2);
      folder.addPage(word, file);
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(org.eclipse.jface.viewers
   * .SelectionChangedEvent)
   */
  @Override
  public void selectionChanged(SelectionChangedEvent event) {
    updateButtons();
  }

  private void updateButtons() {
    fClearButton.setEnabled(folders != null && folders.size() > 0);
    IStructuredSelection selection = (IStructuredSelection) fLocationsViewer.getSelection();

    boolean canRemove = true;
    if (folders == null)
      canRemove = false;
    else {
      List list = selection.toList();
      for (Iterator iterator = list.iterator(); iterator.hasNext();) {
        Object o = iterator.next();
        if (!folders.contains(o))
          canRemove = false;
        break;
      }
      if (selection.isEmpty())
        canRemove = false;
    }

    fRemoveButton.setEnabled(canRemove);
  }

}
