<commit_before>'use strict'

const config = require(__dirname + '/../config')
const GitHub = require('./GitHub')
const md5 = require('md5')
const NodeRSA = require('node-rsa')
const Notification = require('./Notification')
const objectPath = require('object-path')
const SiteConfig = require(__dirname + '/../siteConfig')
const SubscriptionsManager = require('./SubscriptionsManager')
const uuid = require('node-uuid')
const yaml = require('js-yaml')

const Staticman = function (options, localConfig) {
  this.options = options
  this.localConfig = localConfig

  // Initialise GitHub API
  this.github = new GitHub({
    username: this.options.username,
    repository: this.options.repository,
    branch: this.options.branch
  })

  // Generate unique id
  this.uid = uuid.v1()

  // Initialise RSA
  this.rsa = new NodeRSA()
  this.rsa.importKey(config.get('rsaPrivateKey'))
}

Staticman.prototype._applyInternalFields = function (data) {
  let internalFields = {
    _id: this.uid
  }

  // Inject parent, if present
  if (this.options.parent) {
    internalFields._parent = this.options.parent
  }

  return Object.assign(internalFields, data)
}

Staticman.prototype._applyGeneratedFields = function (data) {
  const generatedFields = this.siteConfig.get('generatedFields')

  if (!generatedFields) return data

  Object.keys(generatedFields).forEach(field => {
    const generatedField = generatedFields[field]

    if (typeof generatedField === 'object') {
      const options = generatedField.options || {}

      switch (generatedField.type) {
        case 'date':
          data[field] = this._createDate(options)

          break
      }
    } else {
      data[field] = generatedField
    }
  })

  return data
}

Staticman.prototype._applyTransforms = function (data) {
  const transforms = this.siteConfig.get('transforms')

  if (!transforms) return Promise.resolve(data)

  // This doesn't serve any purpose for now, but we might want to have
  // asynchronous transforms in the future.
  let queue = []

  Object.keys(transforms).forEach(field => {
    if (!data[field]) return

    if (transforms[field] === 'md5') {
      data[field] = md5(data[field])
    }
  })

  return Promise.all(queue).then((results) => {
    return data
  })
}

Staticman.prototype._checkForSpam = function (fields) {
  if (!this.siteConfig.get('akismet.enabled')) return Promise.resolve(fields)

  return new Promise((resolve, reject) => {
    const akismet = require('akismet').client({
      blog: config.get('akismet.site'),
      apiKey: config.get('akismet.apiKey')
    })

    akismet.checkSpam({
      user_ip: this.ip,
      comment_author: fields[this.siteConfig.get('akismet.author')],
      comment_content: fields[this.siteConfig.get('akismet.content')]
    }, (err, isSpam) => {
      if (err) return reject(err)
      
      if (isSpam) return reject('IS_SPAM')

      return resolve(fields)
    })    
  })
}

Staticman.prototype._createDate = function (options) {
  const date = new Date()

  switch (options.format) {
    case 'timestamp':
      return date.getTime()

    case 'timestamp-seconds':
      return Math.floor(date.getTime() / 1000)

    case 'iso8601':
    default:
      return date.toISOString()
  }
}

Staticman.prototype._createFile = function (fields) {
  return new Promise((resolve, reject) => {
    switch (this.siteConfig.get('format').toLowerCase()) {
      case 'json':
        return resolve(JSON.stringify(fields))

      case 'yaml':
      case 'yml':
        try {
          const output = yaml.safeDump(fields)

          return resolve(output)
        } catch (err) {
          return reject(err)
        }

        break

      case 'frontmatter':
        const transforms = this.siteConfig.get('transforms')

        const contentField = transforms && Object.keys(transforms).find(field => {
          return transforms[field] === 'frontmatterContent'
        })

        if (!contentField) {
          return reject('NO_FRONTMATTER_CONTENT_TRANSFORM')
        }

        const content = fields[contentField]

        delete fields[contentField]

        try {
          const output = `---\n${yaml.safeDump(fields)}---\n${content}\n`

          return resolve(output)
        } catch (err) {
          return reject(err)
        }

        break

      default:
        return reject('INVALID_FORMAT')
    }
  })
}

