/*
 * Copyright (C) 2003-2008 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 */
package org.exoplatform.ecm.webui.viewer;

import org.exoplatform.download.DownloadService;
import org.exoplatform.download.InputStreamDownloadResource;
import org.exoplatform.portal.webui.util.Util;
import org.exoplatform.portal.webui.workspace.UIPortalApplication;
import org.exoplatform.services.cms.mimetype.DMSMimeTypeResolver;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.pdfviewer.PDFViewerService;
import org.exoplatform.services.resources.ResourceBundleService;
import org.exoplatform.services.wcm.utils.WCMCoreUtils;
import org.exoplatform.web.application.RequireJS;
import org.exoplatform.webui.application.portlet.PortletRequestContext;
import org.exoplatform.webui.config.annotation.ComponentConfig;
import org.exoplatform.webui.config.annotation.EventConfig;
import org.exoplatform.webui.core.UIComponent;
import org.exoplatform.webui.core.lifecycle.UIFormLifecycle;
import org.exoplatform.webui.core.model.SelectItemOption;
import org.exoplatform.webui.event.Event;
import org.exoplatform.webui.event.Event.Phase;
import org.exoplatform.webui.event.EventListener;
import org.exoplatform.webui.form.UIForm;
import org.exoplatform.webui.form.UIFormSelectBox;
import org.exoplatform.webui.form.UIFormStringInput;
import org.icepdf.core.pobjects.Document;
import org.icepdf.core.pobjects.PInfo;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.portlet.PortletRequest;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

/**
 * Created by The eXo Platform SARL
 * Author : Dang Van Minh
 *          minh.dang@exoplatform.com
 * Aug 18, 2009
 * 3:49:41 AM
 */
@ComponentConfig(
    lifecycle = UIFormLifecycle.class,
    template = "classpath:resources/templates/PDFJSViewer.gtmpl",
    events = {
        @EventConfig(listeners = PDFViewer.NextPageActionListener.class, phase = Phase.DECODE),
        @EventConfig(listeners = PDFViewer.PreviousPageActionListener.class, phase = Phase.DECODE),
        @EventConfig(listeners = PDFViewer.GotoPageActionListener.class, phase = Phase.DECODE),
        @EventConfig(listeners = PDFViewer.RotateRightPageActionListener.class, phase = Phase.DECODE),
        @EventConfig(listeners = PDFViewer.RotateLeftPageActionListener.class, phase = Phase.DECODE),
        @EventConfig(listeners = PDFViewer.ScalePageActionListener.class, phase = Phase.DECODE),
        @EventConfig(listeners = PDFViewer.DownloadFileActionListener.class, phase = Phase.DECODE),
        @EventConfig(listeners = PDFViewer.ZoomInPageActionListener.class, phase = Phase.DECODE),
        @EventConfig(listeners = PDFViewer.ZoomOutPageActionListener.class, phase = Phase.DECODE)
    }
)
/**
 * PDF Viewer component which will be used to display PDF file on web browser
 */
public class PDFViewer extends UIForm {

  private static final Log LOG = ExoLogger.getExoLogger(PDFViewer.class);

  final static private String PAGE_NUMBER = "pageNumber";
  final static private String SCALE_PAGE = "scalePage";
  final private String localeFile = "locale.portlet.viewer.PDFViewer";
  private static final int MIN_IE_SUPPORTED_BROWSER_VERSION = 9;
  private static final int MIN_FF_SUPPORTED_BROWSER_VERSION = 20;
  private static final int MIN_CHROME_SUPPORTED_BROWSER_VERSION = 20;

  private int currentPageNumber_ = 1;
  private int maximumOfPage_ = 0;
  private float currentRotation_ = 0.0f;
  private float currentScale_ = 1.0f;
  private Map<String, String> metadatas = new HashMap<String, String>();

  public PDFViewer() throws Exception {
    addUIFormInput(new UIFormStringInput(PAGE_NUMBER, PAGE_NUMBER, "1"));
    UIFormSelectBox uiScaleBox = new UIFormSelectBox(SCALE_PAGE, SCALE_PAGE, initScaleOptions());
    uiScaleBox.setOnChange("ScalePage");
    addUIFormInput(uiScaleBox);
    uiScaleBox.setValue("1.0f");
  }

  public Method getMethod(UIComponent uiComponent, String name) throws NoSuchMethodException {
    return uiComponent.getClass().getMethod(name, new Class[0]);
  }

  public void initDatas() throws Exception {
    UIComponent uiParent = getParent();

    Method method = getMethod(uiParent, "getOriginalNode");
    Node originalNode = null;
    if(method != null) originalNode = (Node) method.invoke(uiParent, (Object[]) null);

    if(originalNode != null) {
      Document document = getDocument(originalNode);
      if (document != null) {
        maximumOfPage_ = document.getNumberOfPages();
        metadatas.clear();
        putDocumentInfo(document.getInfo());
        document.dispose();
      } else maximumOfPage_ = -1;
    }
  }

