// Canonical path provides a consistent path (i.e. always forward slashes) across different OSes
var path = require('canonical-path');
var Q = require('q');
var del = require('del');
// delPromise is a 'promise' version of del
var delPromise =  Q.denodeify(del);
var Dgeni = require('dgeni');
var _ = require('lodash');
var globby = require('globby');

var shred = function(shredOptions) {
  try {
    var pkg;
    if (shredOptions.jadeDir) {
      pkg = createShredJadePackage(shredOptions);
    } else {
      pkg = createShredExamplePackage(shredOptions);
    }
    var dgeni = new Dgeni([ pkg]);
    return dgeni.generate();
  } catch(err) {
    console.log(err);
    console.log(err.stack);
    throw err;
  }
}

var shredSingleDir = function(shredOptions, filePath) {
  shredOptions = resolveShredOptions(shredOptions);
  var fileDir = path.dirname(filePath);
  var relativePath = path.relative(shredOptions.examplesDir, fileDir);
  var examplesDir = path.join(shredOptions.examplesDir, relativePath);
  var fragmentsDir = path.join(shredOptions.fragmentsDir, relativePath);
  var options = {
    includeSubdirs: false,
    examplesDir: examplesDir,
    fragmentsDir: fragmentsDir
  }
  var cleanPath = path.join(fragmentsDir, '*.*')
  return delPromise([ cleanPath, '!**/*.ovr.*']).then(function(paths) {
    // console.log('Deleted files/folders:\n', paths.join('\n'));
    return shred(options);
  });
}

var shredSingleJadeDir = function(shredOptions, filePath) {
  shredOptions = resolveShredOptions(shredOptions);
  var fileDir = path.dirname(filePath);
  var relativePath = path.relative(shredOptions.jadeDir, fileDir);
  var jadeDir = path.join(shredOptions.jadeDir, relativePath);

  var options = {
    includeSubdirs: false,
    jadeDir: jadeDir
  }
  // var cleanPath = path.join(jadeDir, '_.*.jade')
  //return delPromise([ cleanPath]).then(function(paths) {
  //  console.log('Deleted files/folders:\n', paths.join('\n'));
  //  return shred(options);
  //});
  return shred(options);
}

var buildShredMap = function(shredMapOptions) {
  try {
    var pkg = createShredMapPackage(shredMapOptions);
    var dgeni = new Dgeni([ pkg]);
    return dgeni.generate();
  } catch(err) {
    console.log(err);
    console.log(err.stack);
    throw err;
  }
}

module.exports = {
  shred: shred,
  shredSingleDir: shredSingleDir,
  shredSingleJadeDir: shredSingleJadeDir,
  buildShredMap: buildShredMap
};

function createShredExamplePackage(shredOptions) {
  var pkg = new Dgeni.Package('doc-shredder', [
    // require('dgeni-packages/base') - doesn't work
  ]);
  var options = resolveShredOptions(shredOptions);

  initializePackage(pkg)
    .factory(require('./fileReaders/regionFileReader'))
    .processor(require('./processors/renderAsMarkdownProcessor'))

    .config(function(readFilesProcessor, regionFileReader) {
      readFilesProcessor.fileReaders = [regionFileReader];
    })
    // default configs - may be overridden
    .config(function(readFilesProcessor) {
      // Specify the base path used when resolving relative paths to source and output files
      readFilesProcessor.basePath = "/";

      // Specify collections of source files that should contain the documentation to extract
      var extns = ['*.ts', '*.html', '*.js', '*.css', '*.json', '*.dart', '*.yaml' ];
      var includeFiles = extns.map(function(extn) {
        if (options.includeSubdirs) {
          return path.join(options.examplesDir, '**', extn);
        } else {
          return path.join(options.examplesDir, extn);
        }
      });

      // HACK ( next two lines) because the glob function that dgeni uses internally isn't good at removing 'node_modules' early
      // this just uses globby to 'preglob' the include files ( and  exclude the node_modules).
      var nmPattern = '**/node_modules/**';
      var includeFiles = globby.sync( includeFiles, { ignore: [nmPattern] } );

      readFilesProcessor.sourceFiles = [ {
        // Process all candidate files in `src` and its subfolders ...
        include: includeFiles ,
        exclude: [ '**/node_modules/**', '**/typings/**', '**/packages/**', '**/build/**'],
        // When calculating the relative path to these files use this as the base path.
        // So `src/foo/bar.js` will have relative path of `foo/bar.js`
        basePath: options.examplesDir
      } ];
    })
    .config(function(writeFilesProcessor) {
      // Specify where the writeFilesProcessor will write our generated doc files
      writeFilesProcessor.outputFolder  = path.resolve(options.fragmentsDir);
    });
  return pkg;
}

