import * as d3 from 'd3-scale';
import { R as nearestPowerOfTwo } from '../../Common/Core/Math/index.js';
import { m as macro } from '../../macros2.js';
import vtkActor from './Actor.js';
import vtkDataArray from '../../Common/Core/DataArray.js';
import vtkScalarsToColors from '../../Common/Core/ScalarsToColors.js';
import vtkMapper from './Mapper.js';
import vtkPolyData from '../../Common/DataModel/PolyData.js';
import vtkTexture from './Texture.js';

const {
  VectorMode
} = vtkScalarsToColors;

// ----------------------------------------------------------------------------
// vtkScalarBarActor
//
// Note log scales are currently not supported
//
// Developer note: This class is broken into the main class and a helper
// class. The main class holds view independent properties (those properties
// that do not change as the view's resolution/aspect ratio change). The
// helper class is instantiated one per view and holds properties that can
// depend on view specific values such as resolution. The helper class code
// could have been left to the View specific implementation (such as
// vtkWebGPUScalarBarActor) but is instead placed here to it can be shared by
// multiple rendering backends.
//
// ----------------------------------------------------------------------------

function applyTextStyle(ctx, style) {
  ctx.strokeStyle = style.strokeColor;
  ctx.lineWidth = style.strokeSize;
  ctx.fillStyle = style.fontColor;
  ctx.font = `${style.fontStyle} ${style.fontSize}px ${style.fontFamily}`;
}

// ----------------------------------------------------------------------------
// Default autoLayout function
// ----------------------------------------------------------------------------

// compute good values to use based on window size etc a bunch of heuristics
// here with hand tuned constants These values worked for me but really this
// method could be redically changed. The basic gist is
// 1) compute a resonable font size
// 2) render the text atlas using those font sizes
// 3) pick horizontal or vertical bsed on window size
// 4) based on the size of the title and tick labels rendered
//    compute the box size and position such that
//    the text will all fit nicely and the bar will be a resonable size
// 5) compute the bar segments based on the above settings
//
// Note that this function can and should read values from the
// ScalarBarActor but should only write values to the view dependent helper
// instance that is provided as those values are the ones that will be used
// for rendering.
//
function defaultAutoLayout(publicAPI, model) {
  return helper => {
    // we don't do a linear scale, the proportions for
    // a 700 pixel window differ from a 1400
    const lastSize = helper.getLastSize();
    const xAxisAdjust = (lastSize[0] / 700) ** 0.8;
    const yAxisAdjust = (lastSize[1] / 700) ** 0.8;
    const minAdjust = Math.min(xAxisAdjust, yAxisAdjust);
    const axisTextStyle = helper.getAxisTextStyle();
    const tickTextStyle = helper.getTickTextStyle();
    Object.assign(axisTextStyle, model.axisTextStyle);
    Object.assign(tickTextStyle, model.tickTextStyle);

    // compute a reasonable font size first
    axisTextStyle.fontSize = Math.max(24 * minAdjust, 12);
    if (helper.getLastAspectRatio() > 1.0) {
      tickTextStyle.fontSize = Math.max(20 * minAdjust, 10);
    } else {
      tickTextStyle.fontSize = Math.max(16 * minAdjust, 10);
    }

    // rebuild the text atlas
    const textSizes = helper.updateTextureAtlas();

    // now compute the boxSize and pixel offsets, different algorithm
    // for horizonal versus vertical
    helper.setTopTitle(false);
    const boxSize = helper.getBoxSizeByReference();

    // if vertical
    if (helper.getLastAspectRatio() > 1.0) {
      helper.setTickLabelPixelOffset(0.3 * tickTextStyle.fontSize);

      // if the title will fit within the width of the bar then that looks
      // nicer to put it at the top (helper.topTitle), otherwise rotate it
      // and place it sideways
      if (textSizes.titleWidth <= textSizes.tickWidth + helper.getTickLabelPixelOffset() + 0.8 * tickTextStyle.fontSize) {
        helper.setTopTitle(true);
        helper.setAxisTitlePixelOffset(0.2 * tickTextStyle.fontSize);
        boxSize[0] = 2.0 * (textSizes.tickWidth + helper.getTickLabelPixelOffset() + 0.8 * tickTextStyle.fontSize) / lastSize[0];
        helper.setBoxPosition([0.98 - boxSize[0], -0.92]);
      } else {
        helper.setAxisTitlePixelOffset(0.2 * tickTextStyle.fontSize);
        boxSize[0] = 2.0 * (textSizes.titleHeight + helper.getAxisTitlePixelOffset() + textSizes.tickWidth + helper.getTickLabelPixelOffset() + 0.8 * tickTextStyle.fontSize) / lastSize[0];
        helper.setBoxPosition([0.99 - boxSize[0], -0.92]);
      }
      boxSize[1] = Math.max(1.2, Math.min(1.84 / yAxisAdjust, 1.84));
    } else {
      // horizontal
      helper.setAxisTitlePixelOffset(1.2 * tickTextStyle.fontSize);
      helper.setTickLabelPixelOffset(0.1 * tickTextStyle.fontSize);
      const titleHeight =
      // total offset from top of bar (includes ticks)
      2.0 * (0.8 * tickTextStyle.fontSize + textSizes.titleHeight + helper.getAxisTitlePixelOffset()) / lastSize[1];
      const tickWidth = 2.0 * textSizes.tickWidth / lastSize[0];
      boxSize[0] = Math.min(1.9, Math.max(1.4, 1.4 * tickWidth * (helper.getTicks().length + 3)));
      boxSize[1] = titleHeight;
      helper.setBoxPosition([-0.5 * boxSize[0], -0.97]);
    }

    // recomute bar segments based on positioning
    helper.recomputeBarSegments(textSizes);
  };
}

// ----------------------------------------------------------------------------
// Default generateTicks function
// ----------------------------------------------------------------------------

// This function returns the default function used to generate vtkScalarBarActor ticks.
// The default function makes use of d3.scaleLinear() to generate 5 tick marks between
// the minimum and maximum values of the scalar bar. Customize this behavior by passing
// a function to vtkScalarBarActor.newInstance({ generateTicks: customGenerateTicks })
// or by calling scalarBarActor.setGenerateTicks(customGenerateTicks).
function defaultGenerateTicks(publicApi, model) {
  return helper => {
    const lastTickBounds = helper.getLastTickBounds();
    const scale = d3.scaleLinear().domain([lastTickBounds[0], lastTickBounds[1]]);
    const ticks = scale.ticks(5);
    const format = scale.tickFormat(5);
    helper.setTicks(ticks);
    helper.setTickStrings(ticks.map(format));
  };
}

// many properties of this actor depend on the API specific view The main
// dependency being the resolution as that drives what font sizes to use.
// Bacause of this we need to do some of the calculations in a API specific
// subclass. But... we don't want a lot of duplicated code between WebGL and
// WebGPU for example so we have this helper class, that is designed to be
// fairly API independent so that API specific views can call this to do
// most of the work.
function vtkScalarBarActorHelper(publicAPI, model) {
  // Set our className
  model.classHierarchy.push('vtkScalarBarActorHelper');
  publicAPI.setRenderable = renderable => {
    if (model.renderable === renderable) {
      return;
    }
    model.renderable = renderable;
    model.barActor.setProperty(renderable.getProperty());
    model.barActor.setParentProp(renderable);
    model.barActor.setCoordinateSystemToDisplay();
    model.tmActor.setProperty(renderable.getProperty());
    model.tmActor.setParentProp(renderable);
    model.tmActor.setCoordinateSystemToDisplay();
    model.generateTicks = renderable.generateTicks;
    model.axisTextStyle = {
      ...renderable.getAxisTextStyle()
    };
    model.tickTextStyle = {
      ...renderable.getTickTextStyle()
    };
    publicAPI.modified();
  };
  publicAPI.updateAPISpecificData = (size, camera, renderWindow) => {
    // has the size changed?
    if (model.lastSize[0] !== size[0] || model.lastSize[1] !== size[1]) {
      model.lastSize[0] = size[0];
      model.lastSize[1] = size[1];
      model.lastAspectRatio = size[0] / size[1];
      model.forceUpdate = true;
    }
    const scalarsToColors = model.renderable.getScalarsToColors();
    if (!scalarsToColors || !model.renderable.getVisibility()) {
      return;
    }

    // make sure the lut is assigned to our mapper
    model.barMapper.setLookupTable(scalarsToColors);

    // camera should be the same for all views
    model.camera = camera;
    model.renderWindow = renderWindow;

    // did something significant change? If so rebuild a lot of things
    if (model.forceUpdate || Math.max(scalarsToColors.getMTime(), publicAPI.getMTime(), model.renderable.getMTime()) > model.lastRebuildTime.getMTime()) {
      const range = scalarsToColors.getMappingRange();
      model.lastTickBounds = [...range];

      // compute tick marks for axes (update for log scale)
      model.renderable.getGenerateTicks()(publicAPI);
      if (model.renderable.getAutomated()) {
        model.renderable.getAutoLayout()(publicAPI);
      } else {
        // copy values from renderable
        model.axisTextStyle = {
          ...model.renderable.getAxisTextStyle()
        };
        model.tickTextStyle = {
          ...model.renderable.getTickTextStyle()
        };
        model.barPosition = [...model.renderable.getBarPosition()];
        model.barSize = [...model.renderable.getBarSize()];
        model.boxPosition = [...model.renderable.getBoxPosition()];
        model.boxSize = [...model.renderable.getBoxSize()];
        model.axisTitlePixelOffset = model.renderable.getAxisTitlePixelOffset();
        model.tickLabelPixelOffset = model.renderable.getTickLabelPixelOffset();

        // rebuild the texture only when force or changed bounds, face
        // visibility changes do to change the atlas
        const textSizes = publicAPI.updateTextureAtlas();

        // recompute bar segments based on positioning
        publicAPI.recomputeBarSegments(textSizes);
      }
      publicAPI.updatePolyDataForLabels();
      publicAPI.updatePolyDataForBarSegments();
      model.lastRebuildTime.modified();
      model.forceUpdate = false;
    }
  };

  // create the texture map atlas that contains the rendering of
  // all the text strings. Only needs to be called when the text strings
  // have changed (labels and ticks)
  publicAPI.updateTextureAtlas = () => {
    // set the text properties
    model.tmContext.textBaseline = 'bottom';
    model.tmContext.textAlign = 'left';

    // return some factors about the text atlas
    const results = {};

    // first the axislabel
    const newTmAtlas = new Map();
    let maxWidth = 0;
    let totalHeight = 1; // start one pixel in so we have a border
    applyTextStyle(model.tmContext, model.axisTextStyle);
    let metrics = model.tmContext.measureText(model.renderable.getAxisLabel());
    let entry = {
      height: metrics.actualBoundingBoxAscent + 2,
      startingHeight: totalHeight,
      width: metrics.width + 2,
      textStyle: model.axisTextStyle
    };
    newTmAtlas.set(model.renderable.getAxisLabel(), entry);
    totalHeight += entry.height;
    maxWidth = entry.width;
    results.titleWidth = entry.width;
    results.titleHeight = entry.height;

    // and the ticks, NaN Below and Above
    results.tickWidth = 0;
    results.tickHeight = 0;
    applyTextStyle(model.tmContext, model.tickTextStyle);
    const strings = [...publicAPI.getTickStrings(), 'NaN', 'Below', 'Above'];
    for (let t = 0; t < strings.length; t++) {
      if (!newTmAtlas.has(strings[t])) {
        metrics = model.tmContext.measureText(strings[t]);
        entry = {
          height: metrics.actualBoundingBoxAscent + 2,
          startingHeight: totalHeight,
          width: metrics.width + 2,
          textStyle: model.tickTextStyle
        };
        newTmAtlas.set(strings[t], entry);
        totalHeight += entry.height;
        if (maxWidth < entry.width) {
          maxWidth = entry.width;
        }
        if (results.tickWidth < entry.width) {
          results.tickWidth = entry.width;
        }
        if (results.tickHeight < entry.height) {
          results.tickHeight = entry.height;
        }
      }
    }

    // always use power of two to avoid interpolation
    // in cases where PO2 is required
    maxWidth = nearestPowerOfTwo(maxWidth);
    totalHeight = nearestPowerOfTwo(totalHeight);

    // set the tcoord values
    newTmAtlas.forEach(value => {
      value.tcoords = [0.0, (totalHeight - value.startingHeight - value.height) / totalHeight, value.width / maxWidth, (totalHeight - value.startingHeight - value.height) / totalHeight, value.width / maxWidth, (totalHeight - value.startingHeight) / totalHeight, 0.0, (totalHeight - value.startingHeight) / totalHeight];
    });

    // make sure we have power of two dimensions
    model.tmCanvas.width = maxWidth;
    model.tmCanvas.height = totalHeight;
    model.tmContext.textBaseline = 'bottom';
    model.tmContext.textAlign = 'left';
    model.tmContext.clearRect(0, 0, maxWidth, totalHeight);

    // draw the text onto the texture
    newTmAtlas.forEach((value, key) => {
      applyTextStyle(model.tmContext, value.textStyle);
      model.tmContext.fillText(key, 1, value.startingHeight + value.height - 1);
    });
    model.tmTexture.setCanvas(model.tmCanvas);
    // mark as modified since the canvas typically doesn't change
    model.tmTexture.modified();
    model._tmAtlas = newTmAtlas;
    return results;
  };
  publicAPI.computeBarSize = textSizes => {
    // compute orientation
    model.vertical = model.boxSize[1] > model.boxSize[0];
    const tickHeight = 2.0 * textSizes.tickHeight / model.lastSize[1];
    const segSize = [1, 1];

    // horizontal and vertical have different astetics so adjust based on
    // orientation
    if (model.vertical) {
      const tickWidth = 2.0 * (textSizes.tickWidth + model.tickLabelPixelOffset) / model.lastSize[0];
      if (model.topTitle) {
        const titleHeight = 2.0 * (textSizes.titleHeight + model.axisTitlePixelOffset) / model.lastSize[1];
        model.barSize[0] = model.boxSize[0] - tickWidth;
        model.barSize[1] = model.boxSize[1] - titleHeight;
      } else {
        // rotated title so width is based off height
        const titleWidth = 2.0 * (textSizes.titleHeight + model.axisTitlePixelOffset) / model.lastSize[0];
        model.barSize[0] = model.boxSize[0] - titleWidth - tickWidth;
        model.barSize[1] = model.boxSize[1];
      }
      model.barPosition[0] = model.boxPosition[0] + tickWidth;
      model.barPosition[1] = model.boxPosition[1];
      segSize[1] = tickHeight;
    } else {
      const tickWidth = (2.0 * textSizes.tickWidth - 8) / model.lastSize[0];
      const titleHeight = 2.0 * (textSizes.titleHeight + model.axisTitlePixelOffset) / model.lastSize[1];
      model.barSize[0] = model.boxSize[0];
      model.barPosition[0] = model.boxPosition[0];
      model.barSize[1] = model.boxSize[1] - titleHeight;
      model.barPosition[1] = model.boxPosition[1];
      segSize[0] = tickWidth;
    }
    return segSize;
  };

  // based on all the settins compute a barSegments array
  // containing the segments of the scalar bar
  // each segment contains
  //   corners[4][2]
  //   title - e.g. NaN, Above, ticks
  //   scalars - the normalized scalars values to use for that segment
  //
  // Note that the bar consumes the space in the box that remains after
  // leaving room for the text labels
  publicAPI.recomputeBarSegments = textSizes => {
    // first compute the barSize/Position
    const segSize = publicAPI.computeBarSize(textSizes);
    model.barSegments = [];
    const startPos = [0.0, 0.0];

    // horizontal and vertical have different astetics so adjust based on
    // orientation
    const barAxis = model.vertical ? 1 : 0;
    const segSpace = model.vertical ? 0.01 : 0.02;
    function pushSeg(title, scalars) {
      model.barSegments.push({
        corners: [[...startPos], [startPos[0] + segSize[0], startPos[1]], [startPos[0] + segSize[0], startPos[1] + segSize[1]], [startPos[0], startPos[1] + segSize[1]]],
        scalars,
        title
      });
      startPos[barAxis] += segSize[barAxis] + segSpace;
    }
    if (model.renderable.getDrawNanAnnotation() && model.renderable.getScalarsToColors().getNanColor()) {
      pushSeg('NaN', [NaN, NaN, NaN, NaN]);
    }
    if (model.renderable.getDrawBelowRangeSwatch() && model.renderable.getScalarsToColors().getUseBelowRangeColor?.()) {
      pushSeg('Below', [-0.1, -0.1, -0.1, -0.1]);
    }
    const haveAbove = model.renderable.getScalarsToColors().getUseAboveRangeColor?.();

    // extra space around the ticks section
    startPos[barAxis] += segSpace;
    const oldSegSize = segSize[barAxis];
    segSize[barAxis] = haveAbove ? 1.0 - 2.0 * segSpace - segSize[barAxis] - startPos[barAxis] : 1.0 - segSpace - startPos[barAxis];
    pushSeg('ticks', model.vertical ? [0, 0, 0.995, 0.995] : [0, 0.995, 0.995, 0]);
    if (model.renderable.getDrawAboveRangeSwatch() && haveAbove) {
      segSize[barAxis] = oldSegSize;
      startPos[barAxis] += segSpace;
      pushSeg('Above', [1.1, 1.1, 1.1, 1.1]);
    }
  };

  // called by updatePolyDataForLabels
  // modifies class constants tmp2v3
  const tmp2v3 = new Float64Array(3);

  // anchor point = pos
  // H alignment = left, middle, right
  // V alignment = bottom, middle, top
  // Text Orientation = horizontal, vertical
  // orientation
  publicAPI.createPolyDataForOneLabel = (text, pos, alignment, orientation, offset, results) => {
    const value = model._tmAtlas.get(text);
    if (!value) {
      return;
    }
    // have to find the four corners of the texture polygon for this label
    let ptIdx = results.ptIdx;
    let cellIdx = results.cellIdx;

    // get achor point in pixels
    tmp2v3[0] = (0.5 * pos[0] + 0.5) * model.lastSize[0];
    tmp2v3[1] = (0.5 * pos[1] + 0.5) * model.lastSize[1];
    tmp2v3[2] = pos[2];
    tmp2v3[0] += offset[0];
    tmp2v3[1] += offset[1];

    // get text size in display pixels
    const textSize = [];
    const textAxes = orientation === 'vertical' ? [1, 0] : [0, 1];
    if (orientation === 'vertical') {
      textSize[0] = value.width;
      textSize[1] = -value.height;
      // update anchor point based on alignment
      if (alignment[0] === 'middle') {
        tmp2v3[1] -= value.width / 2.0;
      } else if (alignment[0] === 'right') {
        tmp2v3[1] -= value.width;
      }
      if (alignment[1] === 'middle') {
        tmp2v3[0] += value.height / 2.0;
      } else if (alignment[1] === 'top') {
        tmp2v3[0] += value.height;
      }
    } else {
      textSize[0] = value.width;
      textSize[1] = value.height;
      // update anchor point based on alignment
      if (alignment[0] === 'middle') {
        tmp2v3[0] -= value.width / 2.0;
      } else if (alignment[0] === 'right') {
        tmp2v3[0] -= value.width;
      }
      if (alignment[1] === 'middle') {
        tmp2v3[1] -= value.height / 2.0;
      } else if (alignment[1] === 'top') {
        tmp2v3[1] -= value.height;
      }
    }
    results.points[ptIdx * 3] = tmp2v3[0];
    results.points[ptIdx * 3 + 1] = tmp2v3[1];
    results.points[ptIdx * 3 + 2] = tmp2v3[2];
    results.tcoords[ptIdx * 2] = value.tcoords[0];
    results.tcoords[ptIdx * 2 + 1] = value.tcoords[1];
    ptIdx++;
    tmp2v3[textAxes[0]] += textSize[0];
    results.points[ptIdx * 3] = tmp2v3[0];
    results.points[ptIdx * 3 + 1] = tmp2v3[1];
    results.points[ptIdx * 3 + 2] = tmp2v3[2];
    results.tcoords[ptIdx * 2] = value.tcoords[2];
    results.tcoords[ptIdx * 2 + 1] = value.tcoords[3];
    ptIdx++;
    tmp2v3[textAxes[1]] += textSize[1];
    results.points[ptIdx * 3] = tmp2v3[0];
    results.points[ptIdx * 3 + 1] = tmp2v3[1];
    results.points[ptIdx * 3 + 2] = tmp2v3[2];
    results.tcoords[ptIdx * 2] = value.tcoords[4];
    results.tcoords[ptIdx * 2 + 1] = value.tcoords[5];
    ptIdx++;
    tmp2v3[textAxes[0]] -= textSize[0];
    results.points[ptIdx * 3] = tmp2v3[0];
    results.points[ptIdx * 3 + 1] = tmp2v3[1];
    results.points[ptIdx * 3 + 2] = tmp2v3[2];
    results.tcoords[ptIdx * 2] = value.tcoords[6];
    results.tcoords[ptIdx * 2 + 1] = value.tcoords[7];
    ptIdx++;

    // add the two triangles to represent the quad
    results.polys[cellIdx * 4] = 3;
    results.polys[cellIdx * 4 + 1] = ptIdx - 4;
    results.polys[cellIdx * 4 + 2] = ptIdx - 3;
    results.polys[cellIdx * 4 + 3] = ptIdx - 2;
    cellIdx++;
    results.polys[cellIdx * 4] = 3;
    results.polys[cellIdx * 4 + 1] = ptIdx - 4;
    results.polys[cellIdx * 4 + 2] = ptIdx - 2;
    results.polys[cellIdx * 4 + 3] = ptIdx - 1;
    results.ptIdx += 4;
    results.cellIdx += 2;
  };

  // update the polydata associated with drawing the text labels
  // specifically the quads used for each label and their associated tcoords
  // etc. This changes every time the camera viewpoint changes
  const tmpv3 = new Float64Array(3);
  publicAPI.updatePolyDataForLabels = () => {
    // update the polydata
    const numLabels = publicAPI.getTickStrings().length + model.barSegments.length;
    const numPts = numLabels * 4;
    const numTris = numLabels * 2;
    const points = new Float64Array(numPts * 3);
    const polys = new Uint16Array(numTris * 4);
    const tcoords = new Float32Array(numPts * 2);
    const results = {
      ptIdx: 0,
      cellIdx: 0,
      polys,
      points,
      tcoords
    };

    // compute the direction vector
    const offsetAxis = model.vertical ? 0 : 1;
    const spacedAxis = model.vertical ? 1 : 0;
    tmpv3[2] = -0.99; // near plane

    // draw the title
    const alignment = model.vertical ? ['right', 'middle'] : ['middle', 'bottom'];
    let dir = [0, 1];
    const tickOffsets = [0, 0];
    if (model.vertical) {
      tickOffsets[0] = -model.tickLabelPixelOffset;
      if (model.topTitle) {
        tmpv3[0] = model.boxPosition[0] + 0.5 * model.boxSize[0];
        tmpv3[1] = model.barPosition[1] + model.barSize[1];

        // write the axis label
        publicAPI.createPolyDataForOneLabel(model.renderable.getAxisLabel(), tmpv3, ['middle', 'bottom'], 'horizontal', [0, model.axisTitlePixelOffset], results);
      } else {
        tmpv3[0] = model.barPosition[0] + model.barSize[0];
        tmpv3[1] = model.barPosition[1] + 0.5 * model.barSize[1];

        // write the axis label
        publicAPI.createPolyDataForOneLabel(model.renderable.getAxisLabel(), tmpv3, ['middle', 'top'], 'vertical', [model.axisTitlePixelOffset, 0], results);
      }
      dir = [-1, 0];
    } else {
      tickOffsets[1] = model.tickLabelPixelOffset;
      tmpv3[0] = model.barPosition[0] + 0.5 * model.barSize[0];
      tmpv3[1] = model.barPosition[1] + model.barSize[1];
      publicAPI.createPolyDataForOneLabel(model.renderable.getAxisLabel(), tmpv3, ['middle', 'bottom'], 'horizontal', [0, model.axisTitlePixelOffset], results);
    }
    tmpv3[offsetAxis] = model.barPosition[offsetAxis] + (0.5 * dir[offsetAxis] + 0.5) * model.barSize[offsetAxis];
    tmpv3[spacedAxis] = model.barPosition[spacedAxis] + model.barSize[spacedAxis] * 0.5;

    // draw bar segment labels
    let tickSeg = null;
    for (let i = 0; i < model.barSegments.length; i++) {
      const seg = model.barSegments[i];
      if (seg.title === 'ticks') {
        // handle ticks below
        tickSeg = seg;
      } else {
        tmpv3[spacedAxis] = model.barPosition[spacedAxis] + 0.5 * model.barSize[spacedAxis] * (seg.corners[2][spacedAxis] + seg.corners[0][spacedAxis]);
        publicAPI.createPolyDataForOneLabel(seg.title, tmpv3, alignment, 'horizontal', tickOffsets, results);
      }
    }

    // write the tick labels
    const tickSegmentStart = model.barPosition[spacedAxis] + model.barSize[spacedAxis] * tickSeg.corners[0][spacedAxis];
    const tickSegmentSize = model.barSize[spacedAxis] * (tickSeg.corners[2][spacedAxis] - tickSeg.corners[0][spacedAxis]);
    const ticks = publicAPI.getTicks();
    const tickStrings = publicAPI.getTickStrings();
    for (let t = 0; t < ticks.length; t++) {
      const tickPos = (ticks[t] - model.lastTickBounds[0]) / (model.lastTickBounds[1] - model.lastTickBounds[0]);
      tmpv3[spacedAxis] = tickSegmentStart + tickSegmentSize * tickPos;
      publicAPI.createPolyDataForOneLabel(tickStrings[t], tmpv3, alignment, 'horizontal', tickOffsets, results);
    }
    const tcoordDA = vtkDataArray.newInstance({
      numberOfComponents: 2,
      values: tcoords,
      name: 'TextureCoordinates'
    });
    model.tmPolyData.getPointData().setTCoords(tcoordDA);
    model.tmPolyData.getPoints().setData(points, 3);
    model.tmPolyData.getPoints().modified();
    model.tmPolyData.getPolys().setData(polys, 1);
    model.tmPolyData.getPolys().modified();
    model.tmPolyData.modified();
  };
  publicAPI.updatePolyDataForBarSegments = () => {
    const scalarsToColors = model.renderable.getScalarsToColors();
    let numberOfExtraColors = 0;
    if (model.renderable.getDrawNanAnnotation() && scalarsToColors.getNanColor()) {
      numberOfExtraColors += 1;
    }
    if (model.renderable.getDrawBelowRangeSwatch() && scalarsToColors.getUseBelowRangeColor?.()) {
      numberOfExtraColors += 1;
    }
    if (model.renderable.getDrawAboveRangeSwatch() && scalarsToColors.getUseAboveRangeColor?.()) {
      numberOfExtraColors += 1;
    }
    const numPts = 4 * (1 + numberOfExtraColors);
    const numQuads = numPts;

    // handle vector component mode
    let numComps = 1;
    if (scalarsToColors.getVectorMode() === VectorMode.COMPONENT) {
      numComps = scalarsToColors.getVectorComponent() + 1;
    }

    // create the colored bars
    const points = new Float64Array(numPts * 3);
    const cells = new Uint16Array(numQuads * 5);
    const scalars = new Float32Array(numPts * numComps);
    let ptIdx = 0;
    let cellIdx = 0;
    for (let i = 0; i < model.barSegments.length; i++) {
      const seg = model.barSegments[i];
      for (let e = 0; e < 4; e++) {
        tmpv3[0] = model.barPosition[0] + seg.corners[e][0] * model.barSize[0];
        tmpv3[1] = model.barPosition[1] + seg.corners[e][1] * model.barSize[1];
        points[ptIdx * 3] = (0.5 * tmpv3[0] + 0.5) * model.lastSize[0];
        points[ptIdx * 3 + 1] = (0.5 * tmpv3[1] + 0.5) * model.lastSize[1];
        points[ptIdx * 3 + 2] = tmpv3[2];
        for (let nc = 0; nc < numComps; nc++) {
          scalars[ptIdx * numComps + nc] = model.lastTickBounds[0] + seg.scalars[e] * (model.lastTickBounds[1] - model.lastTickBounds[0]);
        }
        ptIdx++;
      }
      cells[cellIdx * 5] = 4;
      cells[cellIdx * 5 + 1] = ptIdx - 4;
      cells[cellIdx * 5 + 2] = ptIdx - 3;
      cells[cellIdx * 5 + 3] = ptIdx - 2;
      cells[cellIdx * 5 + 4] = ptIdx - 1;
      cellIdx++;
    }
    const scalarsDA = vtkDataArray.newInstance({
      numberOfComponents: numComps,
      values: scalars,
      name: 'Scalars'
    });
    model.polyData.getPointData().setScalars(scalarsDA);
    model.polyData.getPoints().setData(points, 3);
    model.polyData.getPoints().modified();
    model.polyData.getPolys().setData(cells, 1);
    model.polyData.getPolys().modified();
    model.polyData.modified();
  };
}
const newScalarBarActorHelper = macro.newInstance(function (publicAPI, model) {
  let initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {
    renderable: null
  };
  Object.assign(model, {}, initialValues);

  // Inheritance
  macro.obj(publicAPI, model);
  macro.setGet(publicAPI, model, ['axisTitlePixelOffset', 'tickLabelPixelOffset', 'renderable', 'topTitle', 'ticks', 'tickStrings']);
  macro.get(publicAPI, model, ['lastSize', 'lastAspectRatio', 'lastTickBounds', 'axisTextStyle', 'tickTextStyle', 'barActor', 'tmActor']);
  macro.getArray(publicAPI, model, ['boxPosition', 'boxSize']);
  macro.setArray(publicAPI, model, ['boxPosition', 'boxSize'], 2);
  model.forceUpdate = false;
  model.lastRebuildTime = {};
  macro.obj(model.lastRebuildTime, {
    mtime: 0
  });
  model.lastSize = [-1, -1];
  model.tmCanvas = document.createElement('canvas');
  model.tmContext = model.tmCanvas.getContext('2d');
  model._tmAtlas = new Map();
  model.barMapper = vtkMapper.newInstance();
  model.barMapper.setInterpolateScalarsBeforeMapping(true);
  model.barMapper.setUseLookupTableScalarRange(true);
  model.polyData = vtkPolyData.newInstance();
  model.barMapper.setInputData(model.polyData);
  model.barActor = vtkActor.newInstance();
  model.barActor.setMapper(model.barMapper);

  // for texture atlas
  model.tmPolyData = vtkPolyData.newInstance();
  model.tmMapper = vtkMapper.newInstance();
  model.tmMapper.setInputData(model.tmPolyData);
  model.tmTexture = vtkTexture.newInstance({
    resizable: true
  });
  model.tmTexture.setInterpolate(false);
  model.tmActor = vtkActor.newInstance({
    parentProp: publicAPI
  });
  model.tmActor.setMapper(model.tmMapper);
  model.tmActor.addTexture(model.tmTexture);
  model.barPosition = [0, 0];
  model.barSize = [0, 0];
  model.boxPosition = [0.88, -0.92];
  model.boxSize = [0.1, 1.1];

  // internal variables
  model.lastTickBounds = [];
  vtkScalarBarActorHelper(publicAPI, model);
}, 'vtkScalarBarActorHelper');

//
// Now we define the public class that the application sets view independent
// properties on. This class is fairly small as it mainly just holds
// properties setter and getters leaving all calculations to the helper
// class.
//
function vtkScalarBarActor(publicAPI, model) {
  // Set our className
  model.classHierarchy.push('vtkScalarBarActor');
  publicAPI.setTickTextStyle = tickStyle => {
    model.tickTextStyle = {
      ...model.tickTextStyle,
      ...tickStyle
    };
    publicAPI.modified();
  };
  publicAPI.setAxisTextStyle = axisStyle => {
    model.axisTextStyle = {
      ...model.axisTextStyle,
      ...axisStyle
    };
    publicAPI.modified();
  };
  publicAPI.resetAutoLayoutToDefault = () => {
    publicAPI.setAutoLayout(defaultAutoLayout(publicAPI, model));
  };
  publicAPI.resetGenerateTicksToDefault = () => {
    publicAPI.setGenerateTicks(defaultGenerateTicks());
  };
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

function defaultValues(initialValues) {
  return {
    automated: true,
    autoLayout: null,
    axisLabel: 'Scalar Value',
    barPosition: [0, 0],
    barSize: [0, 0],
    boxPosition: [0.88, -0.92],
    boxSize: [0.1, 1.1],
    scalarToColors: null,
    axisTitlePixelOffset: 36.0,
    axisTextStyle: {
      fontColor: 'white',
      fontStyle: 'normal',
      fontSize: 18,
      fontFamily: 'serif'
    },
    tickLabelPixelOffset: 14.0,
    tickTextStyle: {
      fontColor: 'white',
      fontStyle: 'normal',
      fontSize: 14,
      fontFamily: 'serif'
    },
    generateTicks: null,
    drawNanAnnotation: true,
    drawBelowRangeSwatch: true,
    drawAboveRangeSwatch: true,
    ...initialValues
  };
}

// ----------------------------------------------------------------------------

function extend(publicAPI, model) {
  let initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  Object.assign(model, defaultValues(initialValues));
  if (!model.autoLayout) model.autoLayout = defaultAutoLayout(publicAPI, model);
  if (!model.generateTicks) model.generateTicks = defaultGenerateTicks();

  // Inheritance
  vtkActor.extend(publicAPI, model, initialValues);
  publicAPI.getProperty().setDiffuse(0.0);
  publicAPI.getProperty().setAmbient(1.0);
  macro.setGet(publicAPI, model, ['automated', 'autoLayout', 'axisTitlePixelOffset', 'axisLabel', 'scalarsToColors', 'tickLabelPixelOffset', 'generateTicks', 'drawNanAnnotation', 'drawBelowRangeSwatch', 'drawAboveRangeSwatch']);
  macro.get(publicAPI, model, ['axisTextStyle', 'tickTextStyle']);
  macro.getArray(publicAPI, model, ['barPosition', 'barSize', 'boxPosition', 'boxSize']);
  macro.setArray(publicAPI, model, ['barPosition', 'barSize', 'boxPosition', 'boxSize'], 2);

  // Object methods
  vtkScalarBarActor(publicAPI, model);
}

// ----------------------------------------------------------------------------

const newInstance = macro.newInstance(extend, 'vtkScalarBarActor');

// ----------------------------------------------------------------------------

var vtkScalarBarActor$1 = {
  newInstance,
  extend,
  newScalarBarActorHelper
};

export { vtkScalarBarActor$1 as default, extend, newInstance };
