
/**
 * Module dependencies
 */

var _ = require('underscore');
var fs = require('fs');
var path = require('path');
var Backbone = require('backbone');
var _Model = requirejs('libraries/Model');
var _Collection = requirejs('libraries/Collection');
var glob = require('glob');
var isArray = require('../libraries/isArray');
var importNames = [];
var imports = [];
var pages = [];
var optimized = false;

if(fs.existsSync(path.join(__dirname, '../public/scripts/mains/app.js'))) {
  optimized = true;
}
/**
 * Add backbone relational
 */

require('backbone-relational');

/**
 * Add terminal colors
 */

require('terminal-colors');

/**
 * The Page object builds up the whole html for your website.
 * You can specify document, layout, module and components to
 * customize the html you want.
 *
 * @constructor Page
 * @return {Page}
 */

function Page(url) {
  if(typeof url !== 'string'
  && !isArray(url)) {
    throw new TypeError('first parameter must be a string or array of strings');
  }

  this._url = url;
  this._documentTmpls = documentTmpls;
  this._layoutTmpls = layoutTmpls;

  _.bindAll(this, '_next');

  return this;
}

/**
 * Set document
 *
 * @param {String} name
 * @param {Object} opts
 * @return {Page}
 * @api public
 */

Page.prototype.hasDocument = function(name, props) {
  if(typeof this._documentTmpls[name] === 'undefined') {
    throw new TypeError('document ' + name + ' does not exist');
  }

  this._documentTmpl = this._documentTmpls[name];

  return this;
};

/**
 * Set document
 *
 * @param {String} name
 * @param {Object} opts
 * @return {Page}
 * @api public
 */

Page.prototype.withProperties = function(properties) {
  properties.configurations = properties.configurations.map(function(configuration) {
    return '/' + cf.CLIENT_CONFIGURATIONS_BUILD + '/'  + cf.CLIENT_CONFIGURATIONS_MAP[configuration] + '.js';
  });
  this._documentProps = properties;

  if(typeof properties.title === 'string') {
    this._documentProps.renderedTitle = properties.title;
  }
  if(typeof properties.description === 'string') {
    this._documentProps.renderedTitle = properties.description;
  }
  return this;
};

/**
 * Set layout
 *
 * @param {String} name
 * @return {Page}
 * @api public
 */

Page.prototype.hasLayout = function(name) {
  if(typeof this._layoutTmpls[name] === 'undefined') {
    throw new TypeError(name + ' layout doesn\'t exists');
  }

  this._layout = name;

  this._layoutTmpl = this._layoutTmpls[name];

  return this;
};

/**
 * Define (modules) views and models
 *
 * @param {Object} modules
 * @return {void}
 * @api public
 */

Page.prototype.withRegions = function(regions) {
  this.regions = regions;
  this._serve();

  return this;
};

/**
 * Get content
 *
 * @param {Function} callback
 * @return {void}
 * @api private
 */

Page.prototype._getRegions = function(callback, req) {
  var _this = this
    , n = 0
    , regions = {}
    , size = _.size(this.regions)
    , jsonScripts = '';

  for(var name in this.regions) {
    if(!this.regions[name].model) {
      throw new TypeError('name `' + name + '` doesn\'t have a model');
    }
    if(!this.regions[name].view) {
      throw new TypeError('name `' + name + '` doesn\'t have a view');
    }

    var Model = require('../' + this.regions[name].model)
      , View = require('../' + this.regions[name].view);

    if(!Model.prototype.fetch) {
      throw new TypeError(this.regions[name].model + ' is not an instance of Model or Collection');
    }

    Backbone.Relational.store.reset();

    var model = new Model
      , view = new View(model);

    model.sync = function(method, model, opts) {
      Model.prototype.sync.call(this, method, model, opts, req);
    };

    (function(name, model, view, View) {

      try {
        model.fetch({
          success: function() {
            view = new View(model)
            regions[name] = view.toHTML();

            if(typeof model.page.title === 'string'
            && model.page.title.length > 0
            && typeof _this._documentProps.title !== 'string') {
              _this._documentProps.renderedTitle = model.page.title;
            }
            if(typeof model.page.description === 'string'
            && model.page.description.length > 0
            && typeof _this._documentProps.description !== 'string') {
              _this._documentProps.renderedDescription = model.page.description;
            }

            // Push json scripts
            jsonScripts += coreTmpls.jsonScript({
              name : _this.regions[name].model.split('/')[2].toLowerCase(),
              json : _.escape(JSON.stringify(model.toJSON()))
            });

            n++;
            if(n === size) {
              callback(regions, jsonScripts);
            }
          },
          error: _this._errorCallback
        });
      }
      catch(error) {
        if(error.message === 'A "url" property or function must be specified') {
          regions[name] = view.toHTML();
          n++;
          if(n === size) {
            callback(regions, jsonScripts);
          }
        }
        else {
          throw error;
        }
      }

    })(name, model, view, View);
  }
};

/**
 * If something fails it will be forwarded to this callback
 *
 * @callback
 */

Page.prototype.handleErrorsUsing = function(callback) {
  var _this = this;
  this._errorCallback = function(model, response, options) {
    callback.call(_this, response);
  };
};

/**
 * Serve the page
 *
 * @return {void}
 * @api private
 */

Page.prototype._serve = function() {
  var _this = this;
  this._addContent();
  this._addPages();
  app.get(this._url, _this._next);
};

/**
 * Next callback
 */

Page.prototype._next = function(req, res) {
  var _this = this;

  this._getRegions(function(regions, jsonScripts) {
    var html = _this._documentTmpl({
      title: _this._documentProps.renderedTitle,
      description: _this._documentProps.renderedDescription,
      locale: _this._documentProps.locale,
      styles: _this._documentProps.styles,
      configurations: _this._documentProps.configurations,
      main: optimized ? '/public/scripts/mains/app.js' : _this._documentProps.main,
      jsonScripts: jsonScripts,
      layout: _this._layoutTmpl(regions),
      modernizr: cf.MODERNIZR,
      requirejs: cf.REQUIREJS,
      useGoogleAnalytics: !process.env.L10NS_DEV
    });

    res.send(html);
  }, req);
};

/**
 * Get content name
 *
 * @param {String} path
 * @api private
 */

Page.prototype._getConstructorName = function(path) {
  return _.last(path.split('/'));
};

/**
 * We want to add content to the content varaiable, to compile
 * the composite routers.
 *
 * @param {Object} content
 * @api private
 */

Page.prototype._addContent = function() {
  for(var i in this.regions) {
    var region = this.regions[i];
    var name = this._getConstructorName(region.model);

    // If content have been stored continue the loop
    if(importNames.indexOf(name) !== -1) {
      continue;
    }

    var _import = {
      model : {
        name : name,
        path : region.model
      },
      view : {
        name : this._getConstructorName(region.view),
        path : region.view
      }
    }
    imports.push(_import);
    importNames.push(name);
  }
};

/**
 * We want to add pages to the pages variable, to compile
 * the composite routers.
 *
 * @param {Object} content
 * @api private
 */

Page.prototype._addPages = function() {
  var path = this._url.substr(1);
  var page = {
    path : path,
    layout : this._layout
  };
  var regions = [], views = [];
  for(var name in this.regions) {
    var map = {};
    var model = this._getConstructorName(this.regions[name].model);
    map['model'] = model;
    map['view'] = this._getConstructorName(this.regions[name].view);
    map['region'] = name;
    map['path'] = path;
    views.push(model.toLowerCase());
    regions.push(map);
  }
  page.contentScript = coreTmpls['contentScript'](regions);
  page.renderScript = coreTmpls['renderScript'](regions);
  page.mapScript = coreTmpls['mapScript'](regions);
  page.noViewsScript = coreTmpls['noViewsScript'](views);
  pages.push(page);
};

/**
 * Export constructor
 */

module.exports = function(url) {
  return new Page(url);
};


module.exports.createCompositeRouter = function() {
  var router = coreTmpls['compositeRouter']({ pages : pages, imports : imports });
  var routerFile = cf.ROOT_FOLDER + cf.COMPOSER_BUILD_PATH, router;
  if (!fs.existsSync(path.dirname(routerFile))) {
    fs.mkdirSync(path.dirname(routerFile));
  }
  fs.writeFileSync(routerFile, router);
};

/**
 * Read template
 *
 * @exit if built files doesn't exist
 * @return {void}
 */

module.exports.readTemplates = function() {
  if(!fs.existsSync(path.join(__dirname, '../', cf.DOCUMENT_TEMPLATES + '.js'))) {
    console.log('[:(]'.red + ' Have you built your document templates yet?');
    process.exit();
  }
  if(!fs.existsSync(path.join(__dirname, '../', cf.LAYOUT_TEMPLATES + '.js'))) {
    console.log('[:(]'.red + ' Have you built your layout templates yet?');
    process.exit();
  }

  global.documentTmpls = requirejs(cf.DOCUMENT_TEMPLATES);
  global.layoutTmpls = requirejs(cf.LAYOUT_TEMPLATES);
  global.coreTmpls = requirejs(cf.CORE_TEMPLATES);
};
