package com.adobe.epubcheck.stress;

import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.IOException;
import java.util.Iterator;

import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.junit.Ignore;
import org.junit.Test;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.adobe.epubcheck.api.EpubCheck;
import com.adobe.epubcheck.util.Archive;
import com.adobe.epubcheck.util.ValidationReport;
import com.google.common.io.Files;

public class StressTest
{

  /*
    * Keeping the test method commented out to not accidentally become part of
    * "run all..." efforts. To run, - set the TEMP_DIR_PATH and GROW
    * parameters, make sure the temp directory exists and is empty - uncomment
    * and run.
    */
  String TEMP_DIR_PATH = "/Users/mgylling/ec_temp/";
  int GROW = 2000;
  String STRING_FMT = "%05d";

  XPathFactory factory = XPathFactory.newInstance();
  XPath xpath = factory.newXPath();

  String OPF_NS = "http://www.idpf.org/2007/opf";
  String XHT_NS = "http://www.w3.org/1999/xhtml";
  File TEMP_DIR;
  File SOURCE_DIR = new File(StressTest.class.getResource("/stress/")
      .getFile());
  File opfFile;
  Document opfDoc;
  Element manifest;
  Element spine;
  File navFile;
  Document navDoc;
  Element navOlElem;
  File TEMP_CONTENT_DIR;
  int counter = 0;

  /*
  @Ignore
  @Test
  public void testHighFileCount() throws Exception
  {
    setup();
    try
    {
      outWriter.println("Building high file count stress test epub...");
      build();
      outWriter.println("Created epub has " +
          manifest.getChildNodes().getLength() + " manifest items.");
      outWriter.println("Validating...");
      validate();
      outWriter.println("done.");
    }
    finally
    {
      cleanDir(TEMP_DIR);
      File[] files = TEMP_DIR.listFiles();
      if (files != null && files.length > 0)
      {
        System.err.println("temp dir clean failed");
      }
    }
  }
  */
  private void validate() throws Exception
  {
    Archive epub = new Archive(TEMP_DIR.getPath());
    epub.createArchive();
    ValidationReport report = new ValidationReport(TEMP_DIR.getName());
    EpubCheck epubCheck = new EpubCheck(epub.getEpubFile(), report);
    epubCheck.validate();
    assertTrue(report.getErrorCount() < 1);
    assertTrue(report.getWarningCount() < 1);
    epub.deleteEpubFile();
    // outWriter.println(report);
  }

  private void build() throws Exception
  {
    cleanDir(TEMP_DIR);
    TEMP_DIR.delete();

    doCopyDirectory(SOURCE_DIR, TEMP_DIR);
    TEMP_CONTENT_DIR = new File(TEMP_DIR, "EPUB");
    // new File(TEMP_CONTENT_DIR, "META-INF").delete(); // bug in phloc copy

    opfFile = new File(TEMP_CONTENT_DIR, "package.opf");
    opfDoc = build(opfFile);
    manifest = (Element) opfDoc.getDocumentElement()
        .getElementsByTagName("manifest").item(0);
    spine = (Element) opfDoc.getDocumentElement()
        .getElementsByTagName("spine").item(0);

    navFile = new File(TEMP_CONTENT_DIR, "nav.xhtml");
    navDoc = build(navFile);
    navOlElem = (Element) xpath.evaluate("//xht:nav[1]/xht:ol",
        navDoc.getDocumentElement(), XPathConstants.NODE);

    for (int i = 0; i < GROW; i++)
    {
      counter++;

      String newImageName = "image_" + String.format(STRING_FMT, counter)
          + ".png";
      Files.copy(new File(TEMP_CONTENT_DIR,
          "image_00000.png"),
          new File(TEMP_CONTENT_DIR, newImageName));
      appendManifestItem(newImageName, "image/png", "img_");

      String newCssName = "style_" + String.format(STRING_FMT, counter)
          + ".css";
      Files.copy(new File(TEMP_CONTENT_DIR,
          "style_00000.css"), new File(TEMP_CONTENT_DIR, newCssName));
      appendManifestItem(newCssName, "text/css", "css_");

      String newXhtName = "content_" + String.format(STRING_FMT, counter)
          + ".xhtml";
      Document xhtDoc = build(new File(TEMP_CONTENT_DIR,
          "content_00000.xhtml"));
      xhtDoc = tweakXht(xhtDoc);
      appendManifestItem(newXhtName, "application/xhtml+xml", "t_");
      appendSpineItem("t_" + String.format(STRING_FMT, counter));
      File xhtOut = new File(TEMP_CONTENT_DIR, newXhtName);
      save(xhtDoc, xhtOut);

      // add new content doc to nav doc
      Element li = navDoc.createElementNS(XHT_NS, "li");
      Element a = navDoc.createElementNS(XHT_NS, "a");
      Attr href = navDoc.createAttribute("href");
      href.appendChild(navDoc.createTextNode(newXhtName));
      a.getAttributes().setNamedItem(href);
      a.appendChild(navDoc.createTextNode("Lorem Ipsum "
          + String.format(STRING_FMT, counter)));
      li.appendChild(a);
      navOlElem.appendChild(li);

    }

    save(navDoc, navFile);
    save(opfDoc, opfFile);
  }

