/*
 * 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.axis2.deployment.repository.util;

import org.apache.axis2.deployment.Deployer;
import org.apache.axis2.deployment.DeploymentConstants;
import org.apache.axis2.deployment.DeploymentEngine;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class WSInfoList implements DeploymentConstants {

    /**
     * This is to store all the jar files in a specified folder (WEB_INF)
     */
    private List jarList = new ArrayList();

    /**
     * All the currently updated jars
     */
    public Map currentJars = new HashMap();

    /**
     * Reference to DeploymentEngine to make update
     */

    private boolean locked = false;
    private final DeploymentEngine deploymentEngine;

    public WSInfoList(DeploymentEngine deploy_engine) {
        deploymentEngine = deploy_engine;
    }

    /**
     * First checks whether the file is already available by the
     * system call fileExists. If it is not deployed yet then adds to the jarList
     * and to the deployment engine as a new service or module.
     * While adding new item to jarList, first creates the WSInfo object and
     * then adds to the jarlist and actual jar file is added to DeploymentEngine.
     * <p/>
     * If the files already exists, then checks whether it has been updated
     * then changes the last update date of the wsInfo and adds two entries to
     * DeploymentEngine - one for new deployment and other for undeployment.
     *
     * @param file actual jar files for either Module or service
     */
    public synchronized void addWSInfoItem(File file, Deployer deployer , int type) {
        WSInfo info = (WSInfo) currentJars.get(file.getAbsolutePath());
        if (info != null) {
            if (deploymentEngine.isHotUpdate() && isModified(file, info)) {
                WSInfo wsInfo = new WSInfo(info.getFileName(), info.getLastModifiedDate(), deployer,type);
                deploymentEngine.addWSToUndeploy(wsInfo);           // add entry to undeploy list
                DeploymentFileData deploymentFileData = new DeploymentFileData(file, deployer);
                deploymentEngine.addWSToDeploy(deploymentFileData);    // add entry to deploylist
            }
        } else {
            info = getFileItem(file, deployer, type);
            setLastModifiedDate(file, info);
        }

        jarList.add(info.getFileName());
    }
    
    public synchronized void addWSInfoItem(URL url, Deployer deployer, int type) {
        // There is no way to set hot-update.
        // Here file.getAbsolutePath() = info.getFileName()
        WSInfo info = (WSInfo) currentJars.get(url.getPath());
        if (info != null) {
            if (deploymentEngine.isHotUpdate()) {
                WSInfo wsInfo = new WSInfo(info.getFileName(), info.getLastModifiedDate(),
                        deployer, type);
                deploymentEngine.addWSToUndeploy(wsInfo); // add entry to undeploy list
                DeploymentFileData deploymentFileData = new DeploymentFileData(url, deployer, null);
                deploymentEngine.addWSToDeploy(deploymentFileData); // add entry to deploylist
            }
        } else {
            info = getFileItem(url, deployer, type);
        }

        jarList.add(info.getFileName());
    }

    /**
     * Checks undeployed Services. Checks old jars files and current jars.
     * If name of the old jar file does not exist in the current jar
     * list then it is assumed that the jar file has been removed
     * and that is hot undeployment.
     */
    private synchronized void checkForUndeployedServices() {
        if(!locked) {
            locked = true;
        } else{
            return;
        }
        Iterator infoItems = currentJars.keySet().iterator();
        List tobeRemoved = new ArrayList();
        while (infoItems.hasNext()) {
            String  fileName = (String) infoItems.next();
            WSInfo infoItem = (WSInfo) currentJars.get(fileName);
            if (infoItem.getType() == WSInfo.TYPE_MODULE) {
                continue;
            }
            //seems like someone has deleted the file , so need to undeploy
            boolean found = false;
            for (int i = 0; i < jarList.size(); i++) {
                String s = (String) jarList.get(i);
                if(fileName.equals(s)){
                    found = true;
                }
            }
            if(!found){
                tobeRemoved.add(fileName);
                deploymentEngine.addWSToUndeploy(infoItem);
            }
        }

        for (int i = 0; i < tobeRemoved.size(); i++) {
            String fileName = (String) tobeRemoved.get(i);
            currentJars.remove(fileName);
        }
        tobeRemoved.clear();
        jarList.clear();
        locked = false;
    }

    /**
     * Clears the jarlist.
     */
    public void init() {
        jarList.clear();
    }

    /**
     *
     */
    public void update() {
        synchronized (deploymentEngine) {
            checkForUndeployedServices();
            deploymentEngine.unDeploy();
            deploymentEngine.doDeploy();
        }
    }

    /**
     * Gets the WSInfo object related to a file if it exists, null otherwise.
     *
     */
    private WSInfo getFileItem(File file , Deployer deployer , int type) {
        String fileName = file.getName();
        WSInfo info = (WSInfo) currentJars.get(fileName);
        if(info==null){
            info = new WSInfo(file.getAbsolutePath(), file.lastModified(), deployer ,type);
            currentJars.put(file.getAbsolutePath(), info);
            DeploymentFileData fileData = new DeploymentFileData(file, deployer);
            deploymentEngine.addWSToDeploy(fileData);
        }
        return info;
    }
    
    /**
     * Gets the WSInfo object related to a file if it exists, null otherwise.
     * 
     */
    private WSInfo getFileItem(URL url, Deployer deployer, int type) {
        WSInfo info = (WSInfo) currentJars.get(url.getPath());
        if (info == null) {
            info = new WSInfo(url.getPath(), 0, deployer, type);
            currentJars.put(url.getPath(), info);
            DeploymentFileData fileData = new DeploymentFileData(url, deployer, null);
            deploymentEngine.addWSToDeploy(fileData);
        }
        return info;
    }

    /**
     * Checks if a file has been modified by comparing the last update date of
     * both files and WSInfo. If they are different, the file is assumed to have
     * been modified.
     *
     * @param file
     * @param wsInfo
     */
    private boolean isModified(File file, WSInfo wsInfo) {
        long currentTimeStamp = wsInfo.getLastModifiedDate();
        
        setLastModifiedDate(file, wsInfo);
        
        return (currentTimeStamp != wsInfo.getLastModifiedDate());
    }

    /**
     * Obtains the newest (as compared with timestamp stored in wsInfo)
     * timestamp and stores it in WSInfo. 
     */
    private void setLastModifiedDate(File file, WSInfo wsInfo) {
        if (file.isDirectory()) {
            File files [] = file.listFiles();
            for (int i = 0; i < files.length; i++) {
                File fileItem = files[i];
                if (fileItem.isDirectory()) {
                    setLastModifiedDate(fileItem, wsInfo);
                }
                else if(wsInfo.getLastModifiedDate() < fileItem.lastModified()) {
                    wsInfo.setLastModifiedDate(fileItem.lastModified());
                }
            }
        }
        else if(wsInfo.getLastModifiedDate() < file.lastModified()) {
            wsInfo.setLastModifiedDate(file.lastModified());
        }
    }
}
