text: /**
   * @fileoverview Implements x86 video hardware
   * @author Jeff Parsons <Jeff@pcjs.org>
   * @copyright © 2012-2020 Jeff Parsons
   * @license MIT <https://www.pcjs.org/LICENSE.txt>
   *
   * This file is part of PCjs, a computer emulation software project at <https://www.pcjs.org>.
   */
  
  import Monitor from "../../modules/monitor.js";
  
  /**
   * @typedef {MonitorConfig} PCx86VideoConfig
   * @property {number} bufferWidth
   * @property {number} bufferHeight
   * @property {number} bufferAddr
   * @property {number} bufferBits
   * @property {number} bufferLeft
   * @property {number} interruptRate
   */
  
  /**
   * @class {PCx86Video}
   * @unrestricted
   * @property {PCx86VideoConfig} config
   */
  export default class PCx86Video extends Monitor {
      /**
       * PCx86Video(idMachine, idDevice, config)
       *
       * The PCx86Video component can be configured with the following config properties:
       *
       *      bufferWidth: the width of a single frame buffer row, in pixels (eg, 256)
       *      bufferHeight: the number of frame buffer rows (eg, 224)
       *      bufferAddr: the starting address of the frame buffer (eg, 0x2400)
       *      bufferRAM: true to use existing RAM (default is false)
       *      bufferBits: the number of bits per column (default is 1)
       *      bufferLeft: the bit position of the left-most pixel in a byte (default is 0; CGA uses 7)
       *      bufferRotate: the amount of counter-clockwise buffer rotation required (eg, -90 or 270)
       *      interruptRate: normally the same as (or some multiple of) refreshRate (eg, 120)
       *      refreshRate: how many times updateMonitor() should be performed per second (eg, 60)
       *
       * We record all the above values now, but we defer creation of the frame buffer until initBuffers()
       * is called.  At that point, we will also compute the extent of the frame buffer, determine the
       * appropriate "cell" size (ie, the number of pixels that updateMonitor() will fetch and process at once),
       * and then allocate our cell cache.
       *
       * @this {PCx86Video}
       * @param {string} idMachine
       * @param {string} idDevice
       * @param {ROMConfig} [config]
       */
      constructor(idMachine, idDevice, config)
      {
          super(idMachine, idDevice, config);
  
          let video = this
          this.addrBuffer = this.config['bufferAddr'];
  
          this.nColsBuffer = this.config['bufferWidth'];
          this.nRowsBuffer = this.config['bufferHeight'];
  
          this.cxCell = this.config['cellWidth'] || 1;
          this.cyCell = this.config['cellHeight'] || 1;
  
          this.rateInterrupt = this.config['interruptRate'];
          this.rateRefresh = this.config['refreshRate'] || 60;
  
          this.cxMonitorCell = (this.cxMonitor / this.nColsBuffer)|0;
          this.cyMonitorCell = (this.cyMonitor / this.nRowsBuffer)|0;
  
          this.busMemory = /** @type {Bus} */ (this.findDevice(this.config['bus']));
          // this.initBuffers();
  
          this.cpu = /** @type {CPUx86} */ (this.findDeviceByClass("CPU"));
          this.time = /** @type {Time} */ (this.findDeviceByClass("Time"));
          this.timerUpdateNext = this.time.addTimer(this.idDevice, this.updateMonitor.bind(this));
          this.time.addUpdate(this);
  
          this.time.setTimer(this.timerUpdateNext, this.getRefreshTime());
          this.nUpdates = 0;
      }
  
      /**
       * onUpdate(fTransition)
       *
       * This is our obligatory update() function, which every device with visual components should have.
       *
       * For the video device, our sole function is making sure the screen display is up-to-date.  However, calling
       * updateScreen() is a bad idea if the machine is running, because we already have a timer to take care of
       * that.  But we can also be called when the machine is NOT running (eg, the Debugger may be stepping through
       * some code, or editing the frame buffer directly, or something else).  Since we have no way of knowing, we
       * must force an update.
       *
       * @this {PCx86Video}
       * @param {boolean} [fTransition]
       */
      onUpdate(fTransition)
      {
          if (!this.time.isRunning()) this.updateScreen();
      }
  
      /**
       * getRefreshTime()
       *
       * @this {PCx86Video}
       * @returns {number} (number of milliseconds per refresh)
       */
      getRefreshTime()
      {
          return 1000 / Math.max(this.rateRefresh, this.rateInterrupt);
      }
  
      /**
       * initCache(nCells)
       *
       * Initializes the contents of our internal cell cache.
       *
       * @this {PCx86Video}
       * @param {number} [nCells]
       */
      initCache(nCells)
      {
          this.fCacheValid = false;
          if (nCells) {
              this.nCacheCells = nCells;
              if (this.aCacheCells === undefined || this.aCacheCells.length != this.nCacheCells) {
                  this.aCacheCells = new Array(this.nCacheCells);
              }
          }
      }
  
      /**
       * updateMonitor(fForced)
       *
       * Forced updates are generally internal updates triggered by an I/O operation or other state change,
       * while non-forced updates are periodic "refresh" updates.
       *
       * @this {PCx86Video}
       * @param {boolean} [fForced]
       */
      updateMonitor(fForced)
      {
          let fUpdate = true;
          if (!fForced) {
              /*
               * Since this is not a forced update, if our cell cache is valid AND we allocated our own buffer AND the buffer
               * is clean, then there's nothing to do.
               */
              if (fUpdate && this.fCacheValid && this.sizeBuffer) {
                  if (this.busMemory.cleanBlocks(this.addrBuffer, this.sizeBuffer)) {
                      fUpdate = false;
                  }
              }
              this.time.setTimer(this.timerUpdateNext, this.getRefreshTime());
              this.nUpdates++;
              if (!fUpdate) return;
          }
          this.updateScreen();
      }
  
      /**
       * updateScreen()
       *
       * Propagates the video buffer to the cell cache and updates the screen with any changes on the monitor.
       *
       * For every cell in the video buffer, compare it to the cell stored in the cell cache, render if it differs,
       * and then update the cell cache to match.  Since initCache() sets every cell in the cell cache to an
       * invalid value, we're assured that the next call to updateScreen() will redraw the entire (visible) video buffer.
       *
       * @this {PCx86Video}
       */
      updateScreen()
      {
      }
  }
  
  PCx86Video.CLASSES["PCx86Video"] = PCx86Video;
  