/*
 * Copyright 2009 the original author or authors.
 *
 * 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.gradle.api.changedetection.state;

import org.gradle.api.changedetection.digest.DigesterCache;
import org.gradle.api.changedetection.digest.DigesterUtil;
import org.gradle.api.changedetection.digest.DigestStringUtil;
import org.gradle.api.io.IoFactory;
import org.gradle.util.GFileUtils;
import org.apache.commons.lang.StringUtils;

import java.io.File;
import java.security.MessageDigest;
import java.util.*;

/**
 * @author Tom Eyckmans
 */
class DirectoryStateDigestCalculator implements Runnable {

    private final DirectoryState directoryState;
    private final DigesterCache digesterCache;
    private final DigesterUtil digesterUtil;
    private final DefaultDirectoryStateChangeDetecter directoryStateChangeDetecter;
    private final Map<String, DirectoryState> currentLevelDirectoryStates;
    private final Map<String, DirectoryState> previousLevelDirectoryStates;
    private final IoFactory ioFactory;

    DirectoryStateDigestCalculator(
            DirectoryState directoryState,
            DigesterCache digesterCache,
            DigesterUtil digesterUtil,
            DefaultDirectoryStateChangeDetecter directoryStateChangeDetecter,
            Map<String, DirectoryState> currentLevelDirectoryStates,
            Map<String, DirectoryState> previousLevelDirectoryStates, IoFactory ioFactory) {
        this.directoryState = directoryState;
        this.digesterCache = digesterCache;
        this.digesterUtil = digesterUtil;
        this.directoryStateChangeDetecter = directoryStateChangeDetecter;
        this.currentLevelDirectoryStates = currentLevelDirectoryStates;
        this.previousLevelDirectoryStates = previousLevelDirectoryStates;
        this.ioFactory = ioFactory;
    }

    public void run() {
        StateFileWriter stateFileWriter  = null;
        try {
            final MessageDigest fileDigester = digesterCache.getDigester(Thread.currentThread().getName() + "_file");
            final MessageDigest dirDigester = digesterCache.getDigester(Thread.currentThread().getName() + "_dir");
            final File directory = directoryState.getDirectory();
            final String relativeDirectoryPath = directoryState.getRelativePath();
            final StateFileUtil stateFileUtil = directoryStateChangeDetecter.getStateFileUtil();
            final File stateFile = stateFileUtil.getNewDirsStateFile(stateFileUtil.getDirStateFilename(directoryState.getRelativePathDigest())); 
            stateFileWriter = new StateFileWriter(ioFactory, stateFile);

            long directorySize = 0;

            final List<File> subFiles = GFileUtils.getSubFiles(directory);

            if ( subFiles.size() > 0 ) {
                // Sort alphabetically by filename - this simplifies comparing agains the old state later on.
                Collections.sort(subFiles, new Comparator<File>() {
                    public int compare(final File firstFile, final File secondFile) {
                        return firstFile.getName().compareTo(secondFile.getName());
                    }
                });

                for ( final File subFile : subFiles ) {
                    digesterUtil.digestFile(fileDigester, subFile);

                    final String fileDigest = DigestStringUtil.digestToHexString(fileDigester.digest());

                    stateFileWriter.addDigest(subFile.getName(), fileDigest);

                    directorySize += subFile.length();
                    dirDigester.update(fileDigest.getBytes());
                }

                stateFileWriter.lastFileDigestAdded();
            }

            final List<DirectoryState> subDirectoryStateItems = getNewSubDirectoryStates(relativeDirectoryPath);
            for ( final DirectoryState subDirectoryStateItem : subDirectoryStateItems ) {
                dirDigester.update(subDirectoryStateItem.getDigest().getBytes());
                directorySize += subDirectoryStateItem.getSize();
            }

            digesterUtil.digestDirectory(dirDigester, directory, directorySize);

            final String dirDigest = DigestStringUtil.digestToHexString(dirDigester.digest());

            directoryState.setDigest(dirDigest);
            directoryState.setSize(directorySize);
        }
        catch ( Throwable t ) {
            directoryState.setFailureCause(t);
        }
        finally {
            addNewDirectoryState(directoryState);
            if ( stateFileWriter != null ) {
                stateFileWriter.close();
            }
        }
    }

    public void addNewDirectoryState(DirectoryState directoryState) {
        if ( directoryState == null ) throw new IllegalArgumentException("leveledDirectoryState is null!");

        currentLevelDirectoryStates.put(directoryState.getRelativePath(), directoryState);
    }

    public String getNewDirectoryDigest(String relativeDirectoryPath) {
        if ( StringUtils.isEmpty(relativeDirectoryPath) ) throw new IllegalArgumentException("relativeDirectoryPath is empty!");

        final DirectoryState directoryState = previousLevelDirectoryStates.get(relativeDirectoryPath);
        if ( directoryState == null ) {
            return null;
        }
        else {
            return directoryState.getDigest();
        }
    }

    public List<DirectoryState> getNewSubDirectoryStates(String relativeDirectoryPath) {
        final List<DirectoryState> subDirectoryStates = new ArrayList<DirectoryState>();

        for (final String currentStateItemKey : previousLevelDirectoryStates.keySet()) {

            if (    currentStateItemKey.startsWith(relativeDirectoryPath) ) {
                String belowPath = currentStateItemKey.replaceAll(relativeDirectoryPath, "");
                if ( "".equals(belowPath) || StringUtils.countMatches(belowPath, System.getProperty("file.separator")) == 1 )
                    subDirectoryStates.add(previousLevelDirectoryStates.get(currentStateItemKey));
            }
        }

        return subDirectoryStates;
    }
}
