/*
 * ProGuard -- shrinking, optimization, obfuscation, and preverification
 *             of Java bytecode.
 *
 * Copyright (c) 2002-2018 GuardSquare NV
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 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, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package proguard.io;

import proguard.util.MultiValueMap;

import java.io.*;
import java.util.*;

/**
 * This DataEntryWriter writes out all data entries to a delegate
 * DataEntryWriter, inserting additional data entries that are attached
 * to the written data entry. The output stream of the additional data
 * entry is not used.
 *
 * @author Eric Lafortune
 */
public class ExtraDataEntryWriter implements DataEntryWriter
{
    private final MultiValueMap<String, String> extraEntryNameMap;
    private final Set<String>                   extraEntryNamesWritten = new HashSet<String>();

    private final DataEntryWriter dataEntryWriter;
    private final DataEntryWriter extraDataEntryWriter;

    private final String entrySuffix;

    /**
     * Creates a new ExtraDataEntryWriter that writes one given extra data entry
     * together with the first data entry that is written.
     *
     * @param extraEntryName  the name of the extra data entry.
     * @param dataEntryWriter the writer to which the entries are
     *                        written, including the extra data entry.
     */
    public ExtraDataEntryWriter(String          extraEntryName,
                                DataEntryWriter dataEntryWriter)
    {
        this(extraEntryName, dataEntryWriter, dataEntryWriter);
    }


    /**
     * Creates a new ExtraDataEntryWriter that writes one given extra data entry
     * together with the first data entry that is written.
     *
     * @param extraEntryName       the name of the extra data entry.
     * @param dataEntryWriter      the writer to which the entries are
     *                             written.
     * @param extraDataEntryWriter the writer to which the extra data entry
     *                             will be written.
     */
    public ExtraDataEntryWriter(String          extraEntryName,
                                DataEntryWriter dataEntryWriter,
                                DataEntryWriter extraDataEntryWriter)
    {
        this(new MultiValueMap<String, String>(),
             dataEntryWriter,
             extraDataEntryWriter,
             null);
        extraEntryNameMap.put(null, extraEntryName);
    }

    /**
     * Creates a new ExtraDataEntryWriter.
     *
     * @param extraEntryNameMap    a map with data entry names and their
     *                             associated extra data entries. An extra
     *                             data entry that is associated with multiple
     *                             entries is only written once.
     * @param dataEntryWriter      the writer to which the entries are
     *                             written.
     * @param extraDataEntryWriter the writer to which the extra data entry
     *                             will be written.
     * @param entrySuffix          an optional file suffix. It is stripped
     *                             from the entry name when looking up the
     *                             entry in the map, and added to all extra
     *                             entry names.
     */
    public ExtraDataEntryWriter(MultiValueMap<String, String> extraEntryNameMap,
                                DataEntryWriter               dataEntryWriter,
                                DataEntryWriter               extraDataEntryWriter,
                                String                        entrySuffix          )
    {
        this.extraEntryNameMap    = extraEntryNameMap;
        this.dataEntryWriter      = dataEntryWriter;
        this.extraDataEntryWriter = extraDataEntryWriter;
        this.entrySuffix          = entrySuffix;
    }


    // Implementations for DataEntryWriter.

    public boolean createDirectory(DataEntry dataEntry) throws IOException
    {
        return dataEntryWriter.createDirectory(dataEntry);
    }


    public boolean sameOutputStream(DataEntry dataEntry1,
                                    DataEntry dataEntry2)
    throws IOException
    {
        return dataEntryWriter.sameOutputStream(dataEntry1, dataEntry2);
    }


    public OutputStream createOutputStream(DataEntry dataEntry) throws IOException
    {
        // Write all default extra entries.
        writeExtraEntries(dataEntry, null);

        // Write all extra entries attached to the current data entry.
        writeExtraEntries(dataEntry);

        // Delegate to write out the actual entry.
        return dataEntryWriter.createOutputStream(dataEntry);
    }


    private void writeExtraEntries(DataEntry dataEntry) throws IOException
    {
        String mapKey = dataEntry.getName();
        if (entrySuffix != null && mapKey.endsWith(entrySuffix))
        {
            mapKey = mapKey.substring(0, mapKey.length() - entrySuffix.length());
        }

        writeExtraEntries(dataEntry, mapKey);
    }


    private void writeExtraEntries(DataEntry   dataEntry,
                                   String      key) throws IOException
    {
        Set<String> extraEntryNames = extraEntryNameMap.get(key);
        if (extraEntryNames != null)
        {
            for (String extraEntryName : extraEntryNames)
            {
                if (!extraEntryNamesWritten.contains(extraEntryName))
                {
                    String           fullEntryName = entrySuffix != null ? extraEntryName + entrySuffix : extraEntryName;
                    RenamedDataEntry extraEntry    = new RenamedDataEntry(dataEntry, fullEntryName);
                    extraDataEntryWriter.createOutputStream(extraEntry);
                    extraEntryNamesWritten.add(extraEntryName);
                    writeExtraEntries(extraEntry);
                }
            }
        }
    }

    public void close() throws IOException
    {
        dataEntryWriter.close();
    }


    public void println(PrintWriter pw, String prefix)
    {
        pw.println(prefix + "ExtraDataEntryWriter");
        dataEntryWriter.println(pw, prefix + "  ");
    }
}