function createShredJadePackage(shredOptions) {
  var pkg = new Dgeni.Package('jade-doc-shredder', [
    // require('dgeni-packages/base') - doesn't work
  ]);

  var options = shredOptions;
  options.jadeDir = path.resolve(options.jadeDir);
  options.includeSubdirs = options.includeSubdirs == null ? true : options.includeSubdirs;

  initializePackage(pkg)
    .factory(require('./fileReaders/regionFileReader'))
    .processor(require('./processors/renderAsJadeProcessor'))

    .config(function(readFilesProcessor, regionFileReader) {
      readFilesProcessor.fileReaders = [regionFileReader];
    })
    // default configs - may be overridden
    .config(function(readFilesProcessor) {
      // Specify the base path used when resolving relative paths to source and output files
      readFilesProcessor.basePath = "/";

      // Specify collections of source files that should contain the documentation to extract
      var extns = ['*.jade' ];
      var includeFiles = extns.map(function(extn) {
        if (options.includeSubdirs) {
          return path.join(options.jadeDir, '**', extn);
        } else {
          return path.join(options.jadeDir, extn);
        }
      });

      // HACK ( next two lines) because the glob function that dgeni uses internally isn't good at removing 'node_modules' early
      // this just uses globby to 'preglob' the include files ( and  exclude the node_modules).
      var nmPattern = '**/node_modules/**';
      var includeFiles = globby.sync( includeFiles, { ignore: [nmPattern] } );

      readFilesProcessor.sourceFiles = [ {
        // Process all candidate files in `src` and its subfolders ...
        include: includeFiles ,
        exclude: [ '**/node_modules/**', '**/typings/**', '**/packages/**', '**/build/**', '**/_code-examples.jade'],
        // When calculating the relative path to these files use this as the base path.
        // So `src/foo/bar.js` will have relative path of `foo/bar.js`
        basePath: options.jadeDir
      } ];
    })
    .config(function(writeFilesProcessor) {
      // Specify where the writeFilesProcessor will write our generated doc files
      writeFilesProcessor.outputFolder  = '.';
    });
  return pkg;
}


