module.exports.addLink = addLink
module.exports.addLinks = addLinks
module.exports.parseMetadataFromHeader = parseMetadataFromHeader
module.exports.linksHandler = linksHandler
module.exports.addPermissions = addPermissions

const li = require('li')
const path = require('path')
const metadata = require('./metadata.js')
const debug = require('./debug.js')
const utils = require('./utils.js')
const error = require('./http-error')

const MODES = ['Read', 'Write', 'Append', 'Control']
const PERMISSIONS = MODES.map(m => m.toLowerCase())

function addLink (res, value, rel) {
  const oldLink = res.get('Link')
  if (oldLink === undefined) {
    res.set('Link', '<' + value + '>; rel="' + rel + '"')
  } else {
    res.set('Link', oldLink + ', ' + '<' + value + '>; rel="' + rel + '"')
  }
}

function addLinks (res, fileMetadata) {
  if (fileMetadata.isResource) {
    addLink(res, 'http://www.w3.org/ns/ldp#Resource', 'type')
  }
  if (fileMetadata.isSourceResource) {
    addLink(res, 'http://www.w3.org/ns/ldp#RDFSource', 'type')
  }
  if (fileMetadata.isContainer) {
    addLink(res, 'http://www.w3.org/ns/ldp#Container', 'type')
  }
  if (fileMetadata.isBasicContainer) {
    addLink(res, 'http://www.w3.org/ns/ldp#BasicContainer', 'type')
  }
  if (fileMetadata.isDirectContainer) {
    addLink(res, 'http://www.w3.org/ns/ldp#DirectContainer', 'type')
  }
  if (fileMetadata.isStorage) {
    addLink(res, 'http://www.w3.org/ns/pim/space#Storage', 'type')
  }
}

async function linksHandler (req, res, next) {
  const ldp = req.app.locals.ldp
  let filename
  try {
    // Hack: createIfNotExists is set to true for PUT or PATCH requests
    // because the file might not exist yet at this point.
    // But it will be created afterwards.
    // This should be improved with the new server architecture.
    ({ path: filename } = await ldp.resourceMapper
      .mapUrlToFile({ url: req, createIfNotExists: req.method === 'PUT' || req.method === 'PATCH' }))
  } catch (e) {
    // Silently ignore errors here
    // Later handlers will error as well, but they will be able to given a more concrete error message (like 400 or 404)
    return next()
  }

  if (path.extname(filename) === ldp.suffixMeta) {
    debug.metadata('Trying to access metadata file as regular file.')

    return next(error(404, 'Trying to access metadata file as regular file'))
  }
  const fileMetadata = new metadata.Metadata()
  if (req.path.endsWith('/')) {
    // do not add storage header in serverUri
    if (req.path === '/') fileMetadata.isStorage = true
    fileMetadata.isContainer = true
    fileMetadata.isBasicContainer = true
  } else {
    fileMetadata.isResource = true
  }
  // Add LDP-required Accept-Post header for OPTIONS request to containers
  if (fileMetadata.isContainer && req.method === 'OPTIONS') {
    res.header('Accept-Post', '*/*')
  }
  // Add ACL and Meta Link in header
  addLink(res, utils.pathBasename(req.path) + ldp.suffixAcl, 'acl')
  addLink(res, utils.pathBasename(req.path) + ldp.suffixMeta, 'describedBy')
  // Add other Link headers
  addLinks(res, fileMetadata)
  next()
}

function parseMetadataFromHeader (linkHeader) {
  const fileMetadata = new metadata.Metadata()
  if (linkHeader === undefined) {
    return fileMetadata
  }
  const links = linkHeader.split(',')
  for (const linkIndex in links) {
    const link = links[linkIndex]
    const parsedLinks = li.parse(link)
    for (const rel in parsedLinks) {
      if (rel === 'type') {
        if (parsedLinks[rel] === 'http://www.w3.org/ns/ldp#Resource') {
          fileMetadata.isResource = true
        } else if (parsedLinks[rel] === 'http://www.w3.org/ns/ldp#RDFSource') {
          fileMetadata.isSourceResource = true
        } else if (parsedLinks[rel] === 'http://www.w3.org/ns/ldp#Container') {
          fileMetadata.isContainer = true
        } else if (parsedLinks[rel] === 'http://www.w3.org/ns/ldp#BasicContainer') {
          fileMetadata.isBasicContainer = true
        } else if (parsedLinks[rel] === 'http://www.w3.org/ns/ldp#DirectContainer') {
          fileMetadata.isDirectContainer = true
        } else if (parsedLinks[rel] === 'http://www.w3.org/ns/pim/space#Storage') {
          fileMetadata.isStorage = true
        }
      }
    }
  }
  return fileMetadata
}

// Adds a header that describes the user's permissions
async function addPermissions (req, res, next) {
  const { acl, session } = req
  if (!acl) return next()

  // Turn permissions for the public and the user into a header
  const ldp = req.app.locals.ldp
  const resource = ldp.resourceMapper.resolveUrl(req.hostname, req.path)
  let [publicPerms, userPerms] = await Promise.all([
    getPermissionsFor(acl, null, req),
    getPermissionsFor(acl, session.userId, req)
  ])
  if (resource.endsWith('.acl') && userPerms === '' && await ldp.isOwner(session.userId, req.hostname)) userPerms = 'control'
  debug.ACL(`Permissions on ${resource} for ${session.userId || '(none)'}: ${userPerms}`)
  debug.ACL(`Permissions on ${resource} for public: ${publicPerms}`)
  res.set('WAC-Allow', `user="${userPerms}",public="${publicPerms}"`)
  next()
}

// Gets the permissions string for the given user and resource
async function getPermissionsFor (acl, user, req) {
  const accesses = MODES.map(mode => acl.can(user, mode))
  const allowed = await Promise.all(accesses)
  return PERMISSIONS.filter((mode, i) => allowed[i]).join(' ')
}
