text: /**
   * @module Databases/TAbstractResponder
   * @desc Export the TAbstractResponder abstract class.
   * @exports TAbstractResponder
   *
   * @author [Tristan Valcke]{@link https://github.com/Itee}
   * @license [BSD-3-Clause]{@link https://opensource.org/licenses/BSD-3-Clause}
   */
  
  import {
      isArray,
      isDefined,
      isFunction,
      isObject,
      isString
  }                       from 'itee-validators'
  import { TAbstractObject } from 'itee-core'
  import { UnknownError } from '../messages/http/UnknownError'
  
  /**
   * @class
   * @classdesc The TAbstractResponder is the base class for all derived database controller that require to send a response to client.
   * It allow to send preformatted response in function of database query result.
   */
  class TAbstractResponder extends TAbstractObject {
  
      constructor ( parameters = {} ) {
          const _parameters = {
              ...{},
              ...parameters
          }
  
          super(_parameters)
      }
  
  
      /**
       * Normalize errors that can be in different format like single string, object, array of string, or array of object.
       *
       * @example <caption>Normalized error are simple literal object like:</caption>
       * {
       *     name: 'TypeError',
       *     message: 'the error message'
       * }
       *
       * @param {String|Object|Array.<String>|Array.<Object>} error - The error object to normalize
       * @returns {Array.<Object>}
       * @private
       */
      static _formatErrors ( errors = [] ) {
  
          const _errors = ( isArray( errors ) ) ? errors : [ errors ]
  
          let formattedErrors = []
  
          for ( let i = 0, numberOfErrors = _errors.length ; i < numberOfErrors ; i++ ) {
              formattedErrors.push( TAbstractResponder._formatError( _errors[ i ] ) )
          }
  
          return formattedErrors
  
      }
  
      /**
       * Normalize error that can be in different format like single string, object, array of string, or array of object.
       *
       * @example <caption>Normalized error are simple literal object like:</caption>
       * {
       *     name: 'TypeError',
       *     message: 'the error message'
       * }
       *
       * @param {String|Object|Error} error - The error object to normalize
       * @returns {AbstractHTTPError}
       * @private
       */
      static _formatError ( error ) {
  
          let formattedError
  
          if ( error instanceof Error ) {
  
              formattedError = error
  
          } else if ( isString( error ) ) {
  
              formattedError = new UnknownError( error )
  
          } else if ( isObject( error ) ) {
  
              const name    = error.name
              const message = error.message || 'Empty message...'
  
              formattedError = new UnknownError( message )
              if ( name ) {
                  formattedError.name = name
              }
  
          } else {
  
              formattedError = new UnknownError( error.toString() )
  
          }
  
          return formattedError
  
      }
  
      /**
       * In case database call return nothing consider that is a not found.
       * If response parameter is a function consider this is a returnNotFound callback function to call,
       * else check if server response headers aren't send yet, and return response with status 204
       *
       * @param response - The server response or returnNotFound callback
       * @returns {*} callback call or response with status 204
       */
      static returnNotFound ( response ) {
  
          if ( isFunction( response ) ) { return response() }
          if ( response.headersSent ) { return }
  
          response.status( 204 ).end()
  
      }
  
      /**
       * In case database call return an error.
       * If response parameter is a function consider this is a returnError callback function to call,
       * else check if server response headers aren't send yet, log and flush stack trace (if allowed) and return response with status 500 and
       * stringified error as content
       *
       * @param error - A server/database error
       * @param response - The server response or returnError callback
       * @returns {*} callback call or response with status 500 and associated error
       */
      static returnError ( error, response ) {
  
          if ( isFunction( response ) ) { return response( error, null ) }
          if ( response.headersSent ) { return }
  
          const formatedError = TAbstractResponder._formatError( error )
  
          response.format( {
  
              'application/json': () => {
                  response.status( formatedError.statusCode ).json( formatedError )
              },
  
              'default': () => {
                  response.status( 406 ).send( 'Not Acceptable' )
              }
  
          } )
  
      }
  
      /**
       * In case database call return some data.
       * If response parameter is a function consider this is a returnData callback function to call,
       * else check if server response headers aren't send yet, and return response with status 200 and
       * stringified data as content
       *
       * @param data - The server/database data
       * @param response - The server response or returnData callback
       * @returns {*} callback call or response with status 200 and associated data
       */
      static returnData ( data, response ) {
  
          if ( isFunction( response ) ) { return response( null, data ) }
          if ( response.headersSent ) { return }
  
          const _data = isArray( data ) ? data : [ data ]
  
          response.format( {
  
              'application/json': () => {
                  response.status( 200 ).json( _data )
              },
  
              'default': () => {
                  response.status( 406 ).send( 'Not Acceptable' )
              }
  
          } )
  
      }
  
      /**
       * In case database call return some data AND error.
       * If response parameter is a function consider this is a returnErrorAndData callback function to call,
       * else check if server response headers aren't send yet, log and flush stack trace (if allowed) and
       * return response with status 406 with stringified data and error in a literal object as content
       *
       * @param error - A server/database error
       * @param data - The server/database data
       * @param response - The server response or returnErrorAndData callback
       * @returns {*} callback call or response with status 406, associated error and data
       */
      static returnErrorAndData ( error, data, response ) {
  
          if ( isFunction( response ) ) { return response( error, data ) }
          if ( response.headersSent ) { return }
  
          const result = {
              errors: TAbstractResponder._formatErrors( error ),
              datas:  data
          }
  
          response.format( {
  
              'application/json': () => {
                  response.status( 416 ).json( result )
              },
  
              'default': () => {
                  response.status( 416 ).send( 'Range Not Satisfiable' )
              }
  
          } )
  
      }
  
      static return ( response, callbacks = {} ) {
  
          const _callbacks = Object.assign( {
                  immediate:                null,
                  beforeAll:                null,
                  beforeReturnErrorAndData: null,
                  afterReturnErrorAndData:  null,
                  beforeReturnError:        null,
                  afterReturnError:         null,
                  beforeReturnData:         null,
                  afterReturnData:          null,
                  beforeReturnNotFound:     null,
                  afterReturnNotFound:      null,
                  afterAll:                 null
              },
              callbacks,
              {
                  returnErrorAndData: TAbstractResponder.returnErrorAndData.bind( this ),
                  returnError:        TAbstractResponder.returnError.bind( this ),
                  returnData:         TAbstractResponder.returnData.bind( this ),
                  returnNotFound:     TAbstractResponder.returnNotFound.bind( this )
              } )
  
          /**
           * The callback that will be used for parse database response
           */
          function dispatchResult ( error = null, data = null ) {
  
              const haveData  = isDefined( data )
              const haveError = isDefined( error )
  
              if ( _callbacks.beforeAll ) { _callbacks.beforeAll() }
  
              if ( haveData && haveError ) {
  
                  if ( _callbacks.beforeReturnErrorAndData ) { _callbacks.beforeReturnErrorAndData( error, data ) }
                  _callbacks.returnErrorAndData( error, data, response )
                  if ( _callbacks.afterReturnErrorAndData ) { _callbacks.afterReturnErrorAndData( error, data ) }
  
              } else if ( haveData && !haveError ) {
  
                  if ( _callbacks.beforeReturnData ) { _callbacks.beforeReturnData( data ) }
                  _callbacks.returnData( data, response )
                  if ( _callbacks.afterReturnData ) { _callbacks.afterReturnData( data ) }
  
              } else if ( !haveData && haveError ) {
  
                  if ( _callbacks.beforeReturnError ) { _callbacks.beforeReturnError( error ) }
                  _callbacks.returnError( error, response )
                  if ( _callbacks.afterReturnError ) { _callbacks.afterReturnError( error ) }
  
              } else if ( !haveData && !haveError ) {
  
                  if ( _callbacks.beforeReturnNotFound ) { _callbacks.beforeReturnNotFound() }
                  _callbacks.returnNotFound( response )
                  if ( _callbacks.afterReturnNotFound ) { _callbacks.afterReturnNotFound() }
  
              }
  
              if ( _callbacks.afterAll ) { _callbacks.afterAll() }
  
          }
  
          // An immediate callback hook ( for timing for example )
          if ( _callbacks.immediate ) { _callbacks.immediate() }
  
          return dispatchResult
  
      }
  
  }
  
  export { TAbstractResponder }
  