'use strict';

const coercions = require('./coercions.js');

const NQPInt = require('./nqp-int.js');

const JSBI = require('jsbi');
const asIntN = require('./as-int-n.js');

class CodePair {
  constructor(STable) {
    this.STable = STable;
  }

  configure(conf) {
    this.fetch = conf.content.get('fetch');
    this.store = conf.content.get('store');

    this.setupSTable();
  }

  setupSTable() {
    const fetch = this.fetch;
    const store = this.store;

    this.STable.addInternalMethods(class {
      $$assignunchecked(ctx, value) {
        return store.$$call(ctx, {}, this, value);
      }

      $$assign(ctx, value) {
        return store.$$call(ctx, {}, this, value);
      }

      $$iscont() {
        return 1;
      }

      $$isrwcont() {
        return 1;
      }


      $$decont(ctx) {
        return fetch.$$call(ctx, {}, this);
      }
    });
  }

  serialize(cursor) {
    cursor.ref(this.fetch);
    cursor.ref(this.store);
  }

  deserialize(cursor) {
    this.fetch = cursor.variant();
    this.store = cursor.variant();
  }
};

CodePair.prototype.name = 'code_pair';

exports.code_pair = CodePair;

class NativeRef {
  constructor(STable) {
    this.STable = STable;
  }

  configure(conf) {
    this.setupSTable();
  }

  serialize(cursor) {
  }

  deserialize(cursor) {
  }

  setupSTable() {
    const primitiveType = this.STable.REPR.primitiveType;
    const STable = this.STable;

    /* TODO - take the hll in account when converting to object */

    if (primitiveType == 1) { // int
      this.STable.addInternalMethods(class {
        $$iscont_i() {
          return 1;
        }

        $$iscont() {
          return 1;
        }

        $$isrwcont() {
          return 1;
        }

        $$assign(ctx, value) {
          this.set(value.$$getInt());
        }

        $$assign_i(ctx, value) {
          this.set(value);
        }

        $$decont_i(ctx) {
          return this.get();
        }

        $$getInt() {
          return this.get();
        }

        $$getNum() {
          return this.get();
        }

        $$getStr() {
          return this.get().toString();
        }

        $$decont(ctx) {
          let hll = STable.hllOwner;
          if (hll === undefined) {
            hll = ctx.codeRef().staticCode.hll;
          }

          const type = hll.get('int_box');
          if (!type) {
            // HACK - nqp still uses NQPInt instead of the thing in the hll config
            return new NQPInt(this.get());
          } else {
            const repr = type.$$STable.REPR;
            const obj = repr.allocate(type.$$STable);
            obj.$$setInt(this.get());
            return obj;
          }
        }
      });
    } else if (primitiveType == 2) { // num
      this.STable.addInternalMethods(class {
        $$iscont_n() {
          return 1;
        }

        $$iscont() {
          return 1;
        }

        $$isrwcont() {
          return 1;
        }

        $$assign_n(ctx, value) {
          this.set(value);
        }

        $$decont_n(ctx) {
          return this.get();
        }

        $$getInt() {
          return this.get()|0;
        }

        $$getNum() {
          return this.get();
        }

        $$getStr() {
          return coercions.numToStr(this.get());
        }

        $$decont(ctx) {
          let hll = STable.hllOwner;
          if (hll === undefined) {
            hll = ctx.codeRef().staticCode.hll;
          }

          const type = hll.get('num_box');
          if (!type) {
            // HACK - nqp still uses raw javascript numbers instead of the thing in the hll config
            return this.get();
          } else {
            const repr = type.$$STable.REPR;
            const obj = repr.allocate(type.$$STable);
            obj.$$setNum(this.get());
            return obj;
          }
        }

        $$assign(ctx, value) {
          this.set(value.$$getNum());
        }
      });
    } else if (primitiveType == 3) { // str
      this.STable.addInternalMethods(class {
        $$iscont_s() {
          return 1;
        }

        $$iscont() {
          return 1;
        }

        $$isrwcont() {
          return 1;
        }

        $$assign_s(ctx, value) {
          this.set(value);
        }

        $$decont_s(ctx) {
          return this.get();
        }

        $$getStr() {
          return this.get();
        }

        $$getInt() {
          return coercions.strToInt(this.get());
        }

        $$getNum() {
          return coercions.strToNum(this.get());
        }

        $$decont(ctx) {
          let hll = STable.hllOwner;
          if (hll === undefined) {
            hll = ctx.codeRef().staticCode.hll;
          }

          const type = hll.get('str_box');
          if (!type) {
            // HACK - nqp still uses raw javascript strings instead of the thing in the hll config
            return this.get();
          } else {
            const repr = type.$$STable.REPR;
            const obj = repr.allocate(type.$$STable);
            obj.$$setStr(this.get());
            return obj;
          }
        }

        $$assign(ctx, value) {
          this.set(value.$$getStr());
        }
      });
    } else if (primitiveType == 4) { // int64
      this.STable.addInternalMethods(class {
        $$iscont_i() {
          return 1;
        }

        $$iscont() {
          return 1;
        }

        $$isrwcont() {
          return 1;
        }

        $$assign_i64(ctx, value) {
          this.set(value);
        }

        $$assign_u64(ctx, value) {
          this.set(value);
        }

        $$assign(ctx, value) {
          this.set(value.$$getInt64());
        }

        $$getStr() {
          return this.get().toString();
        }

        $$getInt64() {
          return this.get();
        }

        // HACK till we get better int64 support in rakudo.js
        $$getInt() {
          return JSBI.toNumber(asIntN.asIntN(32, this.get()));
        }

        // HACK till we get better int64 support in rakudo.js
        $$assign_i(ctx, value) {
          this.set(JSBI.BigInt(value));
        }

        $$getUint64() {
          return asIntN.asUintN(64, this.get());
        }

        $$decont(ctx) {
          let hll = STable.hllOwner;
          if (hll === undefined) {
            hll = ctx.codeRef().staticCode.hll;
          }

          const type = hll.get('int_box');

          const repr = type.$$STable.REPR;
          const obj = repr.allocate(type.$$STable);
          obj.$$setInt64(this.get());
          return obj;
        }
      });
    } else {
      throw 'incorrect type of NativeRef: ' + primitiveType;
    }
  }
};

NativeRef.prototype.name = 'native_ref';

exports.native_ref = NativeRef;