  private void doCopyDirectory(File srcDir, File destDir) throws Exception
  {
    File[] srcFiles = srcDir.listFiles();
    if (srcFiles == null)
    {
      throw new IOException("Failed to list contents of " + srcDir);
    }
    if (destDir.exists())
    {
      if (!destDir.isDirectory())
      {
        throw new IOException("Destination '" + destDir + "' exists but is not a directory");
      }
    }
    else
    {
      if (!destDir.mkdirs() && !destDir.isDirectory())
      {
        throw new IOException("Destination '" + destDir + "' directory cannot be created");
      }
    }
    if (!destDir.canWrite())
    {
      throw new IOException("Destination '" + destDir + "' cannot be written to");
    }

    for (File srcFile : srcFiles)
    {
      File dstFile = new File(destDir, srcFile.getName());
      if (srcFile.isDirectory())
      {
        doCopyDirectory(srcFile, dstFile);
      }
      else
      {
        Files.copy(srcFile, dstFile);
      }

    }

  }

  private Document tweakXht(Document xhtDoc) throws Exception
  {
    // <link type="text/css" rel="stylesheet" href="style_00000.css" />
    Element link = (Element) xpath.evaluate("//xht:link[1]",
        xhtDoc.getDocumentElement(), XPathConstants.NODE);
    Attr href = link.getAttributeNode("href");
    href.setNodeValue("style_" + String.format(STRING_FMT, counter)
        + ".css");

    // <h1>Lorem Ipsum 00000</h1>
    Element h1 = (Element) xpath.evaluate("//xht:h1[1]",
        xhtDoc.getDocumentElement(), XPathConstants.NODE);
    h1.removeChild(h1.getFirstChild());
    h1.appendChild(xhtDoc.createTextNode("Lorem Ipsum "
        + String.format(STRING_FMT, counter)));

    // <img src="image_00000.png"/>
    Element img = (Element) xpath.evaluate("//xht:img[1]",
        xhtDoc.getDocumentElement(), XPathConstants.NODE);
    Attr src = img.getAttributeNode("src");
    src.setNodeValue("image_" + String.format(STRING_FMT, counter) + ".png");

    return xhtDoc;

  }

  private void appendSpineItem(String id)
  {
    Element itemref = opfDoc.createElementNS(OPF_NS, "itemref");
    Attr idref = opfDoc.createAttribute("idref");
    idref.appendChild(opfDoc.createTextNode(id));
    itemref.getAttributes().setNamedItem(idref);

    spine.appendChild(itemref);
  }

  private void appendManifestItem(String ref, String mimeType, String idPfx)
  {
    Element item = opfDoc.createElementNS(OPF_NS, "item");

    Attr id = opfDoc.createAttribute("id");
    id.appendChild(opfDoc.createTextNode(idPfx
        + String.format(STRING_FMT, counter)));
    item.getAttributes().setNamedItem(id);

    Attr mt = opfDoc.createAttribute("media-type");
    mt.appendChild(opfDoc.createTextNode(mimeType));
    item.getAttributes().setNamedItem(mt);

    Attr href = opfDoc.createAttribute("href");
    href.appendChild(opfDoc.createTextNode(ref));
    item.getAttributes().setNamedItem(href);

    manifest.appendChild(item);

  }

  private void save(Document doc, File out) throws Exception
  {
    // some error with saxon on the classpath
    System.setProperty("javax.xml.transform.TransformerFactory",
        "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
    TransformerFactory transformerFactory = TransformerFactory
        .newInstance();
    Transformer transformer = transformerFactory.newTransformer();

    // TransformerFactory transformerFactory =
    // com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl.newInstance();
    // Transformer transformer = new
    // com.sun.org.apache.xalan.internal.xsltc.trax.Transformer();
    DOMSource source = new DOMSource(doc);
    StreamResult streamResult = new StreamResult(out);
    transformer.setOutputProperty(OutputKeys.INDENT, "yes");
    transformer.transform(source, streamResult);
  }

  private Document build(File f) throws Exception
  {
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setNamespaceAware(true);
    dbf.setValidating(false);
    return dbf.newDocumentBuilder().parse(f);
  }

  private void setup()
  {

    TEMP_DIR = new File(TEMP_DIR_PATH);
    if (!TEMP_DIR.exists())
    {
      throw new IllegalArgumentException("Path " + TEMP_DIR_PATH
          + " does not exist");
    }
    if (!TEMP_DIR.isDirectory())
    {
      throw new IllegalArgumentException("Path " + TEMP_DIR_PATH
          + " is not a directory");
    }
    if (TEMP_DIR.listFiles().length > 0)
    {
      throw new IllegalArgumentException("Directory " + TEMP_DIR_PATH
          + " is not empty");
    }
    if (!SOURCE_DIR.exists() || !SOURCE_DIR.isDirectory())
    {
      throw new IllegalArgumentException("Source directory not found");
    }

    xpath.setNamespaceContext(new NamespaceContext()
    {

      @Override
      public String getNamespaceURI(String prefix)
      {
        if (prefix.equals("xht"))
        {
          return XHT_NS;
        }
        return null;
      }

      @Override
      public String getPrefix(String namespaceURI)
      {
        if (namespaceURI.equals(XHT_NS))
        {
          return "xht";
        }
        return null;
      }

      @Override
      public Iterator getPrefixes(String namespaceURI)
      {
        throw new UnsupportedOperationException();
      }
    });

  }

  private void cleanDir(File path)
  {
    File[] files = path.listFiles();
    if (files != null)
    {
      for (File f : files)
      {
        if (f.isDirectory())
        {
          cleanDir(f);
          f.delete();
        }
        else
        {
          f.delete();
        }
      }
      if (!path.equals(TEMP_DIR))
      {
        path.delete();
      }
    }
  }
}
