'use strict'

function isArguments (obj) {
  return Object.prototype.toString.call(obj) === '[object Arguments]'
}

module.exports = match

function match (obj, pattern) {
  return match_(obj, pattern, [], [])
}

/* istanbul ignore next */
var log = (/\btmatch\b/.test(process.env.NODE_DEBUG || '')) ?
  console.error : function () {}

function match_ (obj, pattern, ca, cb) {
  log('TMATCH', typeof obj, pattern)
  if (obj == pattern) {
    log('TMATCH same object or simple value, or problem')
    // if one is object, and the other isn't, then this is bogus
    if (obj === null || pattern === null) {
      return true

    } else if (typeof obj === 'object' && typeof pattern === 'object') {
      return true

    } else if (typeof obj === 'object' && typeof pattern !== 'object') {
      return false

    } else if (typeof obj !== 'object' && typeof pattern === 'object') {
      return false

    } else {
      return true
    }

  } else if (obj === null || pattern === null) {
    log('TMATCH null test, already failed ==')
    return false

  } else if (typeof obj === 'string' && pattern instanceof RegExp) {
    log('TMATCH string~=regexp test')
    return pattern.test(obj)

  } else if (typeof obj === 'string' && typeof pattern === 'string' && pattern) {
    log('TMATCH string~=string test')
    return obj.indexOf(pattern) !== -1

  } else if (obj instanceof Date && pattern instanceof Date) {
    log('TMATCH date test')
    return obj.getTime() === pattern.getTime()

  } else if (obj instanceof Date && typeof pattern === 'string') {
    log('TMATCH date~=string test')
    return obj.getTime() === new Date(pattern).getTime()

  } else if (isArguments(obj) || isArguments(pattern)) {
    log('TMATCH arguments test')
    var slice = Array.prototype.slice
    return match_(slice.call(obj), slice.call(pattern), ca, cb)

  } else if (pattern === Buffer) {
    log('TMATCH Buffer ctor')
    return Buffer.isBuffer(obj)

  } else if (pattern === Function) {
    log('TMATCH Function ctor')
    return typeof obj === 'function'

  } else if (pattern === Number) {
    log('TMATCH Number ctor (finite, not NaN)')
    return typeof obj === 'number' && obj === obj && isFinite(obj)

  } else if (pattern !== pattern) {
    log('TMATCH NaN')
    return obj !== obj

  } else if (pattern === String) {
    log('TMATCH String ctor')
    return typeof obj === 'string'

  } else if (pattern === Boolean) {
    log('TMATCH Boolean ctor')
    return typeof obj === 'boolean'

  } else if (pattern === Array) {
    log('TMATCH Array ctor', pattern, Array.isArray(obj))
    return Array.isArray(obj)

  } else if (typeof pattern === 'function' && typeof obj === 'object') {
    log('TMATCH object~=function')
    return obj instanceof pattern

  } else if (typeof obj !== 'object' || typeof pattern !== 'object') {
    log('TMATCH obj is not object, pattern is not object, false')
    return false

  } else if (obj instanceof RegExp && pattern instanceof RegExp) {
    log('TMATCH regexp~=regexp test')
    return obj.source === pattern.source &&
      obj.global === pattern.global &&
      obj.multiline === pattern.multiline &&
      obj.lastIndex === pattern.lastIndex &&
      obj.ignoreCase === pattern.ignoreCase

  } else if (Buffer.isBuffer(obj) && Buffer.isBuffer(pattern)) {
    log('TMATCH buffer test')
    if (obj.equals) {
      return obj.equals(pattern)
    } else {
      if (obj.length !== pattern.length) return false

      for (var j = 0; j < obj.length; j++) if (obj[j] != pattern[j]) return false

      return true
    }

  } else {
    // both are objects.  interesting case!
    log('TMATCH object~=object test')
    var kobj = Object.keys(obj)
    var kpat = Object.keys(pattern)
    log('  TMATCH patternkeys=%j objkeys=%j', kpat, kobj)

    // don't bother with stack acrobatics if there's nothing there
    if (kobj.length === 0 && kpat.length === 0) return true

    // if we've seen this exact pattern and object already, then
    // it means that pattern and obj have matching cyclicalness
    // however, non-cyclical patterns can match cyclical objects
    log('  TMATCH check seen objects...')
    var cal = ca.length
    while (cal--) if (ca[cal] === obj && cb[cal] === pattern) return true
    ca.push(obj); cb.push(pattern)
    log('  TMATCH not seen previously')

    var key
    for (var l = kpat.length - 1; l >= 0; l--) {
      key = kpat[l]
      log('  TMATCH test obj[%j]', key, obj[key], pattern[key])
      if (!match_(obj[key], pattern[key], ca, cb)) return false
    }

    ca.pop()
    cb.pop()

    log('  TMATCH object pass')
    return true
  }

  /* istanbul ignore next */
  throw new Error('impossible to reach this point')
}
