/**
 * dfs-client.js
 * Copyright (c) 2000 - 2015 Samsung Electronics Co., Ltd. All rights reserved.
 *
 * Contact:
 * DongHee Yang <donghee.yang@samsung.com>
 * Sungmin Kim <sm.art.kim@samsung.com>
 * Jiil Hyoun <jiil.hyoun@samsung.com>
 * Jonghwan Park <iwin100.park@samsung.com>
 * Kitae Kim <kt920.kim@samsung.com>
 *
 * 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.
 *
 * Contributors:
 * - S-Core Co., Ltd
**/

var fs = require('fs');
var extfs = require('fs-extra');
var net = require('net');
var path = require('path');
var async = require('async');
var optimist = require('optimist');


var dibs = require('../../core/dibs.js');
var utils = require('../../lib/utils.js');
var DError = require('../../core/exception.js');

dibs.initialize();

var argv = optimist.usage('Usage: $0 <SUBCOMMAND> [OPTS]\n\n\
Subcommands:\n\
  download      Download a file\n\
  upload        Upload a file\n\n\
Subcommand usage:\n\
  download  $0 download <remotePath> -h <hostName> -p <port> [--local <localPath>] [--log <logPath>] \n\
  upload    $0 upload <localPath> -h <hostName> -p <port> [--name <fileName>] [--log <logPath>]'
)
    .alias({
        l: 'local',
        h: 'host',
        p: 'port',
        n: 'name'
    })
    .string(['i', 't', 'h', 'n'])
    .describe('h', 'SERVER HOST')
    .describe('p', 'SERVER PORT')
    .describe('name', 'remote file name')
    .describe('local', 'local path')
    .describe('log', 'log file name')
    .demand(['h', 'p']).argv;

var subCmds = argv._;
var subCmd = null;
var targetPath = null;
if (!subCmds || subCmds.length === 0) {
    console.error('No target specified!');
    process.exit(-1);
} else if (subCmds.length === 1) {
    subCmd = 'download';
    targetPath = subCmds[0];
} else {
    subCmd = subCmds[0];
    targetPath = subCmds[1];
}

switch (subCmd) {
case 'download':
    // parse arguments
    handleDownloadCommand(targetPath, argv.host, argv.port, argv.local, argv.log);
    break;
case 'upload':
    handleUploadCommand(targetPath, argv.host, argv.port, argv.name, argv.log);
    break;
default:
    console.error('Invalid sub-command!: \'' + subCmd + '\'');
    process.exit(-1);
}


function handleDownloadCommand(remotePath, host, port, localPath, log) {
    if (log) {
        log = createDFSLog(log);
    } else {
        log = {};
        log.info = function (msg) {
            /*console.log("Info: " + msg); */
        };
        log.warn = function (msg) {
            /*console.log("Warning: " + msg); */
        };
        log.error = function (msg) {
            /*console.log("Error: " + msg); */
        };
    }

    if (!localPath || localPath.length === 0) {
        localPath = path.join(process.cwd(), path.basename(remotePath));
    }

    download(localPath, remotePath, host, port, log, function (err) {
        if (err) {
            console.error('Downloading file failed!');
            console.error(err);
            process.exit(-1);
        } else {
            console.error('Downloading file succeeded!');
            process.exit(0);
        }
    });
}


function handleUploadCommand(localPath, host, port, fileName, log) {
    if (log) {
        log = createDFSLog(log);
    } else {
        log = {};
        log.info = function (msg) {
            /*console.log("Info: " + msg); */
        };
        log.warn = function (msg) {
            /*console.log("Warning: " + msg); */
        };
        log.error = function (msg) {
            /*console.log("Error: " + msg); */
        };
    }

    if (!fileName || fileName.length === 0) {
        fileName = path.basename(localPath);
    }

    upload(localPath, fileName, host, port, log, function (err, dfsPath) {
        if (err) {
            console.error('Uploading file failed!');
            console.error(err);
            process.exit(-1);
        } else {
            console.log('Uploading file succeeded!');
            // NOTE. IMPORTANT!, This log will be parsed by server process
            console.log('dfsPath:' + dfsPath);
            process.exit(0);
        }
    });
}


function createDFSLog(logFilePath) {
    var logId = 'DFS';
    var logOptions = {
        filename: logFilePath,
        colorize: false
    };

    return dibs.log.open(logId, logOptions);
}


function download(localPath, remotePath, host, port, dfsLog, callback) {
    var dsize = 0;
    var oldTime = null;
    var fstream = null;
    var startTime = null;
    var resHeader = null;
    var transferError = null;
    var connNum = utils.genRandom();
    var conn = new net.Socket();

    if (!dfsLog) {
        dfsLog = {};
        dfsLog.info = function (msg) {
            /*console.log("Info: " + msg); */
        };
        dfsLog.warn = function (msg) {
            /*console.log("Warning: " + msg); */
        };
        dfsLog.error = function (msg) {
            /*console.log("Error: " + msg); */
        };
    }

    function parseRequestHeader(data, partialHeader) {
        var header = null;
        var hStr = data.toString();
        if (partialHeader) {
            hStr = partialHeader.concat(hStr);
        }

        try {
            header = JSON.parse(hStr);
        } catch (e) {
            header = null;
        }
        return header;
    }


    //connect to the server
    dfsLog.info(connNum + ' Connecting to DFS server for downloading ... ' +
        host + ':' + Number(port) + 1);

    // connect
    conn.connect(Number(port) + 1, host, function () {
        var partialHeader = '';
        startTime = new Date();

        async.waterfall([
            // create directory if needed
            function (cb) {
                var dirPath = path.dirname(localPath);
                fs.exists(dirPath, function (exists) {
                    if (!exists) {
                        extfs.mkdirs(dirPath, cb);
                    } else {
                        cb(null);
                    }
                });
            }], function (err) {
            if (!err) {
                var header = createDownloadHeader(remotePath, false);
                dfsLog.info(connNum + ' Sending DFS Req. header ... ' + JSON.stringify(header));
                conn.write(header);
            } else {
                dfsLog.error(err);
                transferError = err;
                conn.end();
            }
        });

        var connDestroyed = false;
        var downloadCheckTimer = null;
        conn.on('data', function (data) {
            if (!resHeader) {
                // get data from reponse header
                dfsLog.info(connNum + ' DFS Rep. Header:' + data.toString());
                resHeader = parseRequestHeader(data, partialHeader);
                if (!resHeader) {
                    partialHeader = partialHeader.concat(data.toString());
                    return;
                }

                // check error
                if (resHeader.errorMsg) {
                    conn.destroy(); return;
                }

                if (resHeader.size === 0) {
                    dfsLog.info(connNum + ' File size is zero! done!');
                } else {
                    dfsLog.info(connNum + ' Sending DFS ACK ... ');
                    fstream = fs.createWriteStream(localPath);
                    conn.write('ACK');
                }
            } else {
                // reset timer
                if (downloadCheckTimer) {
                    clearTimeout(downloadCheckTimer);
                }
                downloadCheckTimer = setTimeout(function () {
                    if (conn && !connDestroyed) {
                        conn.destroy(); connDestroyed = true;
                    }
                    transferError = new DError('DFS022', {
                        file: remotePath
                    });
                }, 30 * 1000);

                dsize += data.length;
                conn.pause();
                fstream.write(data, function () {
                    conn.resume();
                    if (dsize >= resHeader.size) {
                        if (!connDestroyed) {
                            conn.destroy(); connDestroyed = true;
                        }
                    } else {
                        var dprogress1 = Math.round(dsize * 100 / resHeader.size);


                        // period = 1 sec
                        var currTime = new Date();
                        if (!oldTime || (currTime - oldTime) >= 1000) {
                            dfsLog.info(connNum + ' Download : ' + dsize + ' (' + dprogress1 + '%)');
                            oldTime = currTime;
                        }
                    }
                });
            }
        });


        var didOnEnd = false;
        conn.on('end', function () {
            if (downloadCheckTimer) {
                clearTimeout(downloadCheckTimer); downloadCheckTimer = null;
            }
            if (didOnEnd) {
                return;
            }
            didOnEnd = true;

            dfsLog.info(connNum + ' Received FIN signal from server!');
            if (fstream) {
                fstream.end();
            }
        });

        //handle closed
        conn.on('close', function (had_error) {
            if (downloadCheckTimer) {
                clearTimeout(downloadCheckTimer); downloadCheckTimer = null;
            }
            if (transferError) {
                if (!connDestroyed) {
                    conn.destroy(); connDestroyed = true;
                }
                callback(transferError);
            } else {
                dfsLog.info(connNum + ' Downloading DFS file is done!...(' +
                    dsize + '/' + resHeader.size + ')');

                var endTime = new Date();
                getFileInfo(localPath, function (err, checksum1, size1) {
                    var duration = endTime - startTime;
                    if (resHeader.errorMsg) {
                        transferError = new DError('DFS019', {
                            msg: resHeader.errorMsg
                        });
                    } else if (size1 !== resHeader.size ||
                        checksum1 !== resHeader.checksum) {
                        transferError = new DError('DFS005', {
                            file: localPath,
                            size: size1,
                            hsize: resHeader.size,
                            checksum: checksum1,
                            hchecksum: resHeader.checksum
                        });
                    } else {
                        dfsLog.info(connNum + ' Transfer Bytes: ' + size1);
                        dfsLog.info(connNum + ' Transfer Time: ' + duration + ' ms');
                        if (duration > 0) {
                            dfsLog.info(connNum + ' Transfer Rate: ' +
                                ((size1 * 1000 / duration) / 1024.0 / 1024.0) + 'MB/s');
                        }
                    }
                    if (!connDestroyed) {
                        conn.destroy(); connDestroyed = true;
                    }
                    callback(transferError);
                });
            }
        });

        conn.on('drain', function () {
            //dfsLog.info( connNum+ " Write buffer is now empty." );
        });

        conn.on('error', function (err) {
            if (downloadCheckTimer) {
                clearTimeout(downloadCheckTimer); downloadCheckTimer = null;
            }
            transferError = new DError('DFS004', {
                host: host,
                port: port,
                rPath: remotePath,
                lPath: localPath
            }, err);

            dfsLog.error(err);
        });

        conn.on('timeout', function () {
            transferError = new DError('DFS020');
            dfsLog.error(connNum + ' Connection timeout from server!');
            conn.end();
        });
    });


}


function createDownloadHeader(remotePath) {
    if (remotePath.charAt(0) === '/') {
        remotePath = remotePath.slice(1, remotePath.length);
    }
    return '{"type":"dfs", "upload": false, "filePath":"' + utils.path2string(remotePath) + '"}';
}


function upload(localPath, fileName, host, port, dfsLog, callback) {
    if (!dfsLog) {
        dfsLog = {};
        dfsLog.info = function (msg) {
            /*console.log("Info: " + msg); */
        };
        dfsLog.warn = function (msg) {
            /*console.log("Warning: " + msg); */
        };
        dfsLog.error = function (msg) {
            /*console.log("Error: " + msg); */
        };
    }
    var resHeader = null;
    var connNum = utils.genRandom();
    var conn = new net.Socket();
    var transferError = null;

    //connect to the server
    dfsLog.info(connNum + ' Connecting to DFS server for uploading... ' +
        host + ':' + (Number(port) + 1));

    // connect
    conn.connect(Number(port) + 1, host, function () {

        createUploadHeader(localPath, fileName, function (err, header) {
            if (!err) {
                dfsLog.info(connNum + ' Sending DFS Req. header ... ' + JSON.stringify(header));
                conn.write(header);
            } else {
                transferError = err;
                conn.end();
            }
        });

        conn.on('data', function (data) {
            if (!resHeader) {
                resHeader = JSON.parse(data.toString());
                dfsLog.info(connNum + ' Received DFS Rep. Header:' + data.toString());

                //send a file to the server
                var fileStream = fs.createReadStream(localPath);
                fileStream.once('error', function (err) {
                    dfsLog.error(err);
                    fileStream.end();
                });

                fileStream.pipe(conn);
            }
        });

        conn.on('end', function () {
            dfsLog.info(connNum + ' Received FIN signal from server!');
        });

        //handle closed
        conn.on('close', function (had_error) {
            if (transferError) {
                conn.destroy();
                callback(transferError);
            } else if (!had_error) {
                // destroy socket
                conn.destroy();

                callback(null, resHeader.incomingFileName);
            } else {
                var error = new DError('DFS007', {
                    localPath: localPath,
                    host: host,
                    port: port,
                    remotePath: fileName
                }, had_error);
                dfsLog.error(ror);

                // destroy socket
                conn.destroy();

                callback(error, null);
            }
        });

        conn.on('drain', function () {
            //dfsLog.info( connNum+ " Write buffer is now empty." );
        });

        conn.on('error', function (err) {
            transferError = new DError('DFS007', {
                localPath: localPath,
                host: host,
                port: port,
                remotePath: fileName
            }, err);
            dfsLog.error(transferError);
        });

        conn.on('timeout', function () {
            transferError = new DError('DFS020');
            dfsLog.error(connNum + ' Connection timeout from server!');
            conn.end();
        });
    });


}


function createUploadHeader(localPath, fileName, callback) {
    var remotePath = null;

    if (fileName !== null) {
        remotePath = fileName;
    } else {
        remotePath = path.basename(localPath);
    }

    getFileInfo(localPath, function (err, checksum, size) {
        if (!err) {
            var header = {
                type: 'dfs',
                upload: true,
                filePath: remotePath,
                size: size,
                checksum: checksum
            };

            callback(err, JSON.stringify(header));
        } else {
            callback(err, null);
        }
    });
}


// callback: (err, checksum, size)
function getFileInfo(localPath, callback) {
    var checksum = null;

    async.waterfall([
        function (cb) {
            utils.getCheckSum(localPath, cb);
        },
        function (checksum1, cb) {
            checksum = checksum1;
            fs.stat(localPath, cb);
        }], function (err, stat) {
        if (!err) {
            callback(err, checksum, stat.size);
        } else {
            callback(err, null, null);
        }
    });
}
