# lcov.js

    /**
     * @module lib/lcov
     */

    const fs = require('fs');

    function walkFile(str, cb) {
      let data = [];
      let item;

      ['end_of_record'].concat(str.split('\n')).forEach((line) => {
        const allparts = line.trim().split(':');
        const parts = [allparts.shift(), allparts.join(':')];
        let lines, fn;

        switch (parts[0].toUpperCase()) {
          case 'TN':
            item.title = parts[1].trim();
            break;
          case 'SF':
            item.file = parts.slice(1).join(':').trim();
            break;
          case 'FNF':
            item.functions.found = Number(parts[1].trim());
            break;
          case 'FNH':
            item.functions.hit = Number(parts[1].trim());
            break;
          case 'LF':
            item.lines.found = Number(parts[1].trim());
            break;
          case 'LH':
            item.lines.hit = Number(parts[1].trim());
            break;
          case 'DA':
            lines = parts[1].split(',');
            item.lines.details.push({
              line: Number(lines[0]),
              hit: Number(lines[1])
            });
            break;
          case 'FN':
            fn = parts[1].split(',');
            item.functions.details.push({
              name: fn[1],
              line: Number(fn[0])
            });
            break;
          case 'FNDA':
            fn = parts[1].split(',');
            item.functions.details.some((i, k) => {
              if (i.name === fn[1] && i.hit === undefined) {
                item.functions.details[k].hit = Number(fn[0]);
                return true;
              }
            });
            break;
          case 'BRDA':
            fn = parts[1].split(',');
            item.branches.details.push({
              line: Number(fn[0]),
              block: Number(fn[1]),
              branch: Number(fn[2]),
              taken: ((fn[3] === '-') ? 0 : Number(fn[3]))
            });
            break;
          case 'BRF':
            item.branches.found = Number(parts[1]);
            break;
          case 'BRH':
            item.branches.hit = Number(parts[1]);
            break;
        }

        if (line.indexOf('end_of_record') > -1) {
          data.push(item);
          item = {
            lines: {
              found: 0,
              hit: 0,
              details: []
            },
            functions: {
              hit: 0,
              found: 0,
              details: []
            },
            branches: {
              hit: 0,
              found: 0,
              details: []
            }
          };
        }
      });

      data.shift();

      if (data.length) {
        cb(undefined, data);
      } else {
        cb('Failed to parse string');
      }
    }

    /**
     * will clean a lcov string to strip out any unnecessary content
     * @method clean
     * @param  {String} coverage - the coverage string to clean
     * @return {String} - the cleaned version of the coverage string
     */
    function clean(coverage) {
      return coverage.substr(coverage.indexOf('TN:'), coverage.lastIndexOf('end_of_record') + 'end_of_record'.length);
    }

    function parse(file) {
      return new Promise(function(resolve, reject) {
        if (fs.existsSync(file)) {
          fs.readFile(file, 'utf8', (err, str) => {
            if (err) {
              reject(err);
            }
            return walkFile(clean(str), function(err, result) {
              if (err) return reject(err);
              resolve(result);
            });
          });
        } else {
          return walkFile(clean(file), function(err, result) {
            if (err) return reject(err);
            resolve(result);
          });
        }
      });
    }

    module.exports = {
      walkFile,
      parse,
      clean
    };