'use strict';
var isPlainObject = require('lodash/isPlainObject');
var isFunction = require('lodash/isFunction');
var _find = require('lodash/find');
var _extend = require('lodash/extend');
var _transform = require('lodash/transform');
var Inflector = require('./inflector');

module.exports = function (jsonapi, data, opts) {
  var alreadyIncluded = [];

  function isComplexType(obj) {
    return Array.isArray(obj) || isPlainObject(obj);
  }

  function getValueForRelationship(relationshipData, included) {
    if (opts && relationshipData && opts[relationshipData.type]) {
      var valueForRelationshipFct = opts[relationshipData.type]
        .valueForRelationship;

      return valueForRelationshipFct(relationshipData, included);
    } else {
      return included;
    }
  }

  function findIncluded(relationshipData, ancestry) {
    return new Promise(function (resolve) {
      if (!jsonapi.included || !relationshipData) { resolve(null); }

      var included = _find(jsonapi.included, {
        id: relationshipData.id,
        type: relationshipData.type
      });

      if (included) {
        // To prevent circular references, check if the record type
        // has already been processed in this thread
        if (ancestry.indexOf(included.type) > -1) {
          return Promise
            .all([extractAttributes(included)])
            .then(function (results) {
              var attributes = results[0];
              var relationships = results[1];
              resolve(_extend(attributes, relationships));
            });
        }

        return Promise
          .all([extractAttributes(included), extractRelationships(included, `${ancestry}:${included.type}${included.id}`)])
          .then(function (results) {
            var attributes = results[0];
            var relationships = results[1];
            resolve(_extend(attributes, relationships));
          });
      } else {
        return resolve(null);
      }
    });
  }

  function keyForAttribute(attribute) {
    if (isPlainObject(attribute)) {
      return _transform(attribute, function (result, value, key) {
        if (isComplexType(value)) {
          result[keyForAttribute(key)] = keyForAttribute(value);
        } else {
          result[keyForAttribute(key)] = value;
        }
      });
    } else if (Array.isArray(attribute)) {
      return attribute.map(function (attr) {
        if (isComplexType(attr)) {
          return keyForAttribute(attr);
        } else {
          return attr;
        }
      });
    } else {
      if (isFunction(opts.keyForAttribute)) {
        return opts.keyForAttribute(attribute);
      } else {
        return Inflector.caserize(attribute, opts);
      }
    }
  }

  function extractAttributes(from) {
    var dest = keyForAttribute(from.attributes || {});
    if ('id' in from) { dest[opts.id || 'id'] = from.id; }

    if (opts.typeAsAttribute) {
      if ('type' in from) { dest.type = from.type; }
    }
    if ('meta' in from) { dest.meta = keyForAttribute(from.meta || {}) }

    return dest;
  }

  function extractRelationships(from, ancestry) {
    if (!from.relationships) { return; }

    var dest = {};

    return Promise
      .all(Object.keys(from.relationships).map(function (key) {
        var relationship = from.relationships[key];

        if (relationship.data === null) {
          dest[keyForAttribute(key)] = null;
        } else if (Array.isArray(relationship.data)) {
          return Promise
            .all(relationship.data.map(function (relationshipData) {
              return extractIncludes(relationshipData, ancestry);
            }))
            .then(function (includes) {
              if (includes) { dest[keyForAttribute(key)] = includes; }
            });
        } else {
          return extractIncludes(relationship.data, ancestry)
            .then(function (includes) {
              if (includes) { dest[keyForAttribute(key)] = includes; }
            });
        }
      }))
      .then(function() {
        return dest;
      });
  }

  function extractIncludes(relationshipData, ancestry) {
    return findIncluded(relationshipData, ancestry)
      .then(function (included) {
        var valueForRelationship = getValueForRelationship(relationshipData,
          included);

        if (valueForRelationship && isFunction(valueForRelationship.then)) {
          return valueForRelationship.then(function (value) {
            return value;
          });
        } else {
          return valueForRelationship;
        }
      });
  }

  this.perform = function () {
    return Promise
      .all([extractAttributes(data), extractRelationships(data, `${data.type}${data.id}`)])
      .then(function (results) {
        var attributes = results[0];
        var relationships = results[1];
        var record = _extend(attributes, relationships);

        // Links
        if (jsonapi.links) {
          record.links = jsonapi.links;
        }

        // If option is present, transform record
        if (opts && opts.transform) {
          record = opts.transform(record);
        }

        return record;
      });
  };
};

@@ -1660,19 +1660,16 @@
 ed, 
-%60$%7B
 ancestry
 %7D:$%7B
@@ -1668,12 +1668,17 @@
 stry
-%7D:$%7B
+ + ':' + 
 incl
@@ -1686,19 +1686,19 @@
 ded.type
-%7D$%7B
+ + 
 included
@@ -1700,18 +1700,16 @@
 luded.id
-%7D%60
 )%5D)%0A    
@@ -4647,11 +4647,8 @@
 ta, 
-%60$%7B
 data
@@ -4656,20 +4656,18 @@
 type
-%7D$%7B
+ + 
 data.id
-%7D%60
 )%5D)%0A

revert template literals to concatenation