/**
 * 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.zookeeper.server.upgrade;

import java.io.File;
import java.io.IOException;

import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.zookeeper.server.DataTree;
import org.apache.zookeeper.server.persistence.FileTxnSnapLog;

/**
 * This class upgrades the older database 
 * to a new database for the zookeeper 
 * servers.
 * The way to run it is 
 * java -class path zookeeper.jar Upgrade dataDir snapShotDir
 * or using zookeeper scripts with zkServer -upgrade dataDir snapShotDir 
 * it creates a backup in the dataDir/.bkup and snapShotDir/.bkup which 
 * can be retrieved back to the snapShotDir and dataDir 
 */
@InterfaceAudience.Public
public class UpgradeMain {
    File snapShotDir;
    File dataDir;
    File bkupsnapShotDir;
    File bkupdataDir;
    File currentdataDir;
    File currentsnapShotDir;
    
    private static final Logger LOG = LoggerFactory.getLogger(UpgradeMain.class);
    private static final String USAGE = "Usage: UpgradeMain dataDir snapShotDir";
    private static final int LASTVERSION = 1;
    private static final int CURRENTVERSION = FileTxnSnapLog.VERSION;
    private static final String dirName = FileTxnSnapLog.version;
    private static final String manual = "Please take manual steps to " +
    		"sanitize your database.\n Please read the upgrade manual";
    
     /**
     * upgrade class that takes the two file 
     * directories.
     * @param dataDir the directory that contains the 
     * transaction logs
     * @param snapShotDir the directory that contains 
     * the snapshots 
     */
    public UpgradeMain(File dataDir, File snapShotDir) {
        this.snapShotDir = snapShotDir; 
        this.dataDir = dataDir;
        this.bkupdataDir = new File(dataDir, dirName + LASTVERSION);
        this.bkupsnapShotDir = new File(snapShotDir, dirName + LASTVERSION );
        this.currentsnapShotDir = new File(snapShotDir, dirName + CURRENTVERSION);
        this.currentdataDir = new File(dataDir, dirName + CURRENTVERSION);
    }
 
    /**
     * create all the bkup directories and the current
     * database directories
     * @throws IOException
     */
    private void createAllDirs() throws IOException {
        String error = "backup directory " + bkupdataDir + " already exists";
        LOG.info("Creating previous version data dir " + bkupdataDir);
        if (!bkupdataDir.mkdirs()) {
            LOG.error(error);
            LOG.error(manual);
            throw new IOException(error);
        }
        LOG.info("Creating previous version snapshot dir " + bkupdataDir);
        if (!bkupsnapShotDir.mkdirs() && !bkupsnapShotDir.exists()) {
            LOG.error(error);
            LOG.error(manual);
            throw new IOException(error);
        }
        error = "current directory " + currentdataDir + " already exists";
        LOG.info("Creating current data dir " + currentdataDir);
        if (!currentdataDir.mkdirs()) {
            LOG.error(error);
            LOG.error(manual);
            throw new IOException(error);
        }
        LOG.info("Creating current snapshot dir " + currentdataDir);
        if (!currentsnapShotDir.mkdirs() && !currentsnapShotDir.exists()) {
            LOG.error(error);
            LOG.error(manual);
            throw new IOException(error);
        }
    }
    
    /**
     * copy files from srcdir to dstdir that have the string 
     * filter in the srcdir filenames
     * @param srcDir the source directory
     * @param dstDir the destination directory
     * @param filter the filter of filenames that 
     * need to be copied.
     * @throws IOException
     */
    void copyFiles(File srcDir, File dstDir, String filter) throws IOException {
        File[] list = srcDir.listFiles();
        if (list != null) {
            for (File file: list) {
                String name = file.getName();
                if (name.startsWith(filter)) {
                    // we need to copy this file
                    File dest = new File(dstDir, name);
                    LOG.info("Renaming " + file + " to " + dest);
                    if (!file.renameTo(dest)) {
                        throw new IOException("Unable to rename "
                                + file + " to " +  dest);
                    }
                }
            }
        }
    }
    
    /**
     * run the upgrade
     * @throws IOException
     */
    public void runUpgrade() throws IOException {
        if (!dataDir.exists()) {
            throw new IOException(dataDir + " does not exist");
        }
        if (!snapShotDir.exists()) {
            throw new IOException(snapShotDir + " does not exist");
        }
        // create the bkup directorya
        createAllDirs();
        //copy all the files for backup
        try {
            copyFiles(dataDir, bkupdataDir, "log");
            copyFiles(snapShotDir, bkupsnapShotDir, "snapshot");
        } catch(IOException io) {
            LOG.error("Failed in backing up.");
            throw io;
        }

        //evrything is backed up
        // read old database and create 
        // an old snapshot
        UpgradeSnapShotV1 upgrade = new UpgradeSnapShotV1(bkupdataDir, 
                bkupsnapShotDir);
        LOG.info("Creating new data tree");
        DataTree dt = upgrade.getNewDataTree();
        FileTxnSnapLog filesnapLog = new FileTxnSnapLog(dataDir, 
                snapShotDir);
        LOG.info("snapshotting the new datatree");
        filesnapLog.save(dt, upgrade.getSessionWithTimeOuts());
        //done saving.
        LOG.info("Upgrade is complete");
    }
    
    public static void main(String[] argv) {
        if (argv.length < 2) {
            LOG.error(USAGE);
            System.exit(-1);
        }
        try {
            UpgradeMain upgrade = new UpgradeMain(new File(argv[0]), new File(argv[1]));
            upgrade.runUpgrade();
        } catch(Throwable th) {
            LOG.error("Upgrade Error: Please read the " +
            		"docs for manual failure recovery ", th);
        }
    }
}
