var fs = require('fs');
var oboe = require('oboe');
var curry = require('curry');
var xmlescape = require('xml-escape');

'use strict';

function extractVerticesEdges(path, vertexNames, edgeNames) {
  return new Promise(function(fulfill, reject) {
    var stream = fs.createReadStream(path);
    stream.on('error', function(e) {
      reject(e);
    });
    var result = {};
    var vertices = [];
    var edges = [];
    oboe(stream)
      .node({
        'records.*': function(record) {
          if (record['@type'] === 'd') {
            if (vertexNames.indexOf(record['@class']) !== -1) {
              vertices.push(record);
            }
            else if (edgeNames.indexOf(record['@class']) !== -1) {
              edges.push(record);
            }
          }
        }
      })
      .done(function() {
        result.edges = edges;
        result.vertices = vertices;
        fulfill(result);
      })
      .on('fail', function(e) {
        reject(e);
      });
  });
}

function convertFile(inFilePath, outFilePath, schemaInfo) {
  var vertexNames = schemaInfo.vertexNames;
  var edgeNames = schemaInfo.edgeNames;
  var writeFileToPath = writeFile(outFilePath);
  return new Promise(function(fulfill, reject) {
    extractVerticesEdges(inFilePath, vertexNames, edgeNames).
      then(convertNodesToGraphML).
      then(writeFileToPath).
      then(function(result) {
        fulfill(result);
    }).catch(function(err) {
      reject(err);
    });
  });
}

function convertNodesToGraphML(result) {
  return new Promise(function(fulfill) {
    var edges = result.edges.map(edgeToXml).map(indent4);
    if (edges.length > 0) {
      edges = '\n' + edges.join('\n');
    }
    else {
      edges = '';
    }
    var vertices = result.vertices.map(vertexToXml).map(indent4);
    var s = `<?xml version="1.0" encoding="UTF-8"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
  <graph id="G" edgedefault="directed">
${vertices.join('\n')}${edges}
  </graph>
</graphml>
`;
    fulfill(s);
  });
}

var writeFile = curry(function(path, graphMLString) {
  return new Promise(function(fulfill, reject) {
    fs.writeFile(path, graphMLString, function(err) {
      if (err) {
        reject(err);
      }
      else {
        fulfill(graphMLString);
      }
    });
  });
});

function vertexToXml(v) {
  var className = v['@class'];
  var value;
  // ugly hack, would fail with nested data structures (though I don't believe those are possible with orientdb)
  var data = [`<data key="labels">:${v['@class']}</data>`].
    concat(validVertexKeys(v).map(function(k) {
      value = v[k];
      if (typeof value === 'string') {
        value = xmlescape(value);
      }
      else if (Array.isArray(value)) {
        value = value.map(function(av) {
          if (typeof av === 'string') {
            return xmlescape(av);
          }
          return av;
        });
      }
      return `<data key="${k}">${value}</data>`;
  })).join('');
  return `<node id="${v['@rid']}" labels=":${className}">${data}</node>`;
}

function edgeToXml(e) {
  return `<edge id="${e['@rid']}" label="${e['@class']}" source="${e['out']}" target="${e['in']}"><data key="label">${e['@class']}</data></edge>`;
}

function validVertexKeys(v) {
  var blacklist = [
    '@class',
    '@version',
    '@rid',
    '@type',
    '@fieldTypes'
  ];
  var r = /(in|out)_[a-zA-Z]+/;
  return Object.keys(v).filter(function(k) {
    return blacklist.indexOf(k) === -1 && k.match(r) === null;
  });
}

var indent = curry(function(numSpaces, str) {
  return ' '.repeat(numSpaces) + str;
});

var indent4 = indent(4);

module.exports = {
  convertFile: convertFile,
  extractVerticesEdges: extractVerticesEdges
};

@@ -1296,29 +1296,24 @@
 then(convert
-Nodes
 ToGraphML).%0A
@@ -1481,13 +1481,8 @@
 vert
-Nodes
 ToGr

Rename conversion function to avoid confusion between vertex/node.