'use strict';

// See documentation here: http://www.microsoft.com/typography/otspec/glyf.htm

var _ = require('lodash');
var ByteBuffer = require('microbuffer');

function getFlags(glyph) {
  var result = [];

  _.forEach(glyph.ttfContours, function (contour) {
    _.forEach(contour, function (point) {
      var flag = point.onCurve ? 1 : 0;

      if (point.x === 0) {
        flag += 16;
      } else {
        if (-0xFF <= point.x && point.x <= 0xFF) {
          flag += 2; // the corresponding x-coordinate is 1 byte long
        }
        if (point.x > 0 && point.x <= 0xFF) {
          flag += 16; // If x-Short Vector is set, this bit describes the sign of the value, with 1 equalling positive and 0 negative
        }
      }
      if (point.y === 0) {
        flag += 32;
      } else {
        if (-0xFF <= point.y && point.y <= 0xFF) {
          flag += 4; // the corresponding y-coordinate is 1 byte long
        }
        if (point.y > 0 && point.y <= 0xFF) {
          flag += 32; // If y-Short Vector is set, this bit describes the sign of the value, with 1 equalling positive and 0 negative.
        }
      }
      result.push(flag);
    });
  });
  return result;
}

//repeating flags can be packed
function compactFlags(flags) {
  var result = [];
  var prevFlag = -1;
  var firstRepeat = false;

  _.forEach(flags, function (flag) {
    if (prevFlag === flag) {
      if (firstRepeat) {
        result[result.length - 1] += 8; //current flag repeats previous one, need to set 3rd bit of previous flag and set 1 to the current one
        result.push(1);
        firstRepeat = false;
      } else {
        result[result.length - 1]++; //when flag is repeating second or more times, we need to increase the last flag value
      }
    } else {
      firstRepeat = true;
      prevFlag = flag;
      result.push(flag);
    }
  });
  return result;
}

function getCoords(glyph, coordName) {
  var result = [];

  _.forEach(glyph.ttfContours, function (contour) {
    result.push.apply(result, _.map(contour, coordName));
  });
  return result;
}

function compactCoords(coords) {
  return _.filter(coords, function (coord) {
    return coord !== 0;
  });
}

//calculates length of glyph data in GLYF table
function glyphDataSize(glyph) {
  // Ignore glyphs without outlines. These will get a length of zero in the “loca” table
  if (!glyph.contours.length) {
    return 0;
  }

  var result = 12; //glyph fixed properties

  result += glyph.contours.length * 2; //add contours

  _.forEach(glyph.ttf_x, function (x) {
    //add 1 or 2 bytes for each coordinate depending of its size
    result += ((-0xFF <= x && x <= 0xFF)) ? 1 : 2;
  });

  _.forEach(glyph.ttf_y, function (y) {
    //add 1 or 2 bytes for each coordinate depending of its size
    result += ((-0xFF <= y && y <= 0xFF)) ? 1 : 2;
  });

  // Add flags length to glyph size.
  result += glyph.ttf_flags.length;

  if (result % 4 !== 0) { // glyph size must be divisible by 4.
    result += 4 - result % 4;
  }
  return result;
}

function tableSize(font) {
  var result = 0;

  _.forEach(font.glyphs, function (glyph) {
    glyph.ttf_size = glyphDataSize(glyph);
    result += glyph.ttf_size;
  });
  font.ttf_glyph_size = result; //sum of all glyph lengths
  return result;
}

function createGlyfTable(font) {

  _.forEach(font.glyphs, function (glyph) {
    glyph.ttf_flags = getFlags(glyph);
    glyph.ttf_flags = compactFlags(glyph.ttf_flags);
    glyph.ttf_x = getCoords(glyph, 'x');
    glyph.ttf_x = compactCoords(glyph.ttf_x);
    glyph.ttf_y = getCoords(glyph, 'y');
    glyph.ttf_y = compactCoords(glyph.ttf_y);
  });

  var buf = new ByteBuffer(tableSize(font));

  _.forEach(font.glyphs, function (glyph) {

    // Ignore glyphs without outlines. These will get a length of zero in the “loca” table
    if (!glyph.contours.length) {
      return;
    }

    var offset = buf.tell();

    buf.writeInt16(glyph.contours.length); // numberOfContours
    buf.writeInt16(glyph.xMin); // xMin
    buf.writeInt16(glyph.yMin); // yMin
    buf.writeInt16(glyph.xMax); // xMax
    buf.writeInt16(glyph.yMax); // yMax

    // Array of end points
    var endPtsOfContours = -1;

    var ttfContours = glyph.ttfContours;

    _.forEach(ttfContours, function (contour) {
      endPtsOfContours += contour.length;
      buf.writeInt16(endPtsOfContours);
    });

    buf.writeInt16(0); // instructionLength, is not used here

    // Array of flags
    _.forEach(glyph.ttf_flags, function (flag) {
      buf.writeInt8(flag);
    });

    // Array of X relative coordinates
    _.forEach(glyph.ttf_x, function (x) {
      if (-0xFF <= x && x <= 0xFF) {
        buf.writeUint8(Math.abs(x));
      } else {
        buf.writeInt16(x);
      }
    });

    // Array of Y relative coordinates
    _.forEach(glyph.ttf_y, function (y) {
      if (-0xFF <= y && y <= 0xFF) {
        buf.writeUint8(Math.abs(y));
      } else {
        buf.writeInt16(y);
      }
    });

    var tail = (buf.tell() - offset) % 4;

    if (tail !== 0) { // glyph size must be divisible by 4.
      for (; tail < 4; tail++) {
        buf.writeUint8(0);
      }
    }
  });
  return buf;
}

module.exports = createGlyfTable;
