import * as r from 'restructure';
import { resolveLength } from 'restructure';
import CFFDict from './CFFDict';
import CFFIndex from './CFFIndex';
import CFFPointer from './CFFPointer';
import CFFPrivateDict from './CFFPrivateDict';
import StandardStrings from './CFFStandardStrings';
import { StandardEncoding, ExpertEncoding } from './CFFEncodings';
import { ISOAdobeCharset, ExpertCharset, ExpertSubsetCharset } from './CFFCharsets';
import { ItemVariationStore } from '../tables/variations';

// Checks if an operand is an index of a predefined value,
// otherwise delegates to the provided type.
class PredefinedOp {
  constructor(predefinedOps, type) {
    this.predefinedOps = predefinedOps;
    this.type = type;
  }

  decode(stream, parent, operands) {
    if (this.predefinedOps[operands[0]]) {
      return this.predefinedOps[operands[0]];
    }

    return this.type.decode(stream, parent, operands);
  }

  size(value, ctx) {
    return this.type.size(value, ctx);
  }

  encode(stream, value, ctx) {
    let index = this.predefinedOps.indexOf(value);
    if (index !== -1) {
      return index;
    }

    return this.type.encode(stream, value, ctx);
  }
}

class CFFEncodingVersion extends r.Number {
  constructor() {
    super('UInt8');
  }

  decode(stream) {
    return r.uint8.decode(stream) & 0x7f;
  }
}

let Range1 = new r.Struct({
  first: r.uint16,
  nLeft: r.uint8
});

let Range2 = new r.Struct({
  first: r.uint16,
  nLeft: r.uint16
});

let CFFCustomEncoding = new r.VersionedStruct(new CFFEncodingVersion(), {
  0: {
    nCodes: r.uint8,
    codes: new r.Array(r.uint8, 'nCodes')
  },

  1: {
    nRanges: r.uint8,
    ranges: new r.Array(Range1, 'nRanges')
  }

  // TODO: supplement?
});

let CFFEncoding = new PredefinedOp([ StandardEncoding, ExpertEncoding ], new CFFPointer(CFFCustomEncoding, { lazy: true }));

// Decodes an array of ranges until the total
// length is equal to the provided length.
class RangeArray extends r.Array {
  decode(stream, parent) {
    let length = resolveLength(this.length, stream, parent);
    let count = 0;
    let res = [];
    while (count < length) {
      let range = this.type.decode(stream, parent);
      range.offset = count;
      count += range.nLeft + 1;
      res.push(range);
    }

    return res;
  }
}

let CFFCustomCharset = new r.VersionedStruct(r.uint8, {
  0: {
    glyphs: new r.Array(r.uint16, t => t.parent.CharStrings.length - 1)
  },

  1: {
    ranges: new RangeArray(Range1, t => t.parent.CharStrings.length - 1)
  },

  2: {
    ranges: new RangeArray(Range2, t => t.parent.CharStrings.length - 1)
  }
});

let CFFCharset = new PredefinedOp([ ISOAdobeCharset, ExpertCharset, ExpertSubsetCharset ], new CFFPointer(CFFCustomCharset, {lazy: true}));

let FDRange3 = new r.Struct({
  first: r.uint16,
  fd: r.uint8
});

let FDRange4 = new r.Struct({
  first: r.uint32,
  fd: r.uint16
});

let FDSelect = new r.VersionedStruct(r.uint8, {
  0: {
    fds: new r.Array(r.uint8, t => t.parent.CharStrings.length)
  },

  3: {
    nRanges: r.uint16,
    ranges: new r.Array(FDRange3, 'nRanges'),
    sentinel: r.uint16
  },

  4: {
    nRanges: r.uint32,
    ranges: new r.Array(FDRange4, 'nRanges'),
    sentinel: r.uint32
  }
});

let ptr = new CFFPointer(CFFPrivateDict);
class CFFPrivateOp {
  decode(stream, parent, operands) {
    parent.length = operands[0];
    return ptr.decode(stream, parent, [operands[1]]);
  }

  size(dict, ctx) {
    return [CFFPrivateDict.size(dict, ctx, false), ptr.size(dict, ctx)[0]];
  }

