// host-cache.js
// Copyright (C) 2022 DTP Technologies, LLC
// License: Apache-2.0

'use strict';

const dgram = require('dgram');
const uuidv4 = require('uuid').v4;

const { SiteService, SiteError } = require('../../lib/site-lib');

class HostCacheService extends SiteService {

  constructor (dtp) {
    super(dtp, module.exports);
    this.transactions = { };
  }

  async start ( ) {
    await super.start();

    this.log.info('creating UDP host-cache socket');
    this.hostCache = dgram.createSocket('udp4', this.onMessage.bind(this));
    this.hostCache.on('error', this.onError.bind(this));

    this.log.info('connecting UDP host-cache socket');
    this.hostCache.bind(0, '127.0.0.1');

    const HOST_PORT = parseInt(process.env.DTP_HOST_CACHE_PORT || '8000', 10);
    this.hostCache.connect(HOST_PORT, '127.0.0.1');
  }

  async stop ( ) {
    if (this.hostCache) {
      this.log.info('disconnecting UDP host-cache socket');
      this.hostCache.disconnect();
      delete this.hostCache;
    }
    await super.stop();
  }

  async getFile (bucket, key) {
    return new Promise((resolve, reject) => {
      const transaction = { tid: uuidv4(), bucket, key, resolve, reject };
      this.transactions[transaction.tid] = transaction;
      const message = JSON.stringify({
        tid: transaction.tid,
        cmd: 'getFile',
        params: { bucket, key },
      });
      this.hostCache.send(message);
    });
  }

  async onMessage (message, rinfo) {
    message = message.toString('utf8');
    message = JSON.parse(message);
    switch (message.res.cmd) {
      case 'getFile':
        return this.onGetFile(message, rinfo);
    }
  }

  async onGetFile (message) {
    const transaction = this.transactions[message.tid];
    if (!transaction) {
      this.log.error('getFile response received with no matching transaction', { tid: message.tid });
      return;
    }
    if (!message.res.success) {
      transaction.reject(new SiteError(message.res.statusCode, message.res.message));
      delete this.transactions[message.tid];
      return;
    }
    transaction.resolve({
      success: message.res.success,
      message: message.res.message,
      file: message.res.file,
      flags: message.flags,
      duration: message.duration,
    });
    delete this.transactions[message.tid];
  }

  async onError (error) {
    this.log.error('onError', { error });
    if ((error.errno !== -111) || (error.code !== 'ECONNREFUSED')) {
      return;
    }
    const keys = Object.keys(this.transactions);
    keys.forEach((key) => {
      const transaction = this.transactions[key];
      transaction.reject(error);
      delete this.transactions[key];
    });
  }
}

module.exports = {
  slug: 'host-cache',
  name: 'hostCache',
  create: (dtp) => { return new HostCacheService(dtp); },
};