/* ====================================================================
   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.poi.examples.hpsf;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.poi.hpsf.HPSFRuntimeException;
import org.apache.poi.hpsf.NoPropertySetStreamException;
import org.apache.poi.hpsf.PropertySet;
import org.apache.poi.hpsf.PropertySetFactory;
import org.apache.poi.hpsf.Section;
import org.apache.poi.hpsf.SummaryInformation;
import org.apache.poi.hpsf.Variant;
import org.apache.poi.hpsf.WritingNotSupportedException;
import org.apache.poi.hpsf.wellknown.PropertyIDMap;
import org.apache.poi.poifs.eventfilesystem.POIFSReader;
import org.apache.poi.poifs.eventfilesystem.POIFSReaderEvent;
import org.apache.poi.poifs.filesystem.DirectoryEntry;
import org.apache.poi.poifs.filesystem.DocumentInputStream;
import org.apache.poi.poifs.filesystem.POIFSDocumentPath;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;

/**
 * <p>This class is a sample application which shows how to write or modify the
 * author and title property of an OLE 2 document. This could be done in two
 * different ways:</p>
 *
 * <ul>
 *
 * <li><p>The first approach is to open the OLE 2 file as a POI filesystem
 * (see class {@link POIFSFileSystem}), read the summary information property
 * set (see classes {@link SummaryInformation} and {@link PropertySet}), write
 * the author and title properties into it and write the property set back into
 * the POI filesystem.</p></li>
 *
 * <li><p>The second approach does not modify the original POI filesystem, but
 * instead creates a new one. All documents from the original POIFS are copied
 * to the destination POIFS, except for the summary information stream. The
 * latter is modified by setting the author and title property before writing
 * it to the destination POIFS. It there are several summary information streams
 * in the original POIFS - e.g. in subordinate directories - they are modified
 * just the same.</p></li>
 *
 * </ul>
 *
 * <p>This sample application takes the second approach. It expects the name of
 * the existing POI filesystem's name as its first command-line parameter and
 * the name of the output POIFS as the second command-line argument. The
 * program then works as described above: It copies nearly all documents
 * unmodified from the input POI filesystem to the output POI filesystem. If it
 * encounters a summary information stream it reads its properties. Then it sets
 * the "author" and "title" properties to new values and writes the modified
 * summary information stream into the output file.</p>
 *
 * <p>Further explanations can be found in the HPSF HOW-TO.</p>
 */
@SuppressWarnings({"java:S106","java:S4823"})
public final class WriteAuthorAndTitle {
    private WriteAuthorAndTitle() {}

    /**
     * <p>Runs the example program.</p>
     *
     * @param args Command-line arguments. The first command-line argument must
     * be the name of a POI filesystem to read.
     * @throws IOException if any I/O exception occurs.
     */
    public static void main(final String[] args) throws IOException {
        /* Check whether we have exactly two command-line arguments. */
        if (args.length != 2) {
            System.err.println("Usage: WriteAuthorAndTitle originPOIFS destinationPOIFS");
            System.exit(1);
        }

        /* Read the names of the origin and destination POI filesystems. */
        final String srcName = args[0];
        final String dstName = args[1];

        /* Read the origin POIFS using the eventing API. The real work is done
         * in the class ModifySICopyTheRest which is registered here as a
         * POIFSReader. */
        try (POIFSFileSystem poifs = new POIFSFileSystem();
             OutputStream out = new FileOutputStream(dstName)) {
            final POIFSReader r = new POIFSReader();
            r.registerListener(e -> handleEvent(poifs, e));
            r.read(new File(srcName));

            /* Write the new POIFS to disk. */
            poifs.writeFilesystem(out);
        }
    }

    private interface InputStreamSupplier {
        InputStream get() throws IOException, WritingNotSupportedException;
    }

    /**
     * The method is called by POI's eventing API for each file in the origin POIFS.
     */
    private static void handleEvent(final POIFSFileSystem poiFs, final POIFSReaderEvent event) {
        // The following declarations are shortcuts for accessing the "event" object.
        final DocumentInputStream stream = event.getStream();

        try {
            final InputStreamSupplier isSup;

            // Find out whether the current document is a property set stream or not.
            if (PropertySet.isPropertySetStream(stream)) {
                // Yes, the current document is a property set stream. Let's create a PropertySet instance from it.
                PropertySet ps = PropertySetFactory.create(stream);

                // Now we know that we really have a property set.
                // The next step is to find out whether it is a summary information or not.
                if (ps.isSummaryInformation()) {
                    // Create a mutable property set as a copy of the original read-only property set.
                    ps = new PropertySet(ps);

                    // Retrieve the section containing the properties to modify.
                    // A summary information property set contains exactly one section.
                    final Section s = ps.getSections().get(0);

                    // Set the properties.
                    s.setProperty(PropertyIDMap.PID_AUTHOR, Variant.VT_LPSTR, "Rainer Klute");
                    s.setProperty(PropertyIDMap.PID_TITLE, Variant.VT_LPWSTR, "Test");
                }

                isSup = ps::toInputStream;
            } else {
                // No, the current document is not a property set stream. We copy it unmodified to the destination POIFS.
                isSup = event::getStream;
            }

            try (InputStream is = isSup.get()) {
                final POIFSDocumentPath path = event.getPath();

                // Ensures that the directory hierarchy for a document in a POI fileystem is in place.
                // Get the root directory. It does not have to be created since it always exists in a POIFS.
                DirectoryEntry de = poiFs.getRoot();

                for (int i=0; i<path.length(); i++) {
                    String subDir = path.getComponent(i);
                    de = (de.hasEntryCaseInsensitive(subDir)) ? (DirectoryEntry)de.getEntryCaseInsensitive(subDir) : de.createDirectory(subDir);
                }

                de.createDocument(event.getName(), is);
            }

        } catch (WritingNotSupportedException | IOException | NoPropertySetStreamException ex) {
            // According to the definition of the processPOIFSReaderEvent method we cannot pass checked
            // exceptions to the caller.
            throw new HPSFRuntimeException("Could not read file " + event.getPath() + "/" + event.getName(), ex);
        }
    }
}
