/*
 * 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.maven.index.packer;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.TimeZone;

import org.apache.maven.index.context.IndexingContext;
import org.apache.maven.index.incremental.IncrementalHandler;
import org.apache.maven.index.updater.IndexDataWriter;
import org.codehaus.plexus.util.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A default {@link IndexPacker} implementation. Creates the properties, legacy index zip and new gz files.
 *
 * @author Tamas Cservenak
 * @author Eugene Kuleshov
 */
@Singleton
@Named
public class DefaultIndexPacker implements IndexPacker {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    protected Logger getLogger() {
        return logger;
    }

    private final IncrementalHandler incrementalHandler;

    @Inject
    public DefaultIndexPacker(IncrementalHandler incrementalHandler) {
        this.incrementalHandler = incrementalHandler;
    }

    public void packIndex(IndexPackingRequest request) throws IOException, IllegalArgumentException {
        if (request.getTargetDir() == null) {
            throw new IllegalArgumentException("The target dir is null");
        }

        if (request.getTargetDir().exists()) {
            if (!request.getTargetDir().isDirectory()) {
                throw new IllegalArgumentException( //
                        String.format(
                                "Specified target path %s is not a directory",
                                request.getTargetDir().getAbsolutePath()));
            }
            if (!request.getTargetDir().canWrite()) {
                throw new IllegalArgumentException(String.format(
                        "Specified target path %s is not writtable",
                        request.getTargetDir().getAbsolutePath()));
            }
        } else {
            if (!request.getTargetDir().mkdirs()) {
                throw new IllegalArgumentException(
                        "Can't create " + request.getTargetDir().getAbsolutePath());
            }
        }

        // These are all of the files we'll be dealing with (except for the incremental chunks of course)
        File v1File = new File(request.getTargetDir(), IndexingContext.INDEX_FILE_PREFIX + ".gz");

        Properties info;

        try {
            // Note that for incremental indexes to work properly, a valid index.properties file
            // must be present
            info = readIndexProperties(request);

            if (request.isCreateIncrementalChunks()) {
                List<Integer> chunk = incrementalHandler.getIncrementalUpdates(request, info);

                if (chunk == null) {
                    getLogger().debug("Problem with Chunks, forcing regeneration of whole index");
                    incrementalHandler.initializeProperties(info);
                } else if (chunk.isEmpty()) {
                    getLogger().debug("No incremental changes, not writing new incremental chunk");
                } else {
                    File file = new File(
                            request.getTargetDir(), //
                            IndexingContext.INDEX_FILE_PREFIX + "."
                                    + info.getProperty(IndexingContext.INDEX_CHUNK_COUNTER) + ".gz");

                    writeIndexData(request, chunk, file);

                    if (request.isCreateChecksumFiles()) {
                        FileUtils.fileWrite(
                                new File(file.getParentFile(), file.getName() + ".sha1").getAbsolutePath(),
                                DigesterUtils.getSha1Digest(file));

                        FileUtils.fileWrite(
                                new File(file.getParentFile(), file.getName() + ".md5").getAbsolutePath(),
                                DigesterUtils.getMd5Digest(file));
                    }
                }
            }
        } catch (IOException e) {
            getLogger().info("Unable to read properties file, will force index regeneration");
            info = new Properties();
            incrementalHandler.initializeProperties(info);
        }

        Date timestamp = request.getContext().getTimestamp();

        if (timestamp == null) {
            timestamp = new Date(0); // never updated
        }

        if (request.getFormats().contains(IndexPackingRequest.IndexFormat.FORMAT_V1)) {
            info.setProperty(IndexingContext.INDEX_TIMESTAMP, format(timestamp));

            writeIndexData(request, null, v1File);

            if (request.isCreateChecksumFiles()) {
                FileUtils.fileWrite(
                        new File(v1File.getParentFile(), v1File.getName() + ".sha1").getAbsolutePath(),
                        DigesterUtils.getSha1Digest(v1File));

                FileUtils.fileWrite(
                        new File(v1File.getParentFile(), v1File.getName() + ".md5").getAbsolutePath(),
                        DigesterUtils.getMd5Digest(v1File));
            }
        }

        writeIndexProperties(request, info);
    }

    private Properties readIndexProperties(IndexPackingRequest request) throws IOException {
        File file;

        if (request.isUseTargetProperties() || request.getContext().getIndexDirectoryFile() == null) {
            file = new File(request.getTargetDir(), IndexingContext.INDEX_REMOTE_PROPERTIES_FILE);
        } else {
            file = new File(request.getContext().getIndexDirectoryFile(), IndexingContext.INDEX_PACKER_PROPERTIES_FILE);
        }

        Properties properties = new Properties();

        try (FileInputStream fos = new FileInputStream(file)) {
            properties.load(fos);
        }

        return properties;
    }

    void writeIndexData(IndexPackingRequest request, List<Integer> docIndexes, File targetArchive) throws IOException {
        if (targetArchive.exists()) {
            targetArchive.delete();
        }

        try (OutputStream os = new FileOutputStream(targetArchive)) {
            IndexDataWriter dw = new IndexDataWriter(os);
            dw.write(request.getContext(), request.getIndexReader(), docIndexes);

            os.flush();
        }
    }

    void writeIndexProperties(IndexPackingRequest request, Properties info) throws IOException {
        File propertyFile =
                new File(request.getContext().getIndexDirectoryFile(), IndexingContext.INDEX_PACKER_PROPERTIES_FILE);
        File targetPropertyFile = new File(request.getTargetDir(), IndexingContext.INDEX_REMOTE_PROPERTIES_FILE);

        info.setProperty(IndexingContext.INDEX_ID, request.getContext().getId());

        try (OutputStream os = new FileOutputStream(propertyFile)) {
            info.store(os, null);
        }

        try (OutputStream os = new FileOutputStream(targetPropertyFile)) {
            info.store(os, null);
        }

        if (request.isCreateChecksumFiles()) {
            FileUtils.fileWrite(
                    new File(targetPropertyFile.getParentFile(), targetPropertyFile.getName() + ".sha1")
                            .getAbsolutePath(),
                    DigesterUtils.getSha1Digest(targetPropertyFile));

            FileUtils.fileWrite(
                    new File(targetPropertyFile.getParentFile(), targetPropertyFile.getName() + ".md5")
                            .getAbsolutePath(),
                    DigesterUtils.getMd5Digest(targetPropertyFile));
        }
    }

    private String format(Date d) {
        SimpleDateFormat df = new SimpleDateFormat(IndexingContext.INDEX_TIME_FORMAT);
        df.setTimeZone(TimeZone.getTimeZone("GMT"));
        return df.format(d);
    }
}