  encode(stream, dict, ctx) {
    return [CFFPrivateDict.size(dict, ctx, false), ptr.encode(stream, dict, ctx)[0]];
  }
}

let FontDict = new CFFDict([
  // key       name                   type(s)                                 default
  [18,        'Private',              new CFFPrivateOp,                       null],
  [[12, 38],  'FontName',             'sid',                                  null],
  [[12, 7],   'FontMatrix',           'array',                                [0.001, 0, 0, 0.001, 0, 0]],
  [[12, 5],   'PaintType',            'number',                               0],
]);

let CFFTopDict = new CFFDict([
  // key       name                   type(s)                                 default
  [[12, 30],  'ROS',                  ['sid', 'sid', 'number'],               null],

  [0,         'version',              'sid',                                  null],
  [1,         'Notice',               'sid',                                  null],
  [[12, 0],   'Copyright',            'sid',                                  null],
  [2,         'FullName',             'sid',                                  null],
  [3,         'FamilyName',           'sid',                                  null],
  [4,         'Weight',               'sid',                                  null],
  [[12, 1],   'isFixedPitch',         'boolean',                              false],
  [[12, 2],   'ItalicAngle',          'number',                               0],
  [[12, 3],   'UnderlinePosition',    'number',                               -100],
  [[12, 4],   'UnderlineThickness',   'number',                               50],
  [[12, 5],   'PaintType',            'number',                               0],
  [[12, 6],   'CharstringType',       'number',                               2],
  [[12, 7],   'FontMatrix',           'array',                                [0.001, 0, 0, 0.001, 0, 0]],
  [13,        'UniqueID',             'number',                               null],
  [5,         'FontBBox',             'array',                                [0, 0, 0, 0]],
  [[12, 8],   'StrokeWidth',          'number',                               0],
  [14,        'XUID',                 'array',                                null],
  [15,        'charset',              CFFCharset,                             ISOAdobeCharset],
  [16,        'Encoding',             CFFEncoding,                            StandardEncoding],
  [17,        'CharStrings',          new CFFPointer(new CFFIndex),           null],
  [18,        'Private',              new CFFPrivateOp,                       null],
  [[12, 20],  'SyntheticBase',        'number',                               null],
  [[12, 21],  'PostScript',           'sid',                                  null],
  [[12, 22],  'BaseFontName',         'sid',                                  null],
  [[12, 23],  'BaseFontBlend',        'delta',                                null],

  // CID font specific
  [[12, 31],  'CIDFontVersion',       'number',                               0],
  [[12, 32],  'CIDFontRevision',      'number',                               0],
  [[12, 33],  'CIDFontType',          'number',                               0],
  [[12, 34],  'CIDCount',             'number',                               8720],
  [[12, 35],  'UIDBase',              'number',                               null],
  [[12, 37],  'FDSelect',             new CFFPointer(FDSelect),               null],
  [[12, 36],  'FDArray',              new CFFPointer(new CFFIndex(FontDict)), null],
  [[12, 38],  'FontName',             'sid',                                  null]
]);

let VariationStore = new r.Struct({
  length: r.uint16,
  itemVariationStore: ItemVariationStore
})

let CFF2TopDict = new CFFDict([
  [[12, 7],   'FontMatrix',           'array',                                [0.001, 0, 0, 0.001, 0, 0]],
  [17,        'CharStrings',          new CFFPointer(new CFFIndex),           null],
  [[12, 37],  'FDSelect',             new CFFPointer(FDSelect),               null],
  [[12, 36],  'FDArray',              new CFFPointer(new CFFIndex(FontDict)), null],
  [24,        'vstore',               new CFFPointer(VariationStore),         null],
  [25,        'maxstack',             'number',                               193]
]);

let CFFTop = new r.VersionedStruct(r.fixed16, {
  1: {
    hdrSize:            r.uint8,
    offSize:            r.uint8,
    nameIndex:          new CFFIndex(new r.String('length')),
    topDictIndex:       new CFFIndex(CFFTopDict),
    stringIndex:        new CFFIndex(new r.String('length')),
    globalSubrIndex:    new CFFIndex
  },

  2: {
    hdrSize:            r.uint8,
    length:             r.uint16,
    topDict:            CFF2TopDict,
    globalSubrIndex:    new CFFIndex
  }
});

export default CFFTop;
