'use strict'

const { operations } = require('@ldapjs/protocol')
const { getControl } = require('@ldapjs/controls')

const messageClasses = {
  AbandonRequest: require('./messages/abandon-request'),
  AddRequest: require('./messages/add-request'),
  BindRequest: require('./messages/bind-request'),
  CompareRequest: require('./messages/compare-request'),
  DeleteRequest: require('./messages/delete-request'),
  ExtensionRequest: require('./messages/extension-request'),
  ModifyRequest: require('./messages/modify-request'),
  ModifyDnRequest: require('./messages/modifydn-request'),
  SearchRequest: require('./messages/search-request'),
  UnbindRequest: require('./messages/unbind-request'),

  AbandonResponse: require('./messages/abandon-response'),
  AddResponse: require('./messages/add-response'),
  BindResponse: require('./messages/bind-response'),
  CompareResponse: require('./messages/compare-response'),
  DeleteResponse: require('./messages/delete-response'),
  ExtensionResponse: require('./messages/extension-response'),
  ModifyResponse: require('./messages/modify-response'),
  ModifyDnResponse: require('./messages/modifydn-response'),

  // Search result messages.
  SearchResultEntry: require('./messages/search-result-entry'),
  SearchResultReference: require('./messages/search-result-reference'),
  SearchResultDone: require('./messages/search-result-done'),

  // Miscellaneous messages.
  IntermediateResponse: require('./messages/intermediate-response')
}

/**
 * Utility function that inspects a BER object and parses it into an instance
 * of a specific LDAP message.
 *
 * @param {import('@ldapjs/asn1').BerReader} ber An object that represents a
 * full LDAP Message sequence as described in
 * https://www.rfc-editor.org/rfc/rfc4511.html#section-4.1.1.
 *
 * @returns {LdapMessage} Some specific instance of the base LDAP Message
 * type.
 *
 * @throws When the input data is malformed.
 */
module.exports = function parseToMessage (ber) {
  const inputType = Object.prototype.toString.apply(ber)
  if (inputType !== '[object BerReader]') {
    throw TypeError(`Expected BerReader but got ${inputType}.`)
  }

  ber.readSequence()

  const messageId = ber.readInt()
  const messageType = identifyType(ber)
  const MessageClass = messageClasses[messageType]
  const pojoMessage = MessageClass.parseToPojo(ber)
  const message = new MessageClass({
    messageId,
    ...pojoMessage
  })

  // Look for controls
  if (ber.peek() === 0xa0) {
    ber.readSequence()
    const end = ber.offset + ber.length
    while (ber.offset < end) {
      const c = getControl(ber)
      /* istanbul ignore else */
      if (c) {
        message.addControl(c)
      }
    }
  }

  return message
}

/**
 * Determines the type of LDAP message the BER represents, e.g. a "Bind Request"
 * message.
 *
 * @param {BerReader} ber
 *
 * @returns {string}
 */
function identifyType (ber) {
  let result
  switch (ber.peek()) {
    case operations.LDAP_REQ_ABANDON: {
      result = 'AbandonRequest'
      break
    }

    case 0x00: {
      result = 'AbandonResponse'
      break
    }

    case operations.LDAP_REQ_ADD: {
      result = 'AddRequest'
      break
    }

    case operations.LDAP_RES_ADD: {
      result = 'AddResponse'
      break
    }

    case operations.LDAP_REQ_BIND: {
      result = 'BindRequest'
      break
    }

    case operations.LDAP_RES_BIND: {
      result = 'BindResponse'
      break
    }

    case operations.LDAP_REQ_COMPARE: {
      result = 'CompareRequest'
      break
    }

    case operations.LDAP_RES_COMPARE: {
      result = 'CompareResponse'
      break
    }

    case operations.LDAP_REQ_DELETE: {
      result = 'DeleteRequest'
      break
    }

    case operations.LDAP_RES_DELETE: {
      result = 'DeleteResponse'
      break
    }

    case operations.LDAP_REQ_EXTENSION: {
      result = 'ExtensionRequest'
      break
    }

    case operations.LDAP_RES_EXTENSION: {
      result = 'ExtensionResponse'
      break
    }

    case operations.LDAP_REQ_MODIFY: {
      result = 'ModifyRequest'
      break
    }

    case operations.LDAP_RES_MODIFY: {
      result = 'ModifyResponse'
      break
    }

    case operations.LDAP_REQ_MODRDN: {
      result = 'ModifyDnRequest'
      break
    }

    case operations.LDAP_RES_MODRDN: {
      result = 'ModifyDnResponse'
      break
    }

    case operations.LDAP_REQ_SEARCH: {
      result = 'SearchRequest'
      break
    }

    case operations.LDAP_RES_SEARCH_ENTRY: {
      result = 'SearchResultEntry'
      break
    }

    case operations.LDAP_RES_SEARCH_REF: {
      result = 'SearchResultReference'
      break
    }

    case operations.LDAP_RES_SEARCH_DONE: {
      result = 'SearchResultDone'
      break
    }

    case operations.LDAP_REQ_UNBIND: {
      result = 'UnbindRequest'
      break
    }

    case operations.LDAP_RES_INTERMEDIATE: {
      result = 'IntermediateResponse'
      break
    }
  }

  return result
}