/**
 * dfs-server.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 assert = require('assert');

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


module.exports = FileServer;


function FileServer(p) {
    var self = this;
    /** @type {module:core/dfs/dfs-server.FileServer} */
    var parent = p;
    var serverHandle = null;
    var dfsLog = null;


    this.createServer = function () {
        serverHandle = net.createServer(handleConnection);
        dfsLog = parent.getLogger();
        dfsLog.info('DFS Server is created!');
    };


    this.listen = function (port) {
        dfsLog.info('DFS Server is listening...PORT ' + port);
        serverHandle.listen(port);
    };


    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;
    }


    function respondToFileUploadHeader(header, connNum, conn) {
        dfsLog.info(connNum + ' Sending DFS reponse header\'... ');
        var tempName = path.basename(header.filePath) + '.' + utils.generateTimeStamp(true);
        header.incomingFileName = tempName;
        var resHeader = {
            err: null,
            incomingFileName: tempName
        };
        conn.write(JSON.stringify(resHeader));
    }


    function createFileUploadStream(header, connNum, callback) {
        var start = new Date();

        // generate temporary incoming file name
        var incomingPath = path.join(parent.getIncomingPath(),
            header.incomingFileName);

        extfs.mkdirs(path.dirname(incomingPath), function (err) {
            if (!err) {
                var fs1 = fs.createWriteStream(incomingPath);

                fs1.on('finish', function () {
                    var end = new Date();
                    fs.stat(incomingPath, function (err, stat) {
                        if (err) {
                            dfsLog.error(err); return;
                        }

                        var size1 = stat.size;
                        var duration = (end - start);
                        dfsLog.info(connNum + ' INFO. File transferred successfully!');
                        dfsLog.info(connNum + ' Transfer Bytes: ' + size1);
                        dfsLog.info(connNum + ' Transfer Time: ' + duration + ' ms');
                        var tRate = 0;
                        if (duration > 0) {
                            dfsLog.info(connNum + ' Transfer Rate: ' +
                                ((size1 * 1000 / duration) / 1024.0 / 1024.0) + 'MB/s');
                            tRate = (size1 * 1000) / (duration);
                        }
                    });
                });

                callback(err, fs1);
            } else {
                callback(err, null);
            }
        });
    }


    function respondToFileDownloadHeader(header, connNum, conn, callback) {
        var fullPath = parent.getRealFilePath(header.filePath);

        fs.exists(fullPath, function (exists) {
            if (!exists) {
                var errHeader = createErrorResponseHeader('DFS file not found!: ' +
                    header.filePath);
                dfsLog.info(connNum + ' Sending DFS Rep. header by Error...' +
                    JSON.stringify(errHeader));
                conn.write(errHeader);
                callback(null, errHeader);
            } else {
                dfsLog.info(connNum + ' Checking checksum...' + fullPath);
                getFileInfo(fullPath, function (err, checksum1, size1) {
                    var respHeader = createResponseHeader(size1, checksum1);
                    dfsLog.info(connNum + ' Sending DFS Rep. header...' +
                        JSON.stringify(respHeader));
                    conn.write(respHeader);
                    callback(null, respHeader);
                });
            }
        });
    }


    function createFileDownloadStream(header, connNum) {
        var fullPath = parent.getRealFilePath(header.filePath);

        var fs1 = fs.createReadStream(fullPath);

        return fs1;
    }


    function handleConnection(conn) {
        var header = null;
        var partialHeader = '';
        var stream = null;
        var connNum = utils.genRandom();
        var dsize = 0;
        var paused = false;
        var respHeader = null;
        var oldTime = null;

        var remoteAddress = {
            address: conn.remoteAddress,
            port: conn.remotePort
        };
        dfsLog.info(connNum + ' New DFS connection is received from ' +
            JSON.stringify(remoteAddress));

        conn.on('data', function (data) {
            if (!header) {
                // Try to parse request header
                header = parseRequestHeader(data, partialHeader);
                if (!header) {
                    partialHeader = partialHeader.concat(data.toString());
                    return;
                }

                dfsLog.info(connNum + ' DFS Req. Header: ' + JSON.stringify(header));
                if (header.upload) {
                    conn.pause();
                    dfsLog.info(connNum + ' Received \'UPLOAD\' request! ');
                    respondToFileUploadHeader(header, connNum, conn);

                    // create write stream
                    createFileUploadStream(header, connNum, function (err, s) {
                        conn.resume();
                        if (!err) {
                            stream = s;
                            // if size is zero, send 'end' immediately
                            if (header.size === 0) {
                                stream.end();
                            } else {
                                //conn.pipe( stream );
                            }

                            stream.on('drain', function () {
                                conn.resume();
                            });
                        } else {
                            dfsLog.error(err);
                            conn.end();
                        }
                    });
                } else {
                    conn.pause();
                    respondToFileDownloadHeader(header, connNum, conn, function (err, hdStr) {
                        conn.resume();
                        respHeader = JSON.parse(hdStr);
                    });
                }
            } else if (header.upload) {
                dsize += data.length;
                if (false === stream.write(data)) {
                    conn.pause();
                }
            } else {
                assert(data.toString(), 'ACK');
                // Creating stream MUST be after receving "ACK"
                stream = createFileDownloadStream(header, connNum);

                stream.on('data', function (data) {
                    dsize += data.length;
                    var dprogress1 = Math.round(dsize * 100 / respHeader.size);
                    // period = 1 sec
                    var currTime = new Date();
                    if (dsize >= respHeader.size ||
                        !oldTime || (currTime - oldTime) >= 1000) {

                        dfsLog.info(connNum + ' Transfering...' + dsize + ' (' + dprogress1 + '%)');
                        oldTime = currTime;
                    }
                    if (false === conn.write(data)) {
                        paused = true;
                        stream.pause();
                    }
                });

                stream.on('error', function (err) {
                    dfsLog.error(err);
                    conn.destroy();
                });

                conn.on('drain', function () {
                    if (paused) {
                        paused = false; stream.resume();
                    }
                });

                stream.on('end', function () {
                    dfsLog.info(connNum + ' Reading file stream done!');

                    // update usedTime
                    parent.updateUsedTime(header.filePath);
                });
            }
        });


        conn.on('end', function () {
            dfsLog.warn(connNum + ' Received FIN signal from client!');
            if (header && header.upload && stream) {
                stream.end();
            }
        });

        conn.on('close', function (err, had_error) {
            conn.destroy();
            if (had_error) {
                dfsLog.warn(connNum + ' Connection closed by error!');
            } else {
                dfsLog.info(connNum + ' Connection closed!');
            }
        });

        conn.on('error', function (err) {
            dfsLog.error(err);
        });

        conn.on('timeout', function () {
            dfsLog.error(connNum + ' Connection timed out from client!');
        });

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


    function createResponseHeader(size, checksum) {
        var respHeader = {
            size: size,
            checksum: checksum
        };

        return JSON.stringify(respHeader);
    }


    function createErrorResponseHeader(msg) {
        var respHeader = {
            size: 0,
            checksum: '',
            errorMsg: msg
        };

        return JSON.stringify(respHeader);
    }
}


// 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);
        }
    });
}
