import * as r from 'restructure';
import {ScriptList, FeatureList, LookupList, Coverage, ClassDef, Device, Context, ChainingContext} from './opentype';
import {FeatureVariations} from './variations';

let ValueFormat = new r.Bitfield(r.uint16, [
  'xPlacement', 'yPlacement',
  'xAdvance', 'yAdvance',
  'xPlaDevice', 'yPlaDevice',
  'xAdvDevice', 'yAdvDevice'
]);

let types = {
  xPlacement: r.int16,
  yPlacement: r.int16,
  xAdvance:   r.int16,
  yAdvance:   r.int16,
  xPlaDevice: new r.Pointer(r.uint16, Device, { type: 'global', relativeTo: ctx => ctx.rel }),
  yPlaDevice: new r.Pointer(r.uint16, Device, { type: 'global', relativeTo: ctx => ctx.rel }),
  xAdvDevice: new r.Pointer(r.uint16, Device, { type: 'global', relativeTo: ctx => ctx.rel }),
  yAdvDevice: new r.Pointer(r.uint16, Device, { type: 'global', relativeTo: ctx => ctx.rel })
};

class ValueRecord {
  constructor(key = 'valueFormat') {
    this.key = key;
  }

  buildStruct(parent) {
    let struct = parent;
    while (!struct[this.key] && struct.parent) {
      struct = struct.parent;
    }

    if (!struct[this.key]) return;

    let fields = {};
    fields.rel = () => struct._startOffset;

    let format = struct[this.key];
    for (let key in format) {
      if (format[key]) {
        fields[key] = types[key];
      }
    }

    return new r.Struct(fields);
  }

  size(val, ctx) {
    return this.buildStruct(ctx).size(val, ctx);
  }

  decode(stream, parent) {
    let res = this.buildStruct(parent).decode(stream, parent);
    delete res.rel;
    return res;
  }
}

let PairValueRecord = new r.Struct({
  secondGlyph:    r.uint16,
  value1:         new ValueRecord('valueFormat1'),
  value2:         new ValueRecord('valueFormat2')
});

let PairSet = new r.Array(PairValueRecord, r.uint16);

let Class2Record = new r.Struct({
  value1: new ValueRecord('valueFormat1'),
  value2: new ValueRecord('valueFormat2')
});

let Anchor = new r.VersionedStruct(r.uint16, {
  1: { // Design units only
    xCoordinate:    r.int16,
    yCoordinate:    r.int16
  },

  2: { // Design units plus contour point
    xCoordinate:    r.int16,
    yCoordinate:    r.int16,
    anchorPoint:    r.uint16
  },

  3: { // Design units plus Device tables
    xCoordinate:    r.int16,
    yCoordinate:    r.int16,
    xDeviceTable:   new r.Pointer(r.uint16, Device),
    yDeviceTable:   new r.Pointer(r.uint16, Device)
  }
});

let EntryExitRecord = new r.Struct({
  entryAnchor:    new r.Pointer(r.uint16, Anchor, {type: 'parent'}),
  exitAnchor:     new r.Pointer(r.uint16, Anchor, {type: 'parent'})
});

let MarkRecord = new r.Struct({
  class:      r.uint16,
  markAnchor: new r.Pointer(r.uint16, Anchor, {type: 'parent'})
});

let MarkArray = new r.Array(MarkRecord, r.uint16);

let BaseRecord  = new r.Array(new r.Pointer(r.uint16, Anchor), t => t.parent.classCount);
let BaseArray   = new r.Array(BaseRecord, r.uint16);

let ComponentRecord = new r.Array(new r.Pointer(r.uint16, Anchor), t => t.parent.parent.classCount);
let LigatureAttach  = new r.Array(ComponentRecord, r.uint16);
let LigatureArray   = new r.Array(new r.Pointer(r.uint16, LigatureAttach), r.uint16);

let GPOSLookup = new r.VersionedStruct('lookupType', {
  1: new r.VersionedStruct(r.uint16, { // Single Adjustment
    1: { // Single positioning value
      coverage:       new r.Pointer(r.uint16, Coverage),
      valueFormat:    ValueFormat,
      value:          new ValueRecord()
    },
    2: {
      coverage:       new r.Pointer(r.uint16, Coverage),
      valueFormat:    ValueFormat,
      valueCount:     r.uint16,
      values:         new r.LazyArray(new ValueRecord(), 'valueCount')
    }
  }),

  2: new r.VersionedStruct(r.uint16, { // Pair Adjustment Positioning
    1: { // Adjustments for glyph pairs
      coverage:       new r.Pointer(r.uint16, Coverage),
      valueFormat1:   ValueFormat,
      valueFormat2:   ValueFormat,
      pairSetCount:   r.uint16,
      pairSets:       new r.LazyArray(new r.Pointer(r.uint16, PairSet), 'pairSetCount')
    },

    2: { // Class pair adjustment
      coverage:       new r.Pointer(r.uint16, Coverage),
      valueFormat1:   ValueFormat,
      valueFormat2:   ValueFormat,
      classDef1:      new r.Pointer(r.uint16, ClassDef),
      classDef2:      new r.Pointer(r.uint16, ClassDef),
      class1Count:    r.uint16,
      class2Count:    r.uint16,
      classRecords:   new r.LazyArray(new r.LazyArray(Class2Record, 'class2Count'), 'class1Count')
    }
  }),

  3: { // Cursive Attachment Positioning
    format:             r.uint16,
    coverage:           new r.Pointer(r.uint16, Coverage),
    entryExitCount:     r.uint16,
    entryExitRecords:   new r.Array(EntryExitRecord, 'entryExitCount')
  },

  4: { // MarkToBase Attachment Positioning
    format:             r.uint16,
    markCoverage:       new r.Pointer(r.uint16, Coverage),
    baseCoverage:       new r.Pointer(r.uint16, Coverage),
    classCount:         r.uint16,
    markArray:          new r.Pointer(r.uint16, MarkArray),
    baseArray:          new r.Pointer(r.uint16, BaseArray)
  },

  5: { // MarkToLigature Attachment Positioning
    format:             r.uint16,
    markCoverage:       new r.Pointer(r.uint16, Coverage),
    ligatureCoverage:   new r.Pointer(r.uint16, Coverage),
    classCount:         r.uint16,
    markArray:          new r.Pointer(r.uint16, MarkArray),
    ligatureArray:      new r.Pointer(r.uint16, LigatureArray)
  },

  6: { // MarkToMark Attachment Positioning
    format:             r.uint16,
    mark1Coverage:      new r.Pointer(r.uint16, Coverage),
    mark2Coverage:      new r.Pointer(r.uint16, Coverage),
    classCount:         r.uint16,
    mark1Array:         new r.Pointer(r.uint16, MarkArray),
    mark2Array:         new r.Pointer(r.uint16, BaseArray)
  },

  7: Context,          // Contextual positioning
  8: ChainingContext,  // Chaining contextual positioning

  9: { // Extension Positioning
    posFormat:   r.uint16,
    lookupType:  r.uint16,   // cannot also be 9
    extension:   new r.Pointer(r.uint32, null)
  }
});

// Fix circular reference
GPOSLookup.versions[9].extension.type = GPOSLookup;

export default new r.VersionedStruct(r.uint32, {
  header: {
    scriptList:     new r.Pointer(r.uint16, ScriptList),
    featureList:    new r.Pointer(r.uint16, FeatureList),
    lookupList:     new r.Pointer(r.uint16, new LookupList(GPOSLookup))
  },

  0x00010000: {},
  0x00010001: {
    featureVariations: new r.Pointer(r.uint32, FeatureVariations)
  }
});

// export GPOSLookup for JSTF table
export { GPOSLookup };
