/* eslint func-style: 0 */
/* eslint no-var: 0 */
/* eslint object-shorthand: 0 */
'use strict'
var isGetSetProp = require('is-get-set-prop')
  , isJsType = require('is-js-type')
  , isProtoProp = require('is-proto-prop')

/**
 * Return type of value of left or right
 * @param {Object} o - left or right of node.object
 * @return {String} - type of o
 */
function getType(o) {
  var type = typeof o.value

  if (o.regex) {
    return 'RegExp'
  }

  return type.charAt(0).toUpperCase() + type.slice(1)
}

/**
  * Returns type of binary expression result
  * @param {Object} o - node's object with a BinaryExpression type
  * @return {String} - type of value produced
  */
function binaryExpressionProduces(o) {
  var leftType = o.left.type === 'BinaryExpression' ? binaryExpressionProduces(o.left) : getType(o.left)
    , rightType = o.right.type === 'BinaryExpression' ? binaryExpressionProduces(o.right) : getType(o.right)

  if (leftType === 'String' || rightType === 'String' || leftType === rightType && leftType === 'RegExp') {
    return 'String'
  }

  if (leftType === rightType) {
    return leftType
  }

  return 'Unknown'
}

module.exports = function (context) {
  return {
    MemberExpression: function (node) {
      /* eslint complexity: [2, 13] */
      var isExpression, isFunctionCall, getterSetterCalledAsFunction
        , methodName, proto, type
        , unknownGetterSetterOrProtoExpressed, unknownProtoCalledAsFunction

      if (node.object.type === 'NewExpression') {
        proto = node.object.callee.name
      } else if (node.object.type === 'Literal') {
        proto = getType(node.object)
      } else if (node.object.type === 'BinaryExpression') {
        proto = binaryExpressionProduces(node.object)
      } else if (node.object.type === 'Identifier' && node.property.name === 'prototype' && node.parent.property) {
        proto = node.object.name
        methodName = node.parent.property.name
      } else {
        proto = node.object.type.replace('Expression', '')
      }

      methodName = methodName || node.property.name || node.property.value
      type = node.parent.type

      if (typeof proto !== 'string' || !isJsType(proto)) {
        return
      }

      isExpression = type === 'ExpressionStatement' || type === 'MemberExpression'
      unknownGetterSetterOrProtoExpressed = isExpression &&
        !isGetSetProp(proto, methodName) && !isProtoProp(proto, methodName)

      isFunctionCall = type === 'CallExpression'
      getterSetterCalledAsFunction = isFunctionCall && isGetSetProp(proto, methodName)
      unknownProtoCalledAsFunction = isFunctionCall && !isProtoProp(proto, methodName)

      if (unknownGetterSetterOrProtoExpressed || getterSetterCalledAsFunction || unknownProtoCalledAsFunction) {
        context.report(node, 'Avoid using extended native objects')
      }
    }
  }
}

/* eslint func-style: 0 */
/* eslint no-var: 0 */
/* eslint object-shorthand: 0 */
'use strict'
import isGetSetProp from 'is-get-set-prop'
import isJsType from 'is-js-type'
import isProtoProp from 'is-proto-prop'

/**
 * Return type of value of left or right
 * @param {Object} o - left or right of node.object
 * @return {String} - type of o
 */
const getType = o => {
  var type = typeof o.value

  if (o.regex) {
    return 'RegExp'
  }

  return type.charAt(0).toUpperCase() + type.slice(1)
}

/**
  * Returns type of binary expression result
  * @param {Object} o - node's object with a BinaryExpression type
  * @return {String} - type of value produced
  */
const binaryExpressionProduces = o => {
  const leftType = o.left.type === 'BinaryExpression' ? binaryExpressionProduces(o.left) : getType(o.left)
    , rightType = o.right.type === 'BinaryExpression' ? binaryExpressionProduces(o.right) : getType(o.right)

  if (leftType === 'String' || rightType === 'String' || leftType === rightType && leftType === 'RegExp') {
    return 'String'
  }

  if (leftType === rightType) {
    return leftType
  }

  return 'Unknown'
}

module.exports = context => ({
  MemberExpression(node) {
    /* eslint complexity: [2, 13] */
    let methodName, proto

    if (node.object.type === 'NewExpression') {
      proto = node.object.callee.name
    } else if (node.object.type === 'Literal') {
      proto = getType(node.object)
    } else if (node.object.type === 'BinaryExpression') {
      proto = binaryExpressionProduces(node.object)
    } else if (node.object.type === 'Identifier' && node.property.name === 'prototype' && node.parent.property) {
      proto = node.object.name
      methodName = node.parent.property.name
    } else {
      proto = node.object.type.replace('Expression', '')
    }

    methodName = methodName || node.property.name || node.property.value
    const type = node.parent.type

    if (typeof proto !== 'string' || !isJsType(proto)) {
      return
    }

    const isExpression = type === 'ExpressionStatement' || type === 'MemberExpression'
    const unknownGetterSetterOrProtoExpressed = isExpression &&
      !isGetSetProp(proto, methodName) && !isProtoProp(proto, methodName)

    const isFunctionCall = type === 'CallExpression'
    const getterSetterCalledAsFunction = isFunctionCall && isGetSetProp(proto, methodName)
    const unknownProtoCalledAsFunction = isFunctionCall && !isProtoProp(proto, methodName)

    if (unknownGetterSetterOrProtoExpressed || getterSetterCalledAsFunction || unknownProtoCalledAsFunction) {
      context.report(node, 'Avoid using extended native objects')
    }
  }
})

use ES2015