var fs = require('fs')
var entryBuilder = require('./entry/entryBuilder')
var entryEquality = require('./entry/entryEquality')
var stats = require('./statistics/statisticsUpdate')
var pathUtils = require('path')
var fsPromise = require('./fs/fsPromise')
var loopDetector = require('./symlink/loopDetector')
var entryComparator = require('./entry/entryComparator')
var entryType = require('./entry/entryType')

/**
 * Returns the sorted list of entries in a directory.
 */
var getEntries = function (rootEntry, relativePath, loopDetected, options) {
    if (!rootEntry || loopDetected) {
        return Promise.resolve([])
    }
    if (rootEntry.isDirectory) {
        return fsPromise.readdir(rootEntry.absolutePath)
            .then(function (entries) {
                return entryBuilder.buildDirEntries(rootEntry, entries, relativePath, options)
            })
    }
    return Promise.resolve([rootEntry])
}

/**
 * Compares two directories asynchronously.
 */
var compare = function (rootEntry1, rootEntry2, level, relativePath, options, statistics, diffSet, symlinkCache) {
    var loopDetected1 = loopDetector.detectLoop(rootEntry1, symlinkCache.dir1)
    var loopDetected2 = loopDetector.detectLoop(rootEntry2, symlinkCache.dir2)
    loopDetector.updateSymlinkCache(symlinkCache, rootEntry1, rootEntry2, loopDetected1, loopDetected2)

    return Promise.all([getEntries(rootEntry1, relativePath, loopDetected1, options), getEntries(rootEntry2, relativePath, loopDetected2, options)]).then(
        function (entriesResult) {
            var entries1 = entriesResult[0]
            var entries2 = entriesResult[1]
            var i1 = 0, i2 = 0
            var comparePromises = []
            var compareFilePromises = []
            var subDiffSet

            while (i1 < entries1.length || i2 < entries2.length) {
                var entry1 = entries1[i1]
                var entry2 = entries2[i2]
                var type1, type2

                // compare entry name (-1, 0, 1)
                var cmp
                if (i1 < entries1.length && i2 < entries2.length) {
                    cmp = options.ignoreCase ? entryComparator.compareEntryIgnoreCase(entry1, entry2) : entryComparator.compareEntryCaseSensitive(entry1, entry2)
                    type1 = entryType.getType(entry1)
                    type2 = entryType.getType(entry2)
                } else if (i1 < entries1.length) {
                    type1 = entryType.getType(entry1)
                    type2 = entryType.getType(undefined)
                    cmp = -1
                } else {
                    type1 = entryType.getType(undefined)
                    type2 = entryType.getType(entry2)
                    cmp = 1
                }

                // process entry
                if (cmp === 0) {
                    // Both left/right exist and have the same name and type
                    var compareAsyncRes = entryEquality.isEntryEqualAsync(entry1, entry2, type1, diffSet, options)
                    var samePromise = compareAsyncRes.samePromise
                    var same = compareAsyncRes.same
                    if (same !== undefined) {
                        options.resultBuilder(entry1, entry2,
                            same ? 'equal' : 'distinct',
                            level, relativePath, options, statistics, diffSet,
                            compareAsyncRes.reason)
                        stats.updateStatisticsBoth(entry1, entry2, compareAsyncRes.same, compareAsyncRes.reason, type1, statistics, options)
                    } else {
                        compareFilePromises.push(samePromise)
                    }

                    i1++
                    i2++
                    if (!options.skipSubdirs && type1 === 'directory') {
                        if (!options.noDiffSet) {
                            subDiffSet = []
                            diffSet.push(subDiffSet)
                        }
                        comparePromises.push(compare(entry1, entry2, level + 1,
                            pathUtils.join(relativePath, entry1.name),
                            options, statistics, subDiffSet, loopDetector.cloneSymlinkCache(symlinkCache)))
                    }
                } else if (cmp < 0) {
                    // Right missing
                    options.resultBuilder(entry1, undefined, 'left', level, relativePath, options, statistics, diffSet)
                    stats.updateStatisticsLeft(entry1, type1, statistics, options)
                    i1++
                    if (type1 === 'directory' && !options.skipSubdirs) {
                        if (!options.noDiffSet) {
                            subDiffSet = []
                            diffSet.push(subDiffSet)
                        }
                        comparePromises.push(compare(entry1, undefined,
                            level + 1,
                            pathUtils.join(relativePath, entry1.name), options, statistics, subDiffSet, loopDetector.cloneSymlinkCache(symlinkCache)))
                    }
                } else {
                    // Left missing
                    options.resultBuilder(undefined, entry2, 'right', level, relativePath, options, statistics, diffSet)
                    stats.updateStatisticsRight(entry2, type2, statistics, options)
                    i2++
                    if (type2 === 'directory' && !options.skipSubdirs) {
                        if (!options.noDiffSet) {
                            subDiffSet = []
                            diffSet.push(subDiffSet)
                        }
                        comparePromises.push(compare(undefined, entry2,
                            level + 1,
                            pathUtils.join(relativePath, entry2.name), options, statistics, subDiffSet, loopDetector.cloneSymlinkCache(symlinkCache)))
                    }
                }
            }
            return Promise.all(comparePromises).then(function () {
                return Promise.all(compareFilePromises).then(function (sameResults) {
                    for (var i = 0; i < sameResults.length; i++) {
                        var sameResult = sameResults[i]
                        if (sameResult.error) {
                            return Promise.reject(sameResult.error)
                        } else {
                            options.resultBuilder(sameResult.entry1, sameResult.entry2,
                                sameResult.same ? 'equal' : 'distinct',
                                level, relativePath, options, statistics, sameResult.diffSet,
                                sameResult.reason)
                            stats.updateStatisticsBoth(sameResult.entries1, sameResult.entries2, sameResult.same, sameResult.reason, sameResult.type1, statistics, options)
                        }
                    }
                })
            })
        })
}

module.exports = compare
