import Parsers from './Parsers'
// a set of 0x00 terminated subblocks
var subBlocks = {
  label: "blocks",
  parser: function(stream) {
    var out = [];
    var terminator = 0x00;
    for (
      var size = stream.readByte();
      size !== terminator;
      size = stream.readByte()
    ) {
      out = out.concat(stream.readBytes(size));
    }
    return out;
  }
};

// global control extension
var gce = {
  label: "gce",
  requires: function(stream) {
    // just peek at the top two bytes, and if true do this
    var codes = stream.peekBytes(2);
    return codes[0] === 0x21 && codes[1] === 0xf9;
  },
  parts: [
    { label: "codes", parser: Parsers.readBytes(2), skip: true },
    { label: "byteSize", parser: Parsers.readByte() },
    {
      label: "extras",
      bits: {
        future: { index: 0, length: 3 },
        disposal: { index: 3, length: 3 },
        userInput: { index: 6 },
        transparentColorGiven: { index: 7 }
      }
    },
    { label: "delay", parser: Parsers.readUnsigned(true) },
    { label: "transparentColorIndex", parser: Parsers.readByte() },
    { label: "terminator", parser: Parsers.readByte(), skip: true }
  ]
};

// image pipeline block
var image = {
  label: "image",
  requires: function(stream) {
    // peek at the next byte
    var code = stream.peekByte();
    return code === 0x2c;
  },
  parts: [
    { label: "code", parser: Parsers.readByte(), skip: true },
    {
      label: "descriptor", // image descriptor
      parts: [
        { label: "left", parser: Parsers.readUnsigned(true) },
        { label: "top", parser: Parsers.readUnsigned(true) },
        { label: "width", parser: Parsers.readUnsigned(true) },
        { label: "height", parser: Parsers.readUnsigned(true) },
        {
          label: "lct",
          bits: {
            exists: { index: 0 },
            interlaced: { index: 1 },
            sort: { index: 2 },
            future: { index: 3, length: 2 },
            size: { index: 5, length: 3 }
          }
        }
      ]
    },
    {
      label: "lct", // optional local color table
      requires: function(stream, obj, parent) {
        return parent.descriptor.lct.exists;
      },
      parser: Parsers.readArray(3, function(stream, obj, parent) {
        return Math.pow(2, parent.descriptor.lct.size + 1);
      })
    },
    {
      label: "data", // the image data blocks
      parts: [{ label: "minCodeSize", parser: Parsers.readByte() }, subBlocks]
    }
  ]
};

// plain text block
var text = {
  label: "text",
  requires: function(stream) {
    // just peek at the top two bytes, and if true do this
    var codes = stream.peekBytes(2);
    return codes[0] === 0x21 && codes[1] === 0x01;
  },
  parts: [
    { label: "codes", parser: Parsers.readBytes(2), skip: true },
    { label: "blockSize", parser: Parsers.readByte() },
    {
      label: "preData",
      parser: function(stream, obj, parent) {
        return stream.readBytes(parent.text.blockSize);
      }
    },
    subBlocks
  ]
};

// application block
var application = {
  label: "application",
  requires: function(stream, obj, parent) {
    // make sure this frame doesn't already have a gce, text, comment, or image
    // as that means this block should be attached to the next frame
    //if(parent.gce || parent.text || parent.image || parent.comment){ return false; }

    // peek at the top two bytes
    var codes = stream.peekBytes(2);
    return codes[0] === 0x21 && codes[1] === 0xff;
  },
  parts: [
    { label: "codes", parser: Parsers.readBytes(2), skip: true },
    { label: "blockSize", parser: Parsers.readByte() },
    {
      label: "id",
      parser: function(stream, obj, parent) {
        return stream.readString(parent.blockSize);
      }
    },
    subBlocks
  ]
};

// comment block
var comment = {
  label: "comment",
  requires: function(stream, obj, parent) {
    // make sure this frame doesn't already have a gce, text, comment, or image
    // as that means this block should be attached to the next frame
    //if(parent.gce || parent.text || parent.image || parent.comment){ return false; }

    // peek at the top two bytes
    var codes = stream.peekBytes(2);
    return codes[0] === 0x21 && codes[1] === 0xfe;
  },
  parts: [
    { label: "codes", parser: Parsers.readBytes(2), skip: true },
    subBlocks
  ]
};

// frames of ext and image data
var frames = {
  label: "frames",
  parts: [gce, application, comment, image, text],
  loop: function(stream) {
    var nextCode = stream.peekByte();
    // rather than check for a terminator, we should check for the existence
    // of an ext or image block to avoid infinite loops
    //var terminator = 0x3B;
    //return nextCode !== terminator;
    return nextCode === 0x21 || nextCode === 0x2c;
  }
};

// main GIF schema
var schemaGIF = [
  {
    label: "header", // gif header
    parts: [
      { label: "signature", parser: Parsers.readString(3) },
      { label: "version", parser: Parsers.readString(3) }
    ]
  },
  {
    label: "lsd", // local screen descriptor
    parts: [
      { label: "width", parser: Parsers.readUnsigned(true) },
      { label: "height", parser: Parsers.readUnsigned(true) },
      {
        label: "gct",
        bits: {
          exists: { index: 0 },
          resolution: { index: 1, length: 3 },
          sort: { index: 4 },
          size: { index: 5, length: 3 }
        }
      },
      { label: "backgroundColorIndex", parser: Parsers.readByte() },
      { label: "pixelAspectRatio", parser: Parsers.readByte() }
    ]
  },
  {
    label: "gct", // global color table
    requires: function(stream, obj) {
      return obj.lsd.gct.exists;
    },
    parser: Parsers.readArray(3, function(stream, obj) {
      return Math.pow(2, obj.lsd.gct.size + 1);
    })
  },
  frames // content frames
];
export default schemaGIF
