import Serializer from 'ember-data/serializer';
import { isEmberArray } from 'ember-array/utils';
import get from 'ember-metal/get';
import Ember from 'ember';
const { String: EmberString } = Ember;

export default Serializer.extend({
  isNewSerializerAPI: true,

  keyForAttribute (attr) {
    return EmberString.underscore(attr);
  },

  normalizeResponse (store, modelClass, payload, id, requestType) {
    switch (requestType) {
      case 'findRecord':
        return this.normalizeFindRecordResponse(...arguments);
      case 'queryRecord':
        return this.normalizeQueryRecordResponse(...arguments);
      case 'findAll':
        return this.normalizeFindAllResponse(...arguments);
      case 'findMany':
        return this.normalizeFindManyResponse(...arguments);
      case 'query':
        return this.normalizeQueryResponse(...arguments);
      case 'createRecord':
        return this.normalizeCreateRecordResponse(...arguments);
      case 'deleteRecord':
        return this.normalizeDeleteRecordResponse(...arguments);
      case 'updateRecord':
        return this.normalizeUpdateRecordResponse(...arguments);
      default:
        return this.normalizeArrayResponse(...arguments);
    }
  },

  normalizeArrayResponse (/* store, modelClass, payload, id, requestType */) {
    return this._normalizeResponse(...arguments, false);
  },

  normalizeSingleResponse (/* store, modelClass, payload, id, requestType */) {
    return this._normalizeResponse(...arguments, true);
  },

  normalizeFindRecordResponse(/* store, modelClass, payload, id, requestType */) {
    return this.normalizeSingleResponse(...arguments);
  },

  normalizeQueryRecordResponse(/* store, modelClass, payload, id, requestType */) {
    return this.normalizeSingleResponse(...arguments);
  },

  normalizeFindAllResponse(/* store, modelClass, payload, id, requestType */) {
    return this.normalizeArrayResponse(...arguments);
  },

  normalizeFindManyResponse(/* store, modelClass, payload, id, requestType */) {
    return this.normalizeArrayResponse(...arguments);
  },

  normalizeQueryResponse(/* store, modelClass, payload, id, requestType */) {
    return this.normalizeArrayResponse(...arguments);
  },

  normalizeCreateRecordResponse(/* store, modelClass, payload, id, requestType */) {
    return this.normalizeSingleResponse(...arguments);
  },

  normalizeDeleteRecordResponse(/* store, modelClass, payload, id, requestType */) {
    return this.normalizeSingleResponse(...arguments);
  },

  normalizeUpdateRecordResponse(/* store, modelClass, payload, id, requestType */) {
    return this.normalizeSingleResponse(...arguments);
  },

  /*
     Normalizes the deeply nested JSON into a flat JSONAPI doucment.

     Walks the tree and uses relationship information as provided by the models themselves to parse relationships.
     Normalize starts by looking at the object returned by getRootNode()
      If its an array it assumes that this is a multi-model document and proceeds to walk each one.
      If its a single object it assumes that it defines this model type `modelClass`.

    On each model store.normalize is called which returns a micro JSONAPI document (with includes of submodels).
    Included models are then joined into one array, deduplicated, and returned as a single JSONAPI document.

    When extending this Serializer likely you'll only have to override:
      keyForAttribute: if you want to rewrite attribute names, or change the default underscored verison
      getRootNode: for telling the parser which node to start in the returned hash.
      readMeta: for populating the meta field based on other attributes in the payload.
  */
  _normalizeResponse (store, modelClass, payload , id , requestType, isSingleResponse) {
    let modelName = get(modelClass, 'modelName');

    let data = [];
    let included = [];

    let topLevel = this.getRootNode(payload);
    if (isEmberArray(topLevel)) {
      topLevel.forEach((modelData) => {
        let subDocument = this.normalize(modelClass, modelData);
        if (subDocument) {
          data.push(...subDocument.data);
          if (subDocument.included) {
            included.push(...subDocument.included);
          }
        }
      });
    } else if(topLevel) {
      return this.normalize(modelClass, topLevel, isSingleResponse);
    } else {
      console.error(`normalizePayload for ${modelName} passed undefined payload`);
      return {
        data,
        included,
        meta: this.readMeta(payload)
      };
    }

    // Deduplicate models to be unique for each (model,id) pair.
    let modelsByType = {};
    included = included.filter((model) => {
      let type = model.type;
      let modelsOfMyType = modelsByType[type] || {};
      if (modelsOfMyType.hasOwnProperty(model.id)) {
        return false;
      }
      modelsOfMyType[model.id] = true;
      modelsByType[type] = modelsOfMyType;
      return true;
    });

    if (isSingleResponse) {
      data = data[0];
    }

    return {
      data,
      included,
      meta: this.readMeta(payload)
    };
  },

  /* Parses a deeply nested hash as returned by the audrey API into a micro
     JSONAPI doucment that defines the models as well as the included (embedded)
     submodels.

     First it reads all attributes as defined by the Model and tries to populate
     based on presence in the provided hash.

     Second it reads all relationships, parses them into relationships and then
     recursively calls store.normalize on the sub models to include them in the
     included section of the JSONAPI method.
  */
  normalize (modelClass, hash, isSingleResponse) {
    let modelName = get(modelClass, 'modelName');

    // Parse all attributes as they're defined on the hash.
    let attributes = {};
    modelClass.eachAttribute((attribute) => {
      let attrName = this.keyForAttribute(attribute);
      if (hash.hasOwnProperty(attrName) && hash[attrName] !== null) {
        attributes[attribute] = hash[attrName];
      }
    });

    // Parse all relationships and populate this models relationships and
    // include submodels in the "included" section of the JSONAPI document
    let relationships = {};
    let included = [];
    modelClass.eachRelationship((name, descriptor) => {
      let relationship = {};
      let subModelPayload = hash[this.keyForAttribute(name)];

      if (!subModelPayload) { return; }

      if (descriptor.kind === "hasMany") {
        if (isEmberArray(subModelPayload)) {
          relationship.data = subModelPayload.map(subModelData => {
            let normalizedSubModel = this.store.normalize(descriptor.type, subModelData);
            let subSerializer = this.store.serializerFor(descriptor.type);
            included.push(...normalizedSubModel.data);
            if (normalizedSubModel.included) {
              included.push(...normalizedSubModel.included);
            }
            return {
              id: subModelData[subSerializer.keyForAttribute('id')],
              type: descriptor.type
            };
          });
        }
      } else if (descriptor.kind === "belongsTo") {
        let subSerializer = this.store.serializerFor(descriptor.type);
        relationship.data = {
          id: subModelPayload[subSerializer.keyForAttribute('id')],
          type: descriptor.type
        };
        let normalizedSubModel = this.store.normalize(descriptor.type, subModelPayload);
        included.push(...normalizedSubModel.data);
        if (normalizedSubModel.included) {
          included.push(...normalizedSubModel.included);
        }
      } else {
        console.warn(`normalize for ${modelName}: Unhandled relationship type ${descriptor.kind} for relationship key ${name}`);
      }

      relationships[name] = relationship;
    });

    let data = [];

    data.push({
      id: hash[this.keyForAttribute('id')],
      type: modelName,
      attributes,
      relationships
    });

    if (isSingleResponse) {
      data = data[0];
    }

    return {
      data,
      included
    };
  },

  readMeta () {
    return {};
  },

  getRootNode (payload) {
    return payload;
  }

});