  public Map getMetadataExtraction() { return metadatas; }

  public int getMaximumOfPage() throws Exception {
    if(maximumOfPage_ == 0) {
      initDatas();
    }
    return maximumOfPage_;
  }

  public float getCurrentRotation() { return currentRotation_; }

  public void setRotation(float rotation) { currentRotation_ = rotation; }

  public float getCurrentScale() { return currentScale_; }

  public void setScale(float scale) { currentScale_ = scale; }

  public int getPageNumber() { return currentPageNumber_; }

  public void setPageNumber(int pageNum) { currentPageNumber_ = pageNum; };

  public String getResourceBundle(String key) {
    try {
      Locale locale = Util.getUIPortal().getAncestorOfType(UIPortalApplication.class).getLocale();
      ResourceBundleService resourceBundleService = WCMCoreUtils.getService(ResourceBundleService.class);
      ResourceBundle resourceBundle = resourceBundleService.getResourceBundle(localeFile, locale, this.getClass().getClassLoader());

      return resourceBundle.getString(key);
    } catch (MissingResourceException e) {
      return key;
    }
  }

  private Document getDocument(Node node) throws RepositoryException, Exception {
    PDFViewerService pdfViewerService = getApplicationComponent(PDFViewerService.class);
    String repository = (String) getMethod(this.getParent(), "getRepository").invoke(this.getParent(), (Object[]) null);
    return pdfViewerService.initDocument(node, repository);
  }

  private void putDocumentInfo(PInfo documentInfo) {
    if (documentInfo != null) {
      if(documentInfo.getTitle() != null && documentInfo.getTitle().length() > 0) {
        metadatas.put("title", documentInfo.getTitle());
      }
      if(documentInfo.getAuthor() != null && documentInfo.getAuthor().length() > 0) {
        metadatas.put("author", documentInfo.getAuthor());
      }
      if(documentInfo.getSubject() != null && documentInfo.getSubject().length() > 0) {
        metadatas.put("subject", documentInfo.getSubject());
      }
      if(documentInfo.getKeywords() != null && documentInfo.getKeywords().length() > 0) {
        metadatas.put("keyWords", documentInfo.getKeywords());
      }
      if(documentInfo.getCreator() != null && documentInfo.getCreator().length() > 0) {
        metadatas.put("creator", documentInfo.getCreator());
      }
      if(documentInfo.getProducer() != null && documentInfo.getProducer().length() > 0) {
        metadatas.put("producer", documentInfo.getProducer());
      }
      try {
        if(documentInfo.getCreationDate() != null) {
          metadatas.put("creationDate", documentInfo.getCreationDate().toString());
        }
      } catch (Exception e) {
        LOG.debug("Error when getting creation date.", e);
      }
      try {
        if(documentInfo.getModDate() != null) {
          metadatas.put("modDate", documentInfo.getModDate().toString());
        }
      } catch (Exception e) {
        LOG.debug("Exception when getting modification date.", e);
      }
    }
  }

  private List<SelectItemOption<String>> initScaleOptions() {
    List<SelectItemOption<String>> scaleOptions = new ArrayList<SelectItemOption<String>>();
    scaleOptions.add(new SelectItemOption<String>("5%",  "0.05f"));
    scaleOptions.add(new SelectItemOption<String>("10%",  "0.1f"));
    scaleOptions.add(new SelectItemOption<String>("25%",  "0.25f"));
    scaleOptions.add(new SelectItemOption<String>("50%",  "0.5f"));
    scaleOptions.add(new SelectItemOption<String>("75%",  "0.75f"));
    scaleOptions.add(new SelectItemOption<String>("100%",  "1.0f"));
    scaleOptions.add(new SelectItemOption<String>("125%",  "1.25f"));
    scaleOptions.add(new SelectItemOption<String>("150%",  "1.5f"));
    scaleOptions.add(new SelectItemOption<String>("200%",  "2.0f"));
    scaleOptions.add(new SelectItemOption<String>("300%",  "3.0f"));
    return scaleOptions;
  }


  /**
   * Check if client has modern browser (IE9+, FF20+, Chrome 20+).
   */
  private boolean isNotModernBrowser() {
    PortletRequestContext requestContext = PortletRequestContext.getCurrentInstance();
    PortletRequest portletRequest = requestContext.getRequest();
    String userAgent = portletRequest.getProperty("user-agent");
    boolean isChrome = (userAgent.indexOf("Chrome/") != -1);
    boolean isMSIE = (userAgent.indexOf("MSIE") != -1);
    boolean isFirefox = (userAgent.indexOf("Firefox/") != -1);
    String version = "1";
    if (isFirefox) {
      // Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:13.0) Gecko/20100101 Firefox/13.0
      version = userAgent.replaceAll("^.*?Firefox/", "").replaceAll("\\.\\d+", "");
    } else if (isChrome) {
      // Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.52 Safari/536.5
      version = userAgent.replaceAll("^.*?Chrome/(\\d+)\\..*$", "$1");
    } else if (isMSIE) {
      // Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C; .NET4.0E)
      version = userAgent.replaceAll("^.*?MSIE\\s+(\\d+).*$", "$1");
    }

    boolean unsupportedBrowser = (isFirefox && Integer.parseInt(version) < MIN_FF_SUPPORTED_BROWSER_VERSION)
            || (isChrome && Integer.parseInt(version) < MIN_CHROME_SUPPORTED_BROWSER_VERSION)
            || (isMSIE && Integer.parseInt(version) < MIN_IE_SUPPORTED_BROWSER_VERSION);
    return unsupportedBrowser;
  }

  static public class PreviousPageActionListener extends EventListener<PDFViewer> {
    public void execute(Event<PDFViewer> event) throws Exception {
      PDFViewer pdfViewer = event.getSource();
      if(pdfViewer.currentPageNumber_ == 1) {
        pdfViewer.getUIStringInput(PAGE_NUMBER).setValue(
            Integer.toString((pdfViewer.currentPageNumber_)));
      } else {
        pdfViewer.getUIStringInput(PAGE_NUMBER).setValue(
            Integer.toString((pdfViewer.currentPageNumber_ -1)));
        pdfViewer.setPageNumber(pdfViewer.currentPageNumber_ - 1);
      }
      event.getRequestContext().addUIComponentToUpdateByAjax(pdfViewer);
    }
  }

  static public class NextPageActionListener extends EventListener<PDFViewer> {
    public void execute(Event<PDFViewer> event) throws Exception {
      PDFViewer pdfViewer = event.getSource();
      if(pdfViewer.currentPageNumber_ == pdfViewer.maximumOfPage_) {
        pdfViewer.getUIStringInput(PAGE_NUMBER).setValue(
            Integer.toString((pdfViewer.currentPageNumber_)));
      } else {
        pdfViewer.getUIStringInput(PAGE_NUMBER).setValue(
            Integer.toString((pdfViewer.currentPageNumber_ + 1)));
        pdfViewer.setPageNumber(pdfViewer.currentPageNumber_ + 1);
      }
      event.getRequestContext().addUIComponentToUpdateByAjax(pdfViewer);
    }
  }

  static public class GotoPageActionListener extends EventListener<PDFViewer> {
    public void execute(Event<PDFViewer> event) throws Exception {
      PDFViewer pdfViewer = event.getSource();
      String pageStr = pdfViewer.getUIStringInput(PAGE_NUMBER).getValue();
      int pageNumber = 1;
      try {
        pageNumber = Integer.parseInt(pageStr);
      } catch(NumberFormatException e) {
        pageNumber = pdfViewer.currentPageNumber_;
      }
      if(pageNumber >= pdfViewer.maximumOfPage_) pageNumber = pdfViewer.maximumOfPage_;
      else if(pageNumber < 1) pageNumber = 1;
      pdfViewer.getUIStringInput(PAGE_NUMBER).setValue(Integer.toString((pageNumber)));
      pdfViewer.setPageNumber(pageNumber);
      event.getRequestContext().addUIComponentToUpdateByAjax(pdfViewer);
    }
  }

  static public class RotateRightPageActionListener extends EventListener<PDFViewer> {
    public void execute(Event<PDFViewer> event) throws Exception {
      PDFViewer pdfViewer = event.getSource();
      pdfViewer.setRotation(pdfViewer.currentRotation_ + 270.0f);
      event.getRequestContext().addUIComponentToUpdateByAjax(pdfViewer);
    }
  }

  static public class RotateLeftPageActionListener extends EventListener<PDFViewer> {
    public void execute(Event<PDFViewer> event) throws Exception {
      PDFViewer pdfViewer = event.getSource();
      pdfViewer.setRotation(pdfViewer.currentRotation_ + 90.0f);
      event.getRequestContext().addUIComponentToUpdateByAjax(pdfViewer);
    }
  }

  static public class ScalePageActionListener extends EventListener<PDFViewer> {
    public void execute(Event<PDFViewer> event) throws Exception {
      PDFViewer pdfViewer = event.getSource();
      String scale = pdfViewer.getUIFormSelectBox(SCALE_PAGE).getValue();
      pdfViewer.setScale(Float.parseFloat(scale));
      event.getRequestContext().addUIComponentToUpdateByAjax(pdfViewer);
    }
  }

