/*
	Licensed Material - Property of IBM

	(C) Copyright 2015, 2016 IBM Corp.

	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.
*/


'use strict';

// Public modules
var fs = require('fs');
var path = require('path');
var log = require('npmlog');
var shell = require('shelljs');
var Gaze = require('gaze').Gaze;
var events = require('events');

// Private modules
var consts = require('./preview-consts.js');
var strings = require('ibm-strings');
var externalStrings = require('../externalizedStrings');
var cordovaSupport = require('mdo-cordova-support');

/*
 This class provides the ability to watch a Cordova projects www directory.
 When a file is changed inside of the www directory, events are emitted to the
 parent. Two modes are provided, watch-and-copy, and watch-and-prepare.
 Files are programmatically copied to the platform directories in watch-and-copy
 mode. For watch-and-prepare, cordova prepare --preview is called to copy the
 www directory to the platform specific directories.
 */
function PreviewWatch(mode, rootDir, platforms) {
    var wwwWatch;           // Watch object
    var that;               // Points to this for events
    var firstPrepare;       // Flag denotes if prepare has been called
    var watchCopy;          // Watch and copy mode
    var watchPrepare;       // Watch and prepare mode

    events.EventEmitter.call(this);

    if (mode === consts.WC_MODE)
        watchCopy = true;
    else if (mode === consts.WP_MODE)
        watchPrepare = true;

    that = this;

    logSilly('Watch-and-copy: ' + watchCopy);
    logSilly('Watch-and-prepare: ' + !watchCopy);
    logSilly('Root directory: ' + rootDir);
    logSilly('Platforms: ' + platforms);

    /*
     Silly logs a message.

     message - Message to log
     */
    function logSilly(message) {
        log.silly(consts.MODULE, message);
    }

    /*
     Verbose logs a message.

     message - Message to log
     */
    function logVerbose(message) {
        log.verbose(consts.MODULE, message);
    }

    /*
     Provides callbacks to monitors for file changes, adds, renames, and deletes.
     For deletes, deleteFromtPlatform() is called. For all other events,
     copyToPlatformDir() is called. wwwWatch must be initialized with Gaze.
     */
    function watchAndCopyCallback() {
        logVerbose('Watch-and-copy callback invoked.');

        wwwWatch.on(consts.GAZE_CHANGED, copyToPlatformDir);
        wwwWatch.on(consts.GAZE_ADDED, copyToPlatformDir);
        wwwWatch.on(consts.GAZE_RENAMED, copyToPlatformDir);
        wwwWatch.on(consts.GAZE_DELETED, deleteFromPlatformDir);
        wwwWatch.on(consts.GAZE_ALL,
            function(event, filePath) {
                logSilly('Event: ' + event);
                logSilly('File path: ' + filePath);
            }
        );
    }

    /*
     Provides callbacks to monitor for file changes, adds, renames, and deletes.
     Then calls invokePrepare(). wwwWatch must be initalized with Gaze.
     */
    function watchAndPrepareCallback() {
        logVerbose('Watch-and-prepare callback invoked.');

        wwwWatch.on(consts.GAZE_CHANGED, invokePrepare);
        wwwWatch.on(consts.GAZE_ADDED, invokePrepare);
        wwwWatch.on(consts.GAZE_RENAMED, invokePrepare);
        wwwWatch.on(consts.GAZE_DELETED, invokePrepare);
        wwwWatch.on(consts.GAZE_ALL,
            function(event, filePath) {
                logSilly('Event: ' + event);
                logSilly('File path: ' + filePath);
            }
        );
    }

    /*
     Provides callbacks to monitor for file changes to watched file for serve.
     Then emits reload event. wwwWatch must be initalized with Gaze.
     */
    function watchAndServeCallback() {
        logVerbose('Watch-and-serve callback invoked.');

        wwwWatch.on(consts.GAZE_CHANGED, function () {
            that.emit(consts.PREVIEW_RELOAD);
        });

        wwwWatch.on(consts.GAZE_ALL,
            function(event, filePath) {
                logSilly('Event: ' + event);
                logSilly('File path: ' + filePath);
            }
        );
    }

    /*
     Determines the destination paths for watch-and-copy. Three platforms are
     supported: Android, iOS, and Windows. A object is returned containing
     the destination paths for all three platforms if they are installed. The
     current working directory must be the root of a Cordova project, and the
     source path must be passed.

     srcFilePath - Path to source file in www directory
     */
    function getDestFilePath(srcFilePath) {
        var androidPath;            // Path to Android directory
        var iOSPath;                // Path to iOS directory
        var winPath;                // Path to Windows directory
        var browserPath;            // Path to Browser directory
        var relativeSrcFilePath;    // Relative path to source file
        var destInfo;               // Resultant destination info

        androidPath = path.join(rootDir, consts.ANDROID_DIR);
        iOSPath = path.join(rootDir, consts.IOS_DIR);
        winPath = path.join(rootDir, consts.WIN_DIR);
        browserPath = path.join(rootDir, consts.BROWSER_DIR);
        relativeSrcFilePath = srcFilePath.substring(rootDir.length);
        destInfo = {};

        logVerbose('Copying file to platform directory');
        logSilly('Source file path: ' + srcFilePath);
        logSilly('Relative source file path: ' + relativeSrcFilePath);
        logSilly('Android platform directory: ' + androidPath);
        logSilly('iOS platform directory: ' + iOSPath);
        logSilly('Windows platform directory: ' + winPath);
        logSilly('Browser platform directory: ' + browserPath);

        // Get destination path for Android
        if (platforms.indexOf(consts.ANDROID) > -1 &&
            fs.existsSync(androidPath))
            destInfo.androidPath = path.join(androidPath, consts.ASSETS_DIR,
                relativeSrcFilePath);

        // Get destination path for iOS
        if (platforms.indexOf(consts.IOS) > -1 && fs.existsSync(iOSPath))
            destInfo.iOSPath = path.join(iOSPath, relativeSrcFilePath);

        // Get destination path for Windows
        if (platforms.indexOf(consts.WIN) > -1 && fs.existsSync(winPath))
            destInfo.winPath = path.join(winPath, relativeSrcFilePath);

        // Get destination path for Browser
        if (platforms.indexOf(consts.BROWSER) > -1 && fs.existsSync(browserPath))
            destInfo.browserPath = path.join(browserPath, relativeSrcFilePath);

        logSilly('Destination path: ' + destInfo);

        return destInfo;
    }

    /*
     Copies a file from the root www of a Cordova project, to the platform www
     directories. Then emits a reload event to the parent. The current working
     directory must be the root of a Cordova project. A file path object must be
     passed that contains path information for Android, iOS, or Windows.

     srcFilePath - Object containing paths to copy to.
     */
    function copyToPlatformDir(srcFilePath) {
        var destInfo;           // Destination path information

        destInfo = getDestFilePath(srcFilePath);

        // Copy file for Android
        if (destInfo.androidPath)
            copyFile(srcFilePath, destInfo.androidPath);

        // Copy file for iOS
        if (destInfo.iOSPath)
            copyFile(srcFilePath, destInfo.iOSPath);

        // Copy file for Windows
        if (destInfo.winPath)
            copyFile(srcFilePath, destInfo.winPath);

        // Copy file for Browser
        if(destInfo.browserPath)
            copyFile(srcFilePath, destInfo.browserPath);

        that.emit(consts.PREVIEW_RELOAD);
    }

    /*
     Deletes a file from the platform directory of Cordova project then emits
     a reload event to the parent. The current working directory must be the
     root of a Cordova project. A file path object must be passed that contains
     path information for Android, iOS, or Windows.

     srcFilePath - Object containing paths to delete from
     */
    function deleteFromPlatformDir(srcFilePath) {
        var destInfo;           // Destination path information

        destInfo = getDestFilePath(srcFilePath);

        // Remove Android file
        if (destInfo.androidPath)
            fs.unlinkSync(destInfo.androidPath);

        // Remove iOS file
        if (destInfo.iOSPath)
            fs.unlinkSync(destInfo.iOSPath);

        // Remove Windows file
        if (destInfo.winPath)
            fs.unlinkSync(destInfo.winPath);

        // Remove Browser file
        if (destInfo.browserPath)
            fs.unlinkSync(destInfo.browserPath);

        that.emit(consts.PREVIEW_RELOAD);
    }

    /*
     Copies a file from one location to another. Source, and destination paths
     must be passed.

     srcPath - File to copy
     destPath - File destination
     */
    function copyFile(srcPath, destPath) {
        logVerbose('Copying file.');
        logSilly('Copying ' + srcPath + ' to ' + destPath);

        try {
            shell.mkdir('-p', path.dirname(destPath));

            // Ensure destPath is not read-only if it already exists
            if (fs.existsSync(destPath))
                shell.chmod('u+w', destPath);

            shell.cp('-f', srcPath, destPath);
        } catch (err) {
            that.emit(consts.PREVIEW_ERROR,
                strings.format(externalStrings.copyFailed, srcPath, destPath),
                err);
        }
    }

    /*
     Invokes "cordova prepare --preview" to setup preview files, and copy
     changed www source files to the platform directories.
     */
    function invokePrepare() {
        logVerbose('Invoking prepare.');

        cordovaSupport.prepare(consts.PREVIEW_ARG).then(
            function () {
                if (firstPrepare) {
                    firstPrepare = false;
                    that.emit(consts.PREVIEW_START);
                } else
                    that.emit(consts.PREVIEW_RELOAD);
            },
            function (err) {
                logSilly(err);
                that.emit(consts.PREVIEW_ERROR, externalStrings.prepareFailed,
                    err);
            }
        );
    }

    /*
     Starts a Gaze watch on the Cordova project denoted by rootDir. rootDir
     must be initialized to the root of a Cordova project
     */
    this.startWatch = function () {
        var watchPath;      // Path to watch

        watchPath = path.join(consts.WWW_WATCH_DIR);

        // Begin the watch based on the specified mode
        if (watchCopy)
            wwwWatch = new Gaze(watchPath, {cwd: rootDir},
                watchAndCopyCallback);
        else if (watchPrepare)
            wwwWatch = new Gaze(watchPath, {cwd: rootDir},
                watchAndPrepareCallback);
        else {
            watchPath = path.join(rootDir, consts.SERVE_TIME_PATH);

            wwwWatch = new Gaze(watchPath, {cwd: rootDir},
                watchAndServeCallback);
        }

        firstPrepare = true;

        invokePrepare();
    };

    /*
     Stops watching the root directory of a Cordova project. Must be used in
     conjunction with startWatch().
     */
    this.stopWatch = function () {
        logVerbose('Stopping watch.');

        // Stop watching if wwwWatch is defined
        if (wwwWatch)
            wwwWatch.close();
    };
}

PreviewWatch.prototype = events.EventEmitter.prototype;
module.exports = PreviewWatch;
