'use strict';
import SwaggerParser from 'swagger-parser';
import JsonPointer from './JsonPointer';
import {methods as swaggerMethods} from  './swagger-defs';

export default class SchemaManager {
  constructor() {
    if (SchemaManager.prototype._instance) {
      return SchemaManager.prototype._instance;
    }

    SchemaManager.prototype._instance = this;

    this._schema = {};
  }

  static instance() {
    return new SchemaManager();
  }

  load(url) {
    let promise = new Promise((resolve, reject) => {
      this._schema = {};

      SwaggerParser.bundle(url)
        .then(
          (schema) => {
            this._schema = schema;
            resolve(this._schema);
          },
          (err) => reject(err)
        );
    });

    return promise;
  }

  get schema() {
    // TODO: consider returning promise
    return this._schema;
  }

  byPointer(pointer) {
    let res = null;
    try {
      res = JsonPointer.get(this._schema, pointer);
    } catch(e)  {/*skip*/ }
    return res;
  }

  resolveRefs(obj) {
    Object.keys(obj).forEach(key => {
      if (obj[key].$ref) {
        obj[key] = this.byPointer(obj[key].$ref);
      }
    });
    return obj;
  }

  getMethodParams(methodPtr, resolveRefs) {
    /* inject JsonPointer into array elements */
    function injectPointers(array, root) {
      if (!array) return array;
      return array.map((element, idx) => {
        element._pointer = JsonPointer.join(root, idx);
        return element;
      });
    }

    //get path params
    let ptr = JsonPointer.dirName(methodPtr, 2) + '/parameters';
    let pathParams = this.byPointer(ptr);
    if (Array.isArray(pathParams)) {
      pathParams = injectPointers(pathParams, ptr);
    } else if (pathParams && pathParams.$ref) {
      pathParams = injectPointers(this.byPointer(pathParams.$ref), pathParams.$ref);
    } else {
      pathParams = [];
    }

    let methodParams = injectPointers(this.byPointer(methodPtr), methodPtr) || [];
    if (resolveRefs) {
      methodParams = this.resolveRefs(methodParams);
    }
    return methodParams.concat(pathParams);
  }

  /* returns ES6 Map */
  buildMenuTree() {
    let tag2MethodMapping = new Map();

    let definedTags = this._schema.tags;
    // add tags into map to preserve order
    for (let tag of definedTags) {
      tag2MethodMapping.set(tag.name, {description: tag.description});
    }

    let paths = this._schema.paths;
    for (let path of Object.keys(paths)) {
      let methods = Object.keys(paths[path]).filter((k) => swaggerMethods.has(k));
      for (let method of methods) {
        let methodInfo = paths[path][method];
        let tags = methodInfo.tags;

        //TODO: mb need to do something cleverer
        if (!tags || !tags.length) {
          tags = ['[Other]'];
        }
        let methodPointer = JsonPointer.compile(['paths', path, method]);
        let methodSummary = methodInfo.summary;
        for (let tag of tags) {
          let tagDetails = tag2MethodMapping.get(tag);
          if (!tagDetails) {
            tagDetails = {};
            tag2MethodMapping.set(tag, tagDetails);
          }
          if (!tagDetails.methods) tagDetails.methods = [];
          tagDetails.methods.push({pointer: methodPointer, summary: methodSummary});
        }
      }
    }
    return tag2MethodMapping;
  }

}

@@ -2336,16 +2336,26 @@
 .name, %7B
+%0A        '
 descript
@@ -2357,16 +2357,17 @@
 cription
+'
 : tag.de
@@ -2375,16 +2375,95 @@
 cription
+,%0A        'x-secondaryTag': tag%5B'x-secondaryTag'%5D,%0A        'methods': %5B%5D%0A      
 %7D);%0A    
@@ -3209,33 +3209,32 @@
 %7D%0A          if (
-!
 tagDetails.metho
@@ -3231,41 +3231,36 @@
 ails
-.methods) tagDetails.methods = %5B%5D
+%5B'x-secondaryTag'%5D) continue
 ;%0A  

Handle x-secondaryTag in menu generation