text: import Hex from "./hex.js";
  import Rect from "./rect.js";
  import Tile from "./tile.js";
  import TileGL from "./tile-gl.js";
  import Term from "./term.js";
  import * as Text from "../text.js";
  import { DEFAULT_WIDTH, DEFAULT_HEIGHT } from "../constants.js";
  const BACKENDS = {
      "hex": Hex,
      "rect": Rect,
      "tile": Tile,
      "tile-gl": TileGL,
      "term": Term
  };
  const DEFAULT_OPTIONS = {
      width: DEFAULT_WIDTH,
      height: DEFAULT_HEIGHT,
      transpose: false,
      layout: "rect",
      fontSize: 15,
      spacing: 1,
      border: 0,
      forceSquareRatio: false,
      fontFamily: "monospace",
      fontStyle: "",
      fg: "#ccc",
      bg: "#000",
      tileWidth: 32,
      tileHeight: 32,
      tileMap: {},
      tileSet: null,
      tileColorize: false
  };
  /**
   * @class Visual map display
   */
  let Display = /** @class */ (() => {
      class Display {
          constructor(options = {}) {
              this._data = {};
              this._dirty = false; // false = nothing, true = all, object = dirty cells
              this._options = {};
              options = Object.assign({}, DEFAULT_OPTIONS, options);
              this.setOptions(options);
              this.DEBUG = this.DEBUG.bind(this);
              this._tick = this._tick.bind(this);
              this._backend.schedule(this._tick);
          }
          /**
           * Debug helper, ideal as a map generator callback. Always bound to this.
           * @param {int} x
           * @param {int} y
           * @param {int} what
           */
          DEBUG(x, y, what) {
              let colors = [this._options.bg, this._options.fg];
              this.draw(x, y, null, null, colors[what % colors.length]);
          }
          /**
           * Clear the whole display (cover it with background color)
           */
          clear() {
              this._data = {};
              this._dirty = true;
          }
          /**
           * @see ROT.Display
           */
          setOptions(options) {
              Object.assign(this._options, options);
              if (options.width || options.height || options.fontSize || options.fontFamily || options.spacing || options.layout) {
                  if (options.layout) {
                      let ctor = BACKENDS[options.layout];
                      this._backend = new ctor();
                  }
                  this._backend.setOptions(this._options, 'tileSet' in options);
                  this._dirty = true;
              }
              return this;
          }
          /**
           * Returns currently set options
           */
          getOptions() { return this._options; }
          /**
           * Returns the DOM node of this display
           */
          getContainer() { return this._backend.getContainer(); }
          /**
           * Compute the maximum width/height to fit into a set of given constraints
           * @param {int} availWidth Maximum allowed pixel width
           * @param {int} availHeight Maximum allowed pixel height
           * @returns {int[2]} cellWidth,cellHeight
           */
          computeSize(availWidth, availHeight) {
              return this._backend.computeSize(availWidth, availHeight);
          }
          /**
           * Compute the maximum font size to fit into a set of given constraints
           * @param {int} availWidth Maximum allowed pixel width
           * @param {int} availHeight Maximum allowed pixel height
           * @returns {int} fontSize
           */
          computeFontSize(availWidth, availHeight) {
              return this._backend.computeFontSize(availWidth, availHeight);
          }
          computeTileSize(availWidth, availHeight) {
              let width = Math.floor(availWidth / this._options.width);
              let height = Math.floor(availHeight / this._options.height);
              return [width, height];
          }
          /**
           * Convert a DOM event (mouse or touch) to map coordinates. Uses first touch for multi-touch.
           * @param {Event} e event
           * @returns {int[2]} -1 for values outside of the canvas
           */
          eventToPosition(e) {
              let x, y;
              if ("touches" in e) {
                  x = e.touches[0].clientX;
                  y = e.touches[0].clientY;
              }
              else {
                  x = e.clientX;
                  y = e.clientY;
              }
              return this._backend.eventToPosition(x, y);
          }
          /**
           * @param {int} x
           * @param {int} y
           * @param {string || string[]} ch One or more chars (will be overlapping themselves)
           * @param {string} [fg] foreground color
           * @param {string} [bg] background color
           */
          draw(x, y, ch, fg, bg) {
              if (!fg) {
                  fg = this._options.fg;
              }
              if (!bg) {
                  bg = this._options.bg;
              }
              let key = `${x},${y}`;
              this._data[key] = [x, y, ch, fg, bg];
              if (this._dirty === true) {
                  return;
              } // will already redraw everything 
              if (!this._dirty) {
                  this._dirty = {};
              } // first!
              this._dirty[key] = true;
          }
          /**
           * Draws a text at given position. Optionally wraps at a maximum length. Currently does not work with hex layout.
           * @param {int} x
           * @param {int} y
           * @param {string} text May contain color/background format specifiers, %c{name}/%b{name}, both optional. %c{}/%b{} resets to default.
           * @param {int} [maxWidth] wrap at what width?
           * @returns {int} lines drawn
           */
          drawText(x, y, text, maxWidth) {
              let fg = null;
              let bg = null;
              let cx = x;
              let cy = y;
              let lines = 1;
              if (!maxWidth) {
                  maxWidth = this._options.width - x;
              }
              let tokens = Text.tokenize(text, maxWidth);
              while (tokens.length) { // interpret tokenized opcode stream
                  let token = tokens.shift();
                  switch (token.type) {
                      case Text.TYPE_TEXT:
                          let isSpace = false, isPrevSpace = false, isFullWidth = false, isPrevFullWidth = false;
                          for (let i = 0; i < token.value.length; i++) {
                              let cc = token.value.charCodeAt(i);
                              let c = token.value.charAt(i);
                              if (this._options.layout === "term") {
                                  let cch = cc >> 8;
                                  let isCJK = cch === 0x11 || (cch >= 0x2e && cch <= 0x9f) || (cch >= 0xac && cch <= 0xd7) || (cc >= 0xA960 && cc <= 0xA97F);
                                  if (isCJK) {
                                      this.draw(cx + 0, cy, c, fg, bg);
                                      this.draw(cx + 1, cy, "\t", fg, bg);
                                      cx += 2;
                                      continue;
                                  }
                              }
                              // Assign to `true` when the current char is full-width.
                              isFullWidth = (cc > 0xff00 && cc < 0xff61) || (cc > 0xffdc && cc < 0xffe8) || cc > 0xffee;
                              // Current char is space, whatever full-width or half-width both are OK.
                              isSpace = (c.charCodeAt(0) == 0x20 || c.charCodeAt(0) == 0x3000);
                              // The previous char is full-width and
                              // current char is nether half-width nor a space.
                              if (isPrevFullWidth && !isFullWidth && !isSpace) {
                                  cx++;
                              } // add an extra position
                              // The current char is full-width and
                              // the previous char is not a space.
                              if (isFullWidth && !isPrevSpace) {
                                  cx++;
                              } // add an extra position
                              this.draw(cx++, cy, c, fg, bg);
                              isPrevSpace = isSpace;
                              isPrevFullWidth = isFullWidth;
                          }
                          break;
                      case Text.TYPE_FG:
                          fg = token.value || null;
                          break;
                      case Text.TYPE_BG:
                          bg = token.value || null;
                          break;
                      case Text.TYPE_NEWLINE:
                          cx = x;
                          cy++;
                          lines++;
                          break;
                  }
              }
              return lines;
          }
          /**
           * Timer tick: update dirty parts
           */
          _tick() {
              this._backend.schedule(this._tick);
              if (!this._dirty) {
                  return;
              }
              if (this._dirty === true) { // draw all
                  this._backend.clear();
                  for (let id in this._data) {
                      this._draw(id, false);
                  } // redraw cached data 
              }
              else { // draw only dirty 
                  for (let key in this._dirty) {
                      this._draw(key, true);
                  }
              }
              this._dirty = false;
          }
          /**
           * @param {string} key What to draw
           * @param {bool} clearBefore Is it necessary to clean before?
           */
          _draw(key, clearBefore) {
              let data = this._data[key];
              if (data[4] != this._options.bg) {
                  clearBefore = true;
              }
              this._backend.draw(data, clearBefore);
          }
      }
      Display.Rect = Rect;
      Display.Hex = Hex;
      Display.Tile = Tile;
      Display.TileGL = TileGL;
      Display.Term = Term;
      return Display;
  })();
  export default Display;
  