
const path = require('path');

const gulp = require('gulp');
const debug = require('gulp-debug');
const del = require("del");
const through = require('through2');
const Vinyl = require("vinyl");
const merge = require('merge-stream');
const rename = require("gulp-rename");
const concat = require('gulp-concat');
const add = require('gulp-add');
const sourcemaps = require('gulp-sourcemaps');
const runSequence = require('run-sequence');
const uglify = require('gulp-uglify');
const babel = require('gulp-babel');
const browserify = require('browserify');
const buffer = require("vinyl-buffer");
const source = require('vinyl-source-stream');
const argv = require('yargs').argv;
const gulpif = require('gulp-if');
const gutil = require('gulp-util');

const modulifyHeaders = {
    model: 
`exports.model = Model = {
    Game: {},
    Board: {},
    Move: {}
};
`
    ,
    view: 
`exports.view = View = {
    Game: {},
    Board: {},
    Move: {}
};
`
};

const allGames = {};

function HandleModuleGames(modelOnly) {

    return through.obj(function(file,enc,next) {
        // this is executed for every game module
        var push = this.push.bind(this);
        var moduleName = path.basename(file.path);
        var moduleManifest = require(file.path);
        var streams = [];
        moduleManifest.games.forEach((game) => {
            // this is executed for every game in the game module

            // same some game data so we can list all games later
            allGames[game.name] = {
                title: game.config.model["title-en"],
                summary: game.config.model.summary,
                thumbnail: game.config.model.thumbnail,
                module: moduleName
            };

            // create the game config file
            push(new Vinyl({
                path: moduleName+"/"+game.name+"-config.js",
                contents: new Buffer('exports.config = '+JSON.stringify(game.config))
            }));

            // create some specified resources
            if(!modelOnly)
                ["thumbnail","rules","description","credits"].forEach((field)=>{
                    var files = [];
                    switch(typeof game.config.model[field]) {
                        case "string":
                            files.push(game.config.model[field]);
                            break;
                        case "object":
                            for(var f in game.config.model[field])
                                files.push(game.config.model[field][f]);
                            break;
                    }
                    files = files.map((file)=>{
                        return "src/games/"+moduleName+"/"+file;
                    });
                    var stream = gulp.src(files)
                        .pipe(rename(function(path) {
                            path.dirname = moduleName;
                        }))
                        .pipe(through.obj(function(file,enc,next) {
                            push(file);
                            next();
                        }))
                        ;
                    streams.push(stream);
                });

            // create model and view script files
            function Scripts(which) {
                var scripts = game[which+"Scripts"].map((script) => {
                    return "src/games/"+moduleName+"/"+script;
                });
                var fileName = moduleName+"/"+game.name+"-"+which+".js";
                var stream = gulp.src(scripts)
                    .pipe(gulpif(!argv.prod,sourcemaps.init()))
                    .pipe(add('_',modulifyHeaders[which],true))
                    .pipe(concat(fileName))
                    .pipe(gulpif(argv.prod,uglify()))
                    .pipe(gulpif(!argv.prod,sourcemaps.write('.')))
                    .pipe(through.obj(function(file,enc,next) {
                        push(file);
                        next();
                    }))
                streams.push(stream);
            }
            if(modelOnly)
                Scripts("model");
            else
                ["model","view"].forEach(Scripts);
        });

        // create module common resources
        if(!modelOnly) {
            var stream = gulp.src("src/games/"+moduleName+"/res/**/*")
                .pipe(rename(function(path) {
                    path.dirname = moduleName + "/res/" +path.dirname;
                }))
                .pipe(through.obj(function(file,enc,next) {
                    push(file);
                    next();
                }))
                ;
            streams.push(stream);
        }

        merge(streams)
            .on("finish",function() {
                next();
            })
    
    });
}

gulp.task("build-node-games",function() {
    return gulp.src("src/games/*")
        .pipe(HandleModuleGames(true))
        .pipe(gulp.dest("dist/node/games"));
});

function ProcessJS(stream,concatName,skipBabel) {
    if(!argv.prod)
        stream = stream.pipe(sourcemaps.init());
    if(!skipBabel)
        stream = stream.pipe(babel({
            presets: ['es2015']
        }));
    if(argv.prod)
        stream = stream.pipe(uglify())
            .on('error', function(err) {
                gutil.log(gutil.colors.red('[Error]'), err.toString());
                this.emit('end');
            })
    if(concatName)
        stream = stream.pipe(concat(concatName));
    if(!argv.prod)
        stream = stream.pipe(sourcemaps.write("."));    
    return stream;
}

