/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow
 */

'use strict';

const fs = require('fs');
const mkdirp = require('mkdirp');
const path = require('path');
const rimraf = require('rimraf');

const NULL_BYTE = 0x00;
const NULL_BYTE_BUFFER = Buffer.from([NULL_BYTE]);

export type Options = {|
  root: string,
|};

class FileStore<T> {
  _root: string;

  constructor(options: Options) {
    this._root = options.root;
    this._createDirs();
  }

  get(key: Buffer): ?T {
    try {
      const data = fs.readFileSync(this._getFilePath(key));

      if (data[0] === NULL_BYTE) {
        return (data.slice(1): any);
      } else {
        return JSON.parse(data.toString('utf8'));
      }
    } catch (err) {
      if (err.code === 'ENOENT') {
        return null;
      }

      throw err;
    }
  }

  set(key: Buffer, value: T): void {
    if (value instanceof Buffer) {
      const fd = fs.openSync(this._getFilePath(key), 'w');

      fs.writeSync(fd, NULL_BYTE_BUFFER);
      fs.writeSync(fd, value);

      fs.closeSync(fd);
    } else {
      fs.writeFileSync(this._getFilePath(key), JSON.stringify(value));
    }
  }

  clear() {
    this._removeDirs();
    this._createDirs();
  }

  _getFilePath(key: Buffer): string {
    return path.join(
      this._root,
      key.slice(0, 1).toString('hex'),
      key.slice(1).toString('hex'),
    );
  }

  _createDirs() {
    for (let i = 0; i < 256; i++) {
      mkdirp.sync(path.join(this._root, ('0' + i.toString(16)).slice(-2)));
    }
  }

  _removeDirs() {
    for (let i = 0; i < 256; i++) {
      rimraf.sync(path.join(this._root, ('0' + i.toString(16)).slice(-2)));
    }
  }
}

module.exports = FileStore;