'use strict'
import isGetSetProp from 'is-get-set-prop'
import isJsType from 'is-js-type'
import isObjProp from 'is-obj-prop'
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 => {
  const 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)
  const rightType = o.right.type === 'BinaryExpression' ? binaryExpressionProduces(o.right) : getType(o.right)

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

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

  return 'Unknown'
}

/**
 * Returns the JS type and property name
 * @param {Object} node - node to examine
 * @return {Object} - jsType and propertyName
 */
const getJsTypeAndPropertyName = node => {
  let propertyName, jsType

  switch (node.object.type) {
    case 'NewExpression':
      jsType = node.object.callee.name
      break
    case 'Literal':
      jsType = getType(node.object)
      break
    case 'BinaryExpression':
      jsType = binaryExpressionProduces(node.object)
      break
    case 'Identifier':
      if (node.property.name === 'prototype' && node.parent.property) {
        jsType = node.object.name
        propertyName = node.parent.property.name
      } else {
        jsType = node.object.name
      }
      break
    default:
      jsType = node.object.type.replace('Expression', '')
  }

  propertyName = propertyName || node.property.name || node.property.value

  return {propertyName, jsType}
}

const isUnkownGettSetterOrJsTypeExpressed = (jsType, propertyName, usageType) => {
  const isExpression = usageType === 'ExpressionStatement' || usageType === 'MemberExpression'

  return isExpression && !isGetSetProp(jsType, propertyName) &&
    !isProtoProp(jsType, propertyName) && !isObjProp(jsType, propertyName)
}

/**
 * Determine if a jsType's usage of propertyName is valid
 * @param {String} jsType - the JS type to validate
 * @param {String} propertyName - the property name to validate usage of on jsType
 * @param {String} usageType - how propertyName is being used
 * @return {Boolean} - is the usage invalid?
 */
const isInvalid = (jsType, propertyName, usageType) => {
  if (typeof propertyName !== 'string' || typeof jsType !== 'string' || !isJsType(jsType)) {
    return false
  }

  const unknownGetterSetterOrjsTypeExpressed = isUnkownGettSetterOrJsTypeExpressed(jsType, propertyName, usageType)

  const isFunctionCall = usageType === 'CallExpression'
  const getterSetterCalledAsFunction = isFunctionCall && isGetSetProp(jsType, propertyName)

  const unknownjsTypeCalledAsFunction = isFunctionCall && !isProtoProp(jsType, propertyName) &&
    !isObjProp(jsType, propertyName)

  return unknownGetterSetterOrjsTypeExpressed || getterSetterCalledAsFunction || unknownjsTypeCalledAsFunction
}

module.exports = context => ({
  MemberExpression(node) {
    /* eslint complexity: [2, 9] */
    if (node.computed && node.property.type === 'Identifier') {
      /**
       * handles cases like {}[i][j]
       * not enough information to identify type of variable in computed properties
       * so ignore false positives by not performing any checks
       */

      return
    }

    const isArgToParent = node.parent.arguments && node.parent.arguments.indexOf(node) > -1
    const usageType = isArgToParent ? node.type : node.parent.type

    const {propertyName, jsType} = getJsTypeAndPropertyName(node)

    if (isInvalid(jsType, propertyName, usageType) && isInvalid('Function', propertyName, usageType)) {
      context.report(node, 'Avoid using extended native objects')
    }
  }
})

@@ -3355,22 +3355,82 @@
 s = 
-context =%3E (%7B%0A
+%7B%0A  meta: %7B%0A    type: 'problem'%0A  %7D,%0A  create(context) %7B%0A    return %7B%0A    
   Me
@@ -3448,24 +3448,28 @@
 ion(node) %7B%0A
+    
     /* eslin
@@ -3492,16 +3492,20 @@
 , 9%5D */%0A
+    
     if (
@@ -3566,16 +3566,20 @@
 %7B%0A      
+    
 /**%0A    
@@ -3581,16 +3581,20 @@
 %0A       
+ 
+   
 * handle
@@ -3615,16 +3615,20 @@
 %7D%5Bi%5D%5Bj%5D%0A
+    
        *
@@ -3710,16 +3710,20 @@
 %0A       
+  
+  
 * so ign
@@ -3782,12 +3782,20 @@
     
-*/%0A%0A
+    */%0A%0A    
     
@@ -3811,15 +3811,23 @@
     
-%7D%0A%0A
+    %7D%0A%0A   
     
+ 
 cons
@@ -3910,16 +3910,20 @@
 e) %3E -1%0A
+    
     cons
@@ -3982,16 +3982,20 @@
 t.type%0A%0A
+    
     cons
@@ -4053,16 +4053,20 @@
 (node)%0A%0A
+    
     if (
@@ -4167,16 +4167,20 @@
 %7B%0A      
+    
 context.
@@ -4231,16 +4231,34 @@
 jects')%0A
+        %7D%0A      %7D%0A
     %7D%0A  
@@ -4260,10 +4260,9 @@
  %7D%0A  %7D%0A%7D
-)
 %0A

use eslint's newer rule format along with `meta.type: "problem"`