/**
 * The Settings Modul reads the settings out of settings.json and provides
 * this information to the other modules
 */

/*
 * 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
 *
 * 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.
 */

var fs = require("fs");
var os = require("os");
var path = require('path');
var argv = require('./Cli').argv;
var npm = require("npm/lib/npm.js");
var jsonminify = require("jsonminify");
var log4js = require("log4js");
var randomString = require("./randomstring");
var suppressDisableMsg = " -- To suppress these warning messages change suppressErrorsInPadText to true in your settings.json\n";
var _ = require("underscore");

/* Root path of the installation */
exports.root = path.normalize(path.join(npm.dir, ".."));

/**
 * The app title, visible e.g. in the browser window
 */
exports.title = "Etherpad";

/**
 * The app favicon fully specified url, visible e.g. in the browser window
 */
exports.favicon = "favicon.ico";
exports.faviconPad = "../" + exports.favicon;
exports.faviconTimeslider = "../../" + exports.favicon;

/**
 * The IP ep-lite should listen to
 */
exports.ip = "0.0.0.0";

/**
 * The Port ep-lite should listen to
 */
exports.port = process.env.PORT || 9001;

/**
 * Should we suppress Error messages from being in Pad Contents
 */
exports.suppressErrorsInPadText = false;

/**
 * The SSL signed server key and the Certificate Authority's own certificate
 * default case: ep-lite does *not* use SSL. A signed server key is not required in this case.
 */
exports.ssl = false;

/**
 * socket.io transport methods
 **/
exports.socketTransportProtocols = ['xhr-polling', 'jsonp-polling', 'htmlfile'];

/*
 * The Type of the database
 */
exports.dbType = "dirty";
/**
 * This setting is passed with dbType to ueberDB to set up the database
 */
exports.dbSettings = { "filename" : path.join(exports.root, "dirty.db") };

/**
 * The default Text of a new pad
 */
exports.defaultPadText = "Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nEtherpad on Github: https:\/\/github.com\/ether\/etherpad-lite\n";

/**
 * The default Pad Settings for a user (Can be overridden by changing the setting
 */
exports.padOptions = {
  "noColors": false,
  "showControls": true,
  "showChat": true,
  "showLineNumbers": true,
  "useMonospaceFont": false,
  "userName": false,
  "userColor": false,
  "rtl": false,
  "alwaysShowChat": false,
  "chatAndUsers": false,
  "lang": "en-gb"
},

/**
 * Whether certain shortcut keys are enabled for a user in the pad
 */
exports.padShortcutEnabled = {
  "altF9" : true,
  "altC" : true,
  "delete" : true,
  "cmdShift2" : true,
  "return" : true,
  "esc" : true,
  "cmdS" : true,
  "tab" : true,
  "cmdZ" : true,
  "cmdY" : true,
  "cmdB" : true,
  "cmdI" : true,
  "cmdU" : true,
  "cmd5" : true,
  "cmdShiftL" : true,
  "cmdShiftN" : true,
  "cmdShift1" : true,
  "cmdShiftC" : true,
  "cmdH" : true,
  "ctrlHome" : true,
  "pageUp" : true,
  "pageDown" : true,
},

/**
 * The toolbar buttons and order.
 */
exports.toolbar = {
  left: [
    ["bold", "italic", "underline", "strikethrough"],
    ["orderedlist", "unorderedlist", "indent", "outdent"],
    ["undo", "redo"],
    ["clearauthorship"]
  ],
  right: [
    ["importexport", "timeslider", "savedrevision"],
    ["settings", "embed"],
    ["showusers"]
  ],
  timeslider: [
    ["timeslider_export", "timeslider_settings", "timeslider_returnToPad"]
  ]
}

/**
 * A flag that requires any user to have a valid session (via the api) before accessing a pad
 */
exports.requireSession = false;

/**
 * A flag that prevents users from creating new pads
 */
exports.editOnly = false;

/**
 * A flag that bypasses password prompts for users with valid sessions
 */
exports.sessionNoPassword = false;

/**
 * Max age that responses will have (affects caching layer).
 */
exports.maxAge = 1000*60*60*6; // 6 hours

/**
 * A flag that shows if minification is enabled or not
 */
exports.minify = true;

/**
 * The path of the abiword executable
 */
exports.abiword = null;

/**
 * The path of the libreoffice executable
 */
exports.soffice = null;

/**
 * The path of the tidy executable
 */
exports.tidyHtml = null;

/**
 * Should we support none natively supported file types on import?
 */
exports.allowUnknownFileEnds = true;

/**
 * The log level of log4js
 */
exports.loglevel = "INFO";

/**
 * Disable IP logging
 */
exports.disableIPlogging = false;

/**
 * Number of seconds to automatically reconnect pad
 */
exports.automaticReconnectionTimeout = 0;

/**
 * Disable Load Testing
 */
exports.loadTest = false;

/**
 * Enable indentation on new lines
 */
exports.indentationOnNewLine = true;

/*
* log4js appender configuration
*/
exports.logconfig = { appenders: [{ type: "console" }]};

/*
* Session Key, do not sure this.
*/
exports.sessionKey = false;

/*
* Trust Proxy, whether or not trust the x-forwarded-for header.
*/
exports.trustProxy = false;

/* This setting is used if you need authentication and/or
 * authorization. Note: /admin always requires authentication, and
 * either authorization by a module, or a user with is_admin set */
exports.requireAuthentication = false;
exports.requireAuthorization = false;
exports.users = {};

/*
* Show settings in admin page, by default it is true
*/
exports.showSettingsInAdminPage = true;

/*
* By default, when caret is moved out of viewport, it scrolls the minimum height needed to make this
* line visible.
*/
exports.scrollWhenFocusLineIsOutOfViewport = {
  /*
  * Percentage of viewport height to be additionally scrolled.
  */
  "percentage": {
    "editionAboveViewport": 0,
    "editionBelowViewport": 0
  },
  /*
  * Time (in milliseconds) used to animate the scroll transition. Set to 0 to disable animation
  */
  "duration": 0,
  /*
  * Flag to control if it should scroll when user places the caret in the last line of the viewport
  */
  /*
  * Percentage of viewport height to be additionally scrolled when user presses arrow up
  * in the line of the top of the viewport.
   */
  "percentageToScrollWhenUserPressesArrowUp": 0,
  "scrollWhenCaretIsInTheLastLineOfViewport": false
};

//checks if abiword is avaiable
exports.abiwordAvailable = function()
{
  if(exports.abiword != null)
  {
    return os.type().indexOf("Windows") != -1 ? "withoutPDF" : "yes";
  }
  else
  {
    return "no";
  }
};

exports.sofficeAvailable = function () {
  if(exports.soffice != null) {
    return os.type().indexOf("Windows") != -1 ? "withoutPDF": "yes";
  } else {
    return "no";
  }
};

exports.exportAvailable = function () {
  var abiword = exports.abiwordAvailable();
  var soffice = exports.sofficeAvailable();

  if(abiword == "no" && soffice == "no") {
    return "no";
  } else if ((abiword == "withoutPDF" && soffice == "no") || (abiword == "no" && soffice == "withoutPDF")) {
    return "withoutPDF";
  } else {
    return "yes";
  }
};

// Provide git version if available
exports.getGitCommit = function() {
  var version = "";
  try
  {
    var rootPath = path.resolve(npm.dir, '..');
    if (fs.lstatSync(rootPath + '/.git').isFile()) {
      rootPath = fs.readFileSync(rootPath + '/.git', "utf8");
      rootPath = rootPath.split(' ').pop().trim();
    } else {
      rootPath += '/.git';
    }
    var ref = fs.readFileSync(rootPath + "/HEAD", "utf-8");
    var refPath = rootPath + "/" + ref.substring(5, ref.indexOf("\n"));
    version = fs.readFileSync(refPath, "utf-8");
    version = version.substring(0, 7);
  }
  catch(e)
  {
    console.warn("Can't get git version for server header\n" + e.message)
  }
  return version;
}

// Return etherpad version from package.json
exports.getEpVersion = function() {
  return require('ep_etherpad-lite/package.json').version;
}

exports.reloadSettings = function reloadSettings() {
  // Discover where the settings file lives
  var settingsFilename = argv.settings || "settings.json";

  // Discover if a credential file exists
  var credentialsFilename = argv.credentials || "credentials.json";

  if (path.resolve(settingsFilename)===settingsFilename) {
    settingsFilename = path.resolve(settingsFilename);
  } else {
    settingsFilename = path.resolve(path.join(exports.root, settingsFilename));
  }

  if (path.resolve(credentialsFilename)===credentialsFilename) {
    credentialsFilename = path.resolve(credentialsFilename);
  }

  var settingsStr, credentialsStr;
  try{
    //read the settings sync
    settingsStr = fs.readFileSync(settingsFilename).toString();
  } catch(e){
    console.warn('No settings file found. Continuing using defaults!');
  }

  try{
    //read the credentials sync
    credentialsStr = fs.readFileSync(credentialsFilename).toString();
  } catch(e){
    // Doesn't matter if no credentials file found..
  }

  // try to parse the settings
  var settings;
  var credentials;
  try {
    if(settingsStr) {
      settingsStr = jsonminify(settingsStr).replace(",]","]").replace(",}","}");
      settings = JSON.parse(settingsStr);
    }
  }catch(e){
    console.error('There was an error processing your settings.json file: '+e.message);
    process.exit(1);
  }

  if(credentialsStr) {
    credentialsStr = jsonminify(credentialsStr).replace(",]","]").replace(",}","}");
    credentials = JSON.parse(credentialsStr);
  }

  //loop trough the settings
  for(var i in settings)
  {
    //test if the setting start with a low character
    if(i.charAt(0).search("[a-z]") !== 0)
    {
      console.warn("Settings should start with a low character: '" + i + "'");
    }

    //we know this setting, so we overwrite it
    //or it's a settings hash, specific to a plugin
    if(exports[i] !== undefined || i.indexOf('ep_')==0)
    {
      if (_.isObject(settings[i]) && !_.isArray(settings[i])) {
        exports[i] = _.defaults(settings[i], exports[i]);
      } else {
        exports[i] = settings[i];
      }
    }
    //this setting is unkown, output a warning and throw it away
    else
    {
      console.warn("Unknown Setting: '" + i + "'. This setting doesn't exist or it was removed");
    }
  }

  //loop trough the settings
  for(var i in credentials)
  {
    //test if the setting start with a low character
    if(i.charAt(0).search("[a-z]") !== 0)
    {
      console.warn("Settings should start with a low character: '" + i + "'");
    }

    //we know this setting, so we overwrite it
    //or it's a settings hash, specific to a plugin
    if(exports[i] !== undefined || i.indexOf('ep_')==0)
    {
      if (_.isObject(credentials[i]) && !_.isArray(credentials[i])) {
        exports[i] = _.defaults(credentials[i], exports[i]);
      } else {
        exports[i] = credentials[i];
      }
    }
    //this setting is unkown, output a warning and throw it away
    else
    {
      console.warn("Unknown Setting: '" + i + "'. This setting doesn't exist or it was removed");
    }
  }

  log4js.configure(exports.logconfig);//Configure the logging appenders
  log4js.setGlobalLogLevel(exports.loglevel);//set loglevel
  process.env['DEBUG'] = 'socket.io:' + exports.loglevel; // Used by SocketIO for Debug
  log4js.replaceConsole();

  if(exports.abiword){
    // Check abiword actually exists
    if(exports.abiword != null)
    {
      fs.exists(exports.abiword, function(exists) {
        if (!exists) {
          var abiwordError = "Abiword does not exist at this path, check your settings file";
          if(!exports.suppressErrorsInPadText){
            exports.defaultPadText = exports.defaultPadText + "\nError: " + abiwordError + suppressDisableMsg;
          }
          console.error(abiwordError);
          exports.abiword = null;
        }
      });
    }
  }

  if(exports.soffice) {
    fs.exists(exports.soffice, function (exists) {
      if(!exists) {
        var sofficeError = "SOffice does not exist at this path, check your settings file";

        if(!exports.suppressErrorsInPadText) {
          exports.defaultPadText = exports.defaultPadText + "\nError: " + sofficeError + suppressDisableMsg;
        }
        console.error(sofficeError);
        exports.soffice = null;
      }
    });
  }

  if (!exports.sessionKey) {
    var sessionkeyFilename = argv.sessionkey || "./SESSIONKEY.txt";
    try {
      exports.sessionKey = fs.readFileSync(sessionkeyFilename,"utf8");
    } catch(e) {
      exports.sessionKey = randomString(32);
      fs.writeFileSync(sessionkeyFilename,exports.sessionKey,"utf8");
    }
  } else {
    console.warn("Declaring the sessionKey in the settings.json is deprecated. This value is auto-generated now. Please remove the setting from the file.");
  }

  if(exports.dbType === "dirty"){
    var dirtyWarning = "DirtyDB is used. This is fine for testing but not recommended for production.";
    if(!exports.suppressErrorsInPadText){
      exports.defaultPadText = exports.defaultPadText + "\nWarning: " + dirtyWarning + suppressDisableMsg;
    }
    console.warn(dirtyWarning);
  }
};

// initially load settings
exports.reloadSettings();