var createShredMapPackage = function(mapOptions) {
  var pkg = new Dgeni.Package('doc-shred-mapper', [
    require('dgeni-packages/base'),
    require('dgeni-packages/nunjucks')
  ]);
  var options = resolveMapOptions(mapOptions);

  initializePackage(pkg)
    .factory(require('./fileReaders/extractPathsReader'))
    .processor(require('./processors/shredMapProcessor'))
    .config(function(shredMapProcessor) {
      shredMapProcessor.options = options;
    })
    .config(function(readFilesProcessor, extractPathsReader ) {
      readFilesProcessor.fileReaders = [ extractPathsReader];
    })
    // default configs - may be overriden
    .config(function(readFilesProcessor) {
      // Specify the base path used when resolving relative paths to source and output files
      readFilesProcessor.basePath = '/';

      // Specify collections of source files that should contain the documentation to extract
      var extns = ['*.jade' ];
      var includeFiles = extns.map(function(extn) {
        if (options.includeSubdirs) {
          return path.join(options.jadeDir, '**', extn);
        } else {
          return path.join(options.jadeDir, extn);
        }
      });

      // HACK ( next two lines) because the glob function that dgeni uses internally isn't good at removing 'node_modules' early
      // this just uses globby to 'preglob' the include files ( and  exclude the node_modules).
      var nmPattern = '**/node_modules/**';
      var includeFiles = globby.sync( includeFiles, { ignore: [nmPattern] } );


      readFilesProcessor.sourceFiles = [ {
        // Process all candidate files in `src` and its subfolders ...
        include: includeFiles,
        exclude: ['**/node_modules/**', '**/typings/**', '**/packages/**', '**/build/**'],
        // When calculating the relative path to these files use this as the base path.
        // So `src/foo/bar.js` will have relative path of `foo/bar.js`
        basePath: options.jadeDir
      } ];
    })
    .config(function(writeFilesProcessor, renderDocsProcessor, unescapeCommentsProcessor) {
      if (!mapOptions.writeFilesEnabled) {
        // dgeni hack to allow a geni task to simply return the results of the shredMapProcessor directly
        renderDocsProcessor.$enabled = false;
        writeFilesProcessor.$enabled = false;
        unescapeCommentsProcessor.$enabled = false;
      } else {
        // Specify where the writeFilesProcessor will write our generated doc files
        writeFilesProcessor.outputFolder = path.resolve(options.outputDir);
      }
    })
    .config(function(templateFinder) {
      // look for templates in this folder
      templateFinder.templateFolders = [ path.resolve(__dirname) ];

      // Specify how to match docs to templates.
      // In this case we just use the same static template for all docs
      templateFinder.templatePatterns = [ '${ doc.docType }.template' ];
    })
    .config(function(computePathsProcessor, computeIdsProcessor)  {
      computePathsProcessor.$enabled = false;
      computeIdsProcessor.$enabled = false;
      // Unused for now.
      //computePathsProcessor.pathTemplates.push({
      //  docTypes: ['foo'],
      //  pathTemplate: '',
      //  getOutputPath: function () {
      //  },
      //});
      //
      //computeIdsProcessor.idTemplates.push({
      //  docTypes: ['foo'],
      //  getAliases: function (doc) {
      //    return [doc.id];
      //  }
      //});
    });



  return pkg;
}

function resolveShredOptions(shredOptions) {
  var DOCS_FOLDER = '../../public/docs';
  var so =  _.defaults({}, shredOptions, {
    // read files from any subdir under here
    examplesDir: path.join(DOCS_FOLDER, "_examples"),
    // shredded files get copied here with same subdir structure.
    fragmentsDir: path.join(DOCS_FOLDER, "_fragments"),
    // whether to include subdirectories when shredding.
    includeSubdirs: true
  });

  so.examplesDir = path.resolve(so.examplesDir);
  so.fragmentsDir = path.resolve(so.fragmentsDir);
  return so;
}

function resolveMapOptions(mapOptions) {
  var so =  _.defaults({}, mapOptions, {
    includeSubdirs: true
  });
  so.jadeDir = path.resolve(so.jadeDir);
  so.devguideExamplesDir = path.resolve(so.devguideExamplesDir);
  so.apiExamplesDir = path.resolve(so.apiExamplesDir);
  so.fragmentsDir = path.resolve(so.fragmentsDir);
  return so;
}

function initializePackage(pkg) {
  return pkg
    .processor(require('dgeni-packages/base/processors/read-files'))
    .processor(require('dgeni-packages/base/processors/write-files'))
    .factory(require('dgeni-packages/base/services/writeFile'))

    // Ugh... Boilerplate that dgeni needs to sequence operations
    .processor({ name: 'reading-files' })
    .processor({ name: 'files-read', $runAfter: ['reading-files'] })
    .processor({ name: 'processing-docs', $runAfter: ['files-read'] })
    .processor({ name: 'docs-processed', $runAfter: ['processing-docs'] })
    .processor({ name: 'adding-extra-docs', $runAfter: ['docs-processed'] })
    .processor({ name: 'extra-docs-added', $runAfter: ['adding-extra-docs'] })
    .processor({ name: 'computing-ids', $runAfter: ['extra-docs-added'] })
    .processor({ name: 'ids-computed', $runAfter: ['computing-ids'] })
    .processor({ name: 'computing-paths', $runAfter: ['ids-computed'] })
    .processor({ name: 'paths-computed', $runAfter: ['computing-paths'] })
    .processor({ name: 'rendering-docs', $runAfter: ['paths-computed'] })
    .processor({ name: 'docs-rendered', $runAfter: ['rendering-docs'] })
    .processor({ name: 'writing-files', $runAfter: ['docs-rendered'] })
    .processor({ name: 'files-written', $runAfter: ['writing-files'] })
    .config(function(log) {
      // Set logging level
      log.level = 'info';
    })
}