  static public class DownloadFileActionListener extends EventListener<PDFViewer> {
    public void execute(Event<PDFViewer> event) throws Exception {
      PDFViewer pdfViewer = event.getSource();
      UIComponent uiParent = pdfViewer.getParent();
      Method methodGetNode = pdfViewer.getMethod(uiParent, "getNode");
      Node node = (Node)methodGetNode.invoke(uiParent, (Object[]) null);
      node = getFileLangNode(node);
      String repository = (String) pdfViewer.getMethod(uiParent, "getRepository").invoke(uiParent, (Object[]) null);
      PDFViewerService pdfViewerService = pdfViewer.getApplicationComponent(PDFViewerService.class);
      File file = pdfViewerService.getPDFDocumentFile(node, repository);
      String fileName = node.getName();
      int index = fileName.lastIndexOf('.');
      if (index < 0) {
        fileName += ".pdf";
      } else if (index == fileName.length() - 1) {
        fileName += "pdf";
      } else {
        String extension = fileName.substring(index + 1);
        fileName = fileName.replace(extension, "pdf");
      }
      DownloadService dservice = pdfViewer.getApplicationComponent(DownloadService.class) ;
      InputStreamDownloadResource dresource = new InputStreamDownloadResource(
          new BufferedInputStream(new FileInputStream(file)), DMSMimeTypeResolver.getInstance().getMimeType(".pdf"));
      dresource.setDownloadName(fileName) ;
      String downloadLink = dservice.getDownloadLink(dservice.addDownloadResource(dresource)) ;

      RequireJS requireJS = event.getRequestContext().getJavascriptManager().getRequireJS();
      requireJS.require("SHARED/ecm-utils", "ecmutil").addScripts("ecmutil.ECMUtils.ajaxRedirect('" + downloadLink + "');");
      event.getRequestContext().addUIComponentToUpdateByAjax(pdfViewer);
    }
  }

  static public class ZoomInPageActionListener extends EventListener<PDFViewer> {
    public void execute(Event<PDFViewer> event) throws Exception {
      PDFViewer pdfViewer = event.getSource();
      String[] arrValue = {"0.05f", "0.1f", "0.25f", "0.5f", "0.75f", "1.0f",
          "1.25f", "1.5f", "2.0f", "3.0f"};
      String scale = pdfViewer.getUIFormSelectBox(SCALE_PAGE).getValue();
      if(scale.equals(arrValue[arrValue.length - 1])) return;
      for(int i = 0; i < arrValue.length - 1; i++) {
        if(scale.equals(arrValue[i])) {
          pdfViewer.setScale(Float.parseFloat(arrValue[i + 1]));
          pdfViewer.getUIFormSelectBox(SCALE_PAGE).setValue(arrValue[i + 1]);
          break;
        }
      }
      event.getRequestContext().addUIComponentToUpdateByAjax(pdfViewer);
    }
  }

  static public class ZoomOutPageActionListener extends EventListener<PDFViewer> {
    public void execute(Event<PDFViewer> event) throws Exception {
      PDFViewer pdfViewer = event.getSource();
      String scale = pdfViewer.getUIFormSelectBox(SCALE_PAGE).getValue();
      String[] arrValue = {"0.05f", "0.1f", "0.25f", "0.5f", "0.75f", "1.0f",
          "1.25f", "1.5f", "2.0f", "3.0f"};
      if(scale.equals(arrValue[0])) return;
      for(int i = 0; i < arrValue.length - 1; i++) {
        if(scale.equals(arrValue[i])) {
          pdfViewer.setScale(Float.parseFloat(arrValue[i - 1]));
          pdfViewer.getUIFormSelectBox(SCALE_PAGE).setValue(arrValue[i - 1]);
          break;
        }
      }
      event.getRequestContext().addUIComponentToUpdateByAjax(pdfViewer);
    }
  }
  static public Node getFileLangNode(Node currentNode) throws Exception {
    if(currentNode.isNodeType("nt:unstructured")) {
      if(currentNode.getNodes().getSize() > 0) {
        NodeIterator nodeIter = currentNode.getNodes() ;
        while(nodeIter.hasNext()) {
          Node ntFile = nodeIter.nextNode() ;
          if(ntFile.isNodeType("nt:file")) {
            return ntFile ;
          }
        }
        return currentNode ;
      }
    }
    return currentNode ;
  }
  
  public String getActionOpenDocInDesktop() {
    UIComponent uiParent = getParent();
    String ret = "";
    try {
      Method method = getMethod(uiParent, "getActionOpenDocInDesktop");
      if(method != null) ret = (String) method.invoke(uiParent, (Object[]) null);
    } catch (NoSuchMethodException| IllegalArgumentException| IllegalAccessException| InvocationTargetException e) {
      ret = "";
    }
    return ret;
  }
}
