/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2012, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.patching.runner;

import static org.jboss.as.patching.Constants.BASE;
import static org.jboss.as.patching.IoUtils.mkdir;
import static org.jboss.as.patching.IoUtils.safeClose;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Properties;

import org.jboss.as.patching.Constants;
import org.jboss.as.patching.DirectoryStructure;
import org.jboss.as.patching.HashUtils;
import org.jboss.as.patching.installation.PatchableTarget;
import org.jboss.as.patching.metadata.ModuleItem;

/**
 * @author Emanuel Muckenhuber
 */
public final class PatchUtils {

    public static final String JAR_EXT = ".jar";
    public static final String BACKUP_EXT = ".jar.patched";

    public static String readRef(final Properties properties, final String name) {
        final String ref = (String) properties.get(name);
        if(ref == null) {
            return Constants.BASE;
        }
        return ref;
    }

    public static List<String> readRefs(final Properties properties) {
        return readRefs(properties, Constants.PATCHES);
    }

    public static List<String> readRefs(final Properties properties, final String property) {
        String layersProp = (String) properties.get(property);
        if (layersProp == null || (layersProp = layersProp.trim()).length() == 0) {
            return Collections.emptyList();
        } else {
            final String[] names = layersProp.split(",");
            final List<String> patches = new ArrayList<String>();
            for (final String name : names) {
                patches.add(name);
            }
            return Collections.unmodifiableList(patches);
        }
    }

    public static String asString(final List<String> values) {
        final StringBuilder builder = new StringBuilder();
        for (final String value : values) {
            builder.append(value);
            builder.append(',');
        }
        if (builder.length() > 0) {
            builder.setLength(builder.length() - 1);
        }
        return builder.toString();
    }

    public static String readRef(final File file) throws IOException {
        if(! file.exists()) {
            return Constants.BASE;
        }
        final InputStream is = new FileInputStream(file);
        try {
            return readRef(is);
        } finally {
            safeClose(is);
        }
    }

    public static List<String> readRefs(final File file) throws IOException {
        if(! file.exists()) {
            return Collections.emptyList();
        }
        final InputStream is = new FileInputStream(file);
        try {
            return readRefs(is);
        } finally {
            safeClose(is);
        }
    }

    static String readRef(final InputStream is) throws IOException {
        final StringBuffer buffer = new StringBuffer();
        readLine(is, buffer);
        return buffer.toString();
    }

    static List<String> readRefs(final InputStream is) throws IOException {
        final List<String> refs = new ArrayList<String>();
        final StringBuffer buffer = new StringBuffer();
        do {
            if(buffer.length() > 0) {
                final String ref = buffer.toString().trim();
                if(ref.length() > 0) {
                    refs.add(ref);
                }
            }
        } while(readLine(is, buffer));
        return refs;
    }

    public static void writeRef(final File file, final String ref) throws IOException {
        mkdir(file.getParentFile());
        final OutputStream os = new FileOutputStream(file);
        try {
            writeLine(os, ref);
            os.flush();
            os.close();
        } finally {
            safeClose(os);
        }
    }

    public static void writeRefs(final File file, final List<String> refs, boolean append) throws IOException {
        mkdir(file.getParentFile());
        final OutputStream os = new FileOutputStream(file, append);
        try {
            writeRefs(os, refs);
            os.flush();
            os.close();
        } finally {
            safeClose(os);
        }
    }

    public static void writeRefs(final File file, final List<String> refs) throws IOException {
        writeRefs(file, refs, false);
    }

    static void writeRefs(final OutputStream os, final List<String> refs) throws IOException {
        for(final String ref : refs) {
            writeLine(os, ref);
        }
    }

    static void writeLine(final OutputStream os, final String s) throws IOException {
        os.write(s.getBytes(StandardCharsets.UTF_8));
        os.write('\n');
    }

    static boolean readLine(InputStream is, StringBuffer buffer) throws IOException {
        buffer.setLength(0);
        int c;
        for(;;) {
            c = is.read();
            switch(c) {
                case '\t':
                case '\r':
                    break;
                case -1: return false;
                case '\n': return true;
                default: buffer.append((char) c);
            }
        }
    }

    // FIXME do we need to i18nize the timestamp?
    static String generateTimestamp() {
        return DateFormat.getInstance().format(new Date());
    }

    static File[] getModulePath(final DirectoryStructure structure, final PatchableTarget.TargetInfo info) {
        final List<File> path = new ArrayList<File>();
        final List<String> patches = info.getPatchIDs();
        for (final String patch : patches) {
            path.add(structure.getModulePatchDirectory(patch));
        }
        final String ref = info.getCumulativePatchID();
        if (!BASE.equals(ref)) {
            path.add(structure.getModulePatchDirectory(ref));
        }
        path.add(structure.getModuleRoot());
        return path.toArray(new File[path.size()]);
    }

    static File[] getBundlePath(final DirectoryStructure structure, final PatchableTarget.TargetInfo info) {
        final List<String> patches = info.getPatchIDs();
        final List<File> path = new ArrayList<File>();
        for (final String patch : patches) {
            path.add(structure.getBundlesPatchDirectory(patch));
        }
        final String ref = info.getCumulativePatchID();
        if (!BASE.equals(ref)) {
            path.add(structure.getBundlesPatchDirectory(ref));
        }
        path.add(structure.getBundleRepositoryRoot());
        return path.toArray(new File[path.size()]);
    }

    public static void writeProperties(final File file, final Properties properties) throws IOException {
        final OutputStream os = new FileOutputStream(file);
        try {
            final Writer writer = new OutputStreamWriter(os, StandardCharsets.UTF_8);
            properties.store(writer, "read only");
            writer.close();
        } finally {
            safeClose(os);
        }
    }

    public static Properties loadProperties(final File file) throws IOException {
        if (! file.exists()) {
            return new Properties();
        }
        Reader reader = null;
        try {
            reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8);
            final Properties props = new Properties();
            props.load(reader);
            return props;
        } finally {
            safeClose(reader);
        }
    }

    public static File getRenamedFileName(final File file) {
        String fileName = file.getName();
        if (fileName.endsWith(BACKUP_EXT)) {
            return new File(file.getParentFile(), fileName.substring(0, fileName.length() - BACKUP_EXT.length()) + JAR_EXT);
        } else if (fileName.endsWith(JAR_EXT)) {
            return new File(file.getParentFile(), fileName.substring(0, fileName.length() - JAR_EXT.length()) + BACKUP_EXT);
        }
        return file;
    }

    public static byte[] getAbsentModuleContent(final ModuleItem item) {
        final StringBuilder builder = new StringBuilder(128);
        builder.append("<?xml version='1.0' encoding='UTF-8'?>\n<module-absent xmlns=\"urn:jboss:module:1.2\"");
        builder.append(" name=\"").append(item.getName()).append("\"");
        builder.append(" slot=\"").append(item.getSlot()).append("\"");
        builder.append(" />\n");
        return builder.toString().getBytes(StandardCharsets.UTF_8);
    }

    public static byte[] getAbsentModuleContentHash(final ModuleItem item) throws IOException {
        final byte[] content = getAbsentModuleContent(item);
        return HashUtils.hashBytes(content);
    }
}