gulp.task("build-node-core",function() {

    var joclyCoreStream = 
        ProcessJS(gulp.src([
            "src/core/jocly.core.js",
        ]));

    var joclyBaseStream = 
        ProcessJS(gulp.src([
            "src/core/jocly.util.js",
            "src/core/jocly.uct.js",
            "src/core/jocly.game.js"
        ]));

    var joclyExtraStream = gulp.src([
            "src/node/package.json",
        ]);

    var joclyExtraScriptStream = 
        ProcessJS(gulp.src([
            "src/node/jocly.proxy.js"
        ]));

    var allGamesStream = source('jocly-allgames.js');
    allGamesStream.end('exports.games = '+JSON.stringify(allGames));
    allGamesStream = ProcessJS(allGamesStream.pipe(buffer()));

    return merge(joclyCoreStream,allGamesStream,joclyBaseStream,
        joclyExtraStream,joclyExtraScriptStream)
        .pipe(gulp.dest("dist/node"));

})

function CopyLicense(target) {
    return gulp.src(["COPYING.md","CONTRIBUTING.md","AGPL-3.0.txt"])
        .pipe(gulp.dest(target));
}

gulp.task("copy-browser-license",function() {
    return CopyLicense("dist/browser");
});

gulp.task("copy-node-license",function() {
    return CopyLicense("dist/node");
});

gulp.task("build-node",function(callback) {
    runSequence("build-node-games",["build-node-core","copy-node-license"],callback);
});

gulp.task("build-browser-games",function() {
    return gulp.src("src/games/*")
        .pipe(HandleModuleGames(false))
        .pipe(gulp.dest("dist/browser/games"));
});

gulp.task("build-browser-core",function() {
    
    var _ProcessJS = function(s) { return s; }

    var b = browserify({
        entries: "src/browser/jocly.js",
        debug: true,
        standalone: "Jocly"
    });

    var joclyBrowserStream = b.bundle()
        .pipe(source('jocly.js'))
        .pipe(buffer());

    var joclyCoreStream = ProcessJS(gulp.src([
        "src/core/jocly.core.js",
    ]));

    var joclyBaseStream = ProcessJS(gulp.src([
            "src/core/jocly.util.js",
            "src/core/jocly.uct.js",
            "src/core/jocly.game.js"
        ]),"jocly.game.js",true);

    var joclyExtraScriptsStream = ProcessJS(gulp.src([
            "src/browser/jocly.aiworker.js",
            "src/browser/jocly.proxy.js",
            "src/browser/jocly.embed.js"
        ]));

    var joclyExtraStream = gulp.src([
            "src/browser/jocly.embed.html"
        ]);

    var joclyResStream = gulp.src("src/browser/res/**/*")
        .pipe(rename(function(path) {
            path.dirname = "res/"+path.dirname;
        }));

    var allGamesStream = source('jocly-allgames.js');
    allGamesStream.end('exports.games = '+JSON.stringify(allGames));
    allGamesStream = ProcessJS(allGamesStream.pipe(buffer()));

    return merge(joclyBrowserStream,joclyCoreStream,allGamesStream,joclyBaseStream,
            joclyExtraStream,joclyExtraScriptsStream,joclyResStream)
        .pipe(gulp.dest("dist/browser"));

});

gulp.task("build-browser-xdview",function() {
    const lib = "third-party/";
    const src = "src/";
    const srcLib = "src/lib/";
    const nmLib = "node_modules/";

    var libs = ProcessJS(gulp.src([
            lib+"three.js",
            nmLib+"jquery/dist/jquery.js"
        ]));

    var packedLibs = ProcessJS(gulp.src([
            lib+"SubdivisionModifier.js",
            lib+"tween.js",
            lib+"tween.fix.js",
            srcLib+"JoclyOrbitControls.js",
            lib+"DeviceOrientationControls.js",
            lib+"Projector.js",
            lib+"threex.domevent.js",
            lib+"threex.domevent.object3d.js",
            lib+"StereoEffect.js",
            srcLib+"VRGamepad.js",
            lib+"VRControls.js",
            lib+"VREffect.js",
            src+"browser/jocly.ar.js",
            src+"browser/jocly.state-machine.js",
            src+"browser/jocly.xd-view.js"
        ]),"jocly-xdview.js",true);

    return merge(libs,packedLibs)
        .pipe(gulp.dest("dist/browser"))
    ;

});

gulp.task("build-browser",function(callback) {
    runSequence("build-browser-games",["build-browser-core",
        "build-browser-xdview","copy-browser-license"],callback);
});


gulp.task("build",function(callback) {
    runSequence("clean",["build-browser","build-node"],callback);
});

gulp.task("clean",function() {
    return del(["dist/*"],{force:true});
});

gulp.task("watch",function() {
    gulp.watch("src/games/**/*",["build-node-games","build-browser-games"]);
    gulp.watch("src/{browser,core,lib}",["build-browser-core","build-browser-xdview"]);
    gulp.watch("src/{node,core}",["build-node-core"]);
});

gulp.task("help", function() {
    var help = `
usage: gulp [<commands>] [<options>]

commands:
    build: generate clean project build
    watch: watch project and build dynamically on changes

options:
    --prod: generate for production
`;
    console.log(help);
    process.exit(0);
});