Staticman.prototype._getConfig = function (force) {
  if (this.siteConfig && !force) return Promise.resolve(this.siteConfig)

  if (!this.options.config) return Promise.reject('NO_CONFIG_PATH')

  return this.github.readFile(this.options.config.file).then(data => {
    const config = objectPath.get(data, this.options.config.path)
    const validationErrors = this._validateConfig(config)

    if (validationErrors) {
      return Promise.reject(validationErrors)
    }

    if (config.branch !== this.options.branch) {
      return Promise.reject('BRANCH_MISMATCH')
    }
  })
}

Staticman.prototype._getNewFilePath = function (data) {
  const configFilename = this.siteConfig.get('filename')
  const filename = configFilename ? this._resolvePlaceholders(configFilename, {
    fields: data,
    options: this.options
  }) : this.uid

  const path = this._resolvePlaceholders(this.siteConfig.get('path'), {
    fields: data,
    options: this.options
  })

  const extension = this._getExtensionForFormat(this.siteConfig.get('format'))

  return `${path}/${filename}.${extension}`
}

Staticman.prototype._getExtensionForFormat = function (format) {
  switch (format.toLowerCase()) {
    case 'json':
      return 'json'

    case 'yaml':
    case 'yml':
      return 'yml'

    case 'frontmatter':
      return 'md'
  }
}

