/*
 * Copyright 2021 The Apache Software Foundation.
 *
 * Licensed 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.tomcat.vestige.installer;

import java.io.BufferedReader;
import java.io.BufferedWriter;
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.net.URL;
import java.util.List;

import org.apache.commons.io.IOUtils;

/**
 * @author Gael Lalire
 */
public class TomcatVestigeInstaller {

    private File base;

    public TomcatVestigeInstaller(final File base) {
        this.base = base;
    }

    public File getBase() {
        return base;
    }

    public void getPreResources(FileAdder fileAdder) throws IOException {
        fileAdder.addDirectory("pre-resources");
    }

    public void getWork(FileAdder fileAdder) throws IOException {
        fileAdder.addDirectory("work");
    }

    public void getWebapps(FileAdder fileAdder) throws IOException {
        fileAdder.addDirectory("webapps" + File.separator + "ROOT");
    }

    public void getConf(FileAdder fileAdder) throws IOException {
        InstallerUtils.addFiles(TomcatVestigeInstaller.class, "conf.txt", fileAdder);
    }
    
    public void getRoot(FileAdder fileAdder) throws IOException {
        InstallerUtils.addFiles(TomcatVestigeInstaller.class, "root/", "root.txt", fileAdder);
    }

    public void getAll(FileAdder fileAdder) throws IOException {
        getRoot(fileAdder);
        getWork(fileAdder);
        getPreResources(fileAdder);
        getWebapps(fileAdder);
        getConf(fileAdder);
    }

    public void create(File base, String suffix) throws IOException {
        FileAdder creatorFileAdder = new FileAdder() {

            @Override
            public void addFile(String pathFromBase, URL url, boolean textFile) throws IOException {
                File file = new File(base, pathFromBase + suffix);
                file.getParentFile().mkdirs();
                if (textFile) {
                    BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
                    try {
                        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8"));
                        try {
                            String line = bufferedReader.readLine();
                            while (line != null) {
                                bufferedWriter.write(line);
                                bufferedWriter.newLine();
                                line = bufferedReader.readLine();
                            }
                        } finally {
                            bufferedReader.close();
                        }
                    } finally {
                        bufferedWriter.close();
                    }
                } else {
                    OutputStream out = new FileOutputStream(file);
                    try {
                        InputStream inputStream = url.openStream();
                        try {
                            IOUtils.copy(inputStream, out);
                        } finally {
                            inputStream.close();
                        }
                    } finally {
                        out.close();
                    }
                }
            }

            @Override
            public void addDirectory(String pathFromBase) throws IOException {
                File file = new File(base, pathFromBase);
                file.mkdirs();
            }
        };
        getAll(creatorFileAdder);
    }

    public void install() throws Exception {
        create(base, "");
    }

    public void updateFiles(File updateDirectory) throws IOException {
        // when this method is called the new version has been extracted with
        // .tmp suffix in update directory
        // our classloader contain the old version, we need to compare our
        // version with the one in base

        FileAdder updaterFileAdder = new FileAdder() {

            @Override
            public void addFile(String pathFromBase, URL url, boolean textFile) throws IOException {
                // this method is called with the old version, the new version
                // is already in newFile if it exists
                File newFile = new File(updateDirectory, pathFromBase + ".tmp");
                File entryDestination = new File(base, pathFromBase);
                if (!newFile.exists()) {
                    // installer removed the file
                    File parentFile = newFile.getParentFile();
                    parentFile.mkdirs();
                    File tmpDel = new File(parentFile, newFile.getName().concat("del"));
                    tmpDel.createNewFile();
                    return;
                } else if (!entryDestination.exists()) {
                    // user removed the file, do nothing
                    newFile.delete();
                    return;
                }
                
                boolean same = false;
                if (textFile) {
                    FileInputStream fileInputStream = new FileInputStream(entryDestination);
                    try {
                        InputStream inputStream = url.openStream();
                        try {
                            if (IOUtils.contentEqualsIgnoreEOL(new InputStreamReader(fileInputStream, "UTF-8"), new InputStreamReader(inputStream, "UTF-8"))) {
                                same = true;
                            }
                        } finally {
                            inputStream.close();
                        }
                    } finally {
                        fileInputStream.close();
                    }
                } else {
                    FileInputStream fileInputStream = new FileInputStream(entryDestination);
                    try {
                        InputStream inputStream = url.openStream();
                        try {
                            if (IOUtils.contentEquals(fileInputStream, inputStream)) {
                                same = true;
                            }
                        } finally {
                            inputStream.close();
                        }
                    } finally {
                        fileInputStream.close();
                    }
                }
                if (same) {
                    File tmpRep = new File(newFile.getParentFile(), newFile.getName().concat("rep"));
                    newFile.renameTo(tmpRep);
                } else {
                    boolean unchanged = false;
                    if (textFile) {
                        FileInputStream fileInputStream = new FileInputStream(newFile);
                        try {
                            InputStream inputStream = url.openStream();
                            try {
                                if (IOUtils.contentEqualsIgnoreEOL(new InputStreamReader(fileInputStream, "UTF-8"), new InputStreamReader(inputStream, "UTF-8"))) {
                                    unchanged = true;
                                }
                            } finally {
                                inputStream.close();
                            }
                        } finally {
                            fileInputStream.close();
                        }
                    } else {
                        FileInputStream fileInputStream = new FileInputStream(newFile);
                        try {
                            InputStream inputStream = url.openStream();
                            try {
                                if (IOUtils.contentEquals(fileInputStream, inputStream)) {
                                    unchanged = true;
                                }
                            } finally {
                                inputStream.close();
                            }
                        } finally {
                            fileInputStream.close();
                        }
                    }
                    if (unchanged) {
                        newFile.delete();
                    } else {
                        File tmpNew = new File(newFile.getParentFile(), newFile.getName().concat("new"));
                        newFile.renameTo(tmpNew);
                    }
                }                
            }

            @Override
            public void addDirectory(String pathFromBase) throws IOException {
            }
        };
        getAll(updaterFileAdder);
    }

    // we are the version replacing fromVersion
    public void prepareMigrateFrom(final List<Integer> fromVersion, Object fromVersionInstaller) throws Exception {
        File updateDirectory = new File(base, "update");
        updateDirectory.mkdirs();

        create(updateDirectory, ".tmp");
        fromVersionInstaller.getClass().getMethod("updateFiles", File.class).invoke(fromVersionInstaller, updateDirectory);
    }

    // our version will be replaced by toVersion
    public void prepareMigrateTo(final List<Integer> toVersion, Object toVersionInstaller) throws Exception {
        File updateDirectory = new File(base, "update");
        updateDirectory.mkdirs();

        toVersionInstaller.getClass().getMethod("create", File.class, String.class).invoke(toVersionInstaller, updateDirectory, ".tmp");
        updateFiles(updateDirectory);
    }

    public void cleanMigration() throws Exception {
        File updateDirectory = new File(base, "update");
        InstallerUtils.cleanDirectory(updateDirectory);
        InstallerUtils.removeEmptyDirectories(updateDirectory);
    }

    public void commitMigration() throws Exception {
        File updateDirectory = new File(base, "update");
        InstallerUtils.commitDirectory(base, updateDirectory);
        InstallerUtils.removeEmptyDirectories(updateDirectory);
    }

}