Staticman.prototype._processSubscriptions = function (fields, options) {
  // Initialise SubscriptionsManager
  const subscriptions = new SubscriptionsManager(this.github, this.rsa)

  if (!options.parent) return Promise.resolve(true)

  // Process existing subscriptions<commit_msg>Add special case for array in generated fields<commit_after>'use strict'

const config = require(__dirname + '/../config')
const GitHub = require('./GitHub')
const md5 = require('md5')
const NodeRSA = require('node-rsa')
const Notification = require('./Notification')
const objectPath = require('object-path')
const SiteConfig = require(__dirname + '/../siteConfig')
const SubscriptionsManager = require('./SubscriptionsManager')
const uuid = require('node-uuid')
const yaml = require('js-yaml')

const Staticman = function (options, localConfig) {
  this.options = options
  this.localConfig = localConfig

  // Initialise GitHub API
  this.github = new GitHub({
    username: this.options.username,
    repository: this.options.repository,
    branch: this.options.branch
  })

  // Generate unique id
  this.uid = uuid.v1()

  // Initialise RSA
  this.rsa = new NodeRSA()
  this.rsa.importKey(config.get('rsaPrivateKey'))
}

Staticman.prototype._applyInternalFields = function (data) {
  let internalFields = {
    _id: this.uid
  }

  // Inject parent, if present
  if (this.options.parent) {
    internalFields._parent = this.options.parent
  }

  return Object.assign(internalFields, data)
}

Staticman.prototype._applyGeneratedFields = function (data) {
  const generatedFields = this.siteConfig.get('generatedFields')

  if (!generatedFields) return data

  Object.keys(generatedFields).forEach(field => {
    const generatedField = generatedFields[field]

    if ((typeof generatedField === 'object') && (!(generatedField instanceof Array))) {
      const options = generatedField.options || {}

      switch (generatedField.type) {
        case 'date':
          data[field] = this._createDate(options)

          break
      }
    } else {
      data[field] = generatedField
    }
  })

  return data
}

Staticman.prototype._applyTransforms = function (data) {
  const transforms = this.siteConfig.get('transforms')

  if (!transforms) return Promise.resolve(data)

  // This doesn't serve any purpose for now, but we might want to have
  // asynchronous transforms in the future.
  let queue = []

  Object.keys(transforms).forEach(field => {
    if (!data[field]) return

    if (transforms[field] === 'md5') {
      data[field] = md5(data[field])
    }
  })

  return Promise.all(queue).then((results) => {
    return data
  })
}

Staticman.prototype._checkForSpam = function (fields) {
  if (!this.siteConfig.get('akismet.enabled')) return Promise.resolve(fields)

  return new Promise((resolve, reject) => {
    const akismet = require('akismet').client({
      blog: config.get('akismet.site'),
      apiKey: config.get('akismet.apiKey')
    })

    akismet.checkSpam({
      user_ip: this.ip,
      comment_author: fields[this.siteConfig.get('akismet.author')],
      comment_content: fields[this.siteConfig.get('akismet.content')]
    }, (err, isSpam) => {
      if (err) return reject(err)
      
      if (isSpam) return reject('IS_SPAM')

      return resolve(fields)
    })    
  })
}

Staticman.prototype._createDate = function (options) {
  const date = new Date()

  switch (options.format) {
    case 'timestamp':
      return date.getTime()

    case 'timestamp-seconds':
      return Math.floor(date.getTime() / 1000)

    case 'iso8601':
    default:
      return date.toISOString()
  }
}

Staticman.prototype._createFile = function (fields) {
  return new Promise((resolve, reject) => {
    switch (this.siteConfig.get('format').toLowerCase()) {
      case 'json':
        return resolve(JSON.stringify(fields))

      case 'yaml':
      case 'yml':
        try {
          const output = yaml.safeDump(fields)

          return resolve(output)
        } catch (err) {
          return reject(err)
        }

        break

      case 'frontmatter':
        const transforms = this.siteConfig.get('transforms')

        const contentField = transforms && Object.keys(transforms).find(field => {
          return transforms[field] === 'frontmatterContent'
        })

        if (!contentField) {
          return reject('NO_FRONTMATTER_CONTENT_TRANSFORM')
        }

        const content = fields[contentField]

        delete fields[contentField]

        try {
          const output = `---\n${yaml.safeDump(fields)}---\n${content}\n`

          return resolve(output)
        } catch (err) {
          return reject(err)
        }

        break

      default:
        return reject('INVALID_FORMAT')
    }
  })
}

Staticman.prototype._getConfig = function (force) {
  if (this.siteConfig && !force) return Promise.resolve(this.siteConfig)

  if (!this.options.config) return Promise.reject('NO_CONFIG_PATH')

  return this.github.readFile(this.options.config.file).then(data => {
    const config = objectPath.get(data, this.options.config.path)
    const validationErrors = this._validateConfig(config)

    if (validationErrors) {
      return Promise.reject(validationErrors)
    }

    if (config.branch !== this.options.branch) {
      return Promise.reject('BRANCH_MISMATCH')
    }
  })
}

Staticman.prototype._getNewFilePath = function (data) {
  const configFilename = this.siteConfig.get('filename')
  const filename = configFilename ? this._resolvePlaceholders(configFilename, {
    fields: data,
    options: this.options
  }) : this.uid

  const path = this._resolvePlaceholders(this.siteConfig.get('path'), {
    fields: data,
    options: this.options
  })

  const extension = this._getExtensionForFormat(this.siteConfig.get('format'))

  return `${path}/${filename}.${extension}`
}

Staticman.prototype._getExtensionForFormat = function (format) {
  switch (format.toLowerCase()) {
    case 'json':
      return 'json'

    case 'yaml':
    case 'yml':
      return 'yml'

    case 'frontmatter':
      return 'md'
  }
}

Staticman.prototype._processSubscriptions = function (fields, options) {
  // Initialise SubscriptionsManager
  const subscriptions = new SubscriptionsManager(this.github, this.rsa)

  // If there isn't a parent or if notifications are not enabled, there's
  // nothing to do here
  if (!options.parent || !this.siteConfig.get('notifications.enabled')) {
    return Promise.resolve(true)
  }

  // Process existing subscriptions
{'fraction_non_alphanumeric': 0.1291276490882208, 'fraction_numerical': 0.002135699030721209, 'mean_word_length': 3.1623931623931623, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '458942', 'n_tokens_mistral': 3658, 'n_tokens_neox': 3520, 'n_words': 780}