/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

package com.facebook.react.views.art;

import javax.annotation.Nullable;

import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;

import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.DisplayMetricsHolder;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.ReactShadowNode;

/**
 * Base class for ARTView virtual nodes: {@link ARTGroupShadowNode}, {@link ARTShapeShadowNode} and
 * indirectly for {@link ARTTextShadowNode}.
 */
public abstract class ARTVirtualNode extends ReactShadowNode {

  protected static final float MIN_OPACITY_FOR_DRAW = 0.01f;

  private static final float[] sMatrixData = new float[9];
  private static final float[] sRawMatrix = new float[9];

  protected float mOpacity = 1f;
  private @Nullable Matrix mMatrix = new Matrix();

  protected final float mScale;

  public ARTVirtualNode() {
    mScale = DisplayMetricsHolder.getWindowDisplayMetrics().density;
  }

  @Override
  public boolean isVirtual() {
    return true;
  }

  public abstract void draw(Canvas canvas, Paint paint, float opacity);

  /**
   * Sets up the transform matrix on the canvas before an element is drawn.
   *
   * NB: for perf reasons this does not apply opacity, as that would mean creating a new canvas
   * layer (which allocates an offscreen bitmap) and having it composited afterwards. Instead, the
   * drawing code should apply opacity recursively.
   *
   * @param canvas the canvas to set up
   */
  protected final void saveAndSetupCanvas(Canvas canvas) {
    canvas.save();
    if (mMatrix != null) {
      canvas.concat(mMatrix);
    }
  }

  /**
   * Restore the canvas after an element was drawn. This is always called in mirror with
   * {@link #saveAndSetupCanvas}.
   *
   * @param canvas the canvas to restore
   */
  protected void restoreCanvas(Canvas canvas) {
    canvas.restore();
  }

  @ReactProp(name = "opacity", defaultFloat = 1f)
  public void setOpacity(float opacity) {
    mOpacity = opacity;
    markUpdated();
  }

  @ReactProp(name = "transform")
  public void setTransform(@Nullable ReadableArray transformArray) {
    if (transformArray != null) {
      int matrixSize = PropHelper.toFloatArray(transformArray, sMatrixData);
      if (matrixSize == 6) {
        setupMatrix();
      } else if (matrixSize != -1) {
        throw new JSApplicationIllegalArgumentException("Transform matrices must be of size 6");
      }
    } else {
      mMatrix = null;
    }
    markUpdated();
  }

  protected void setupMatrix() {
    sRawMatrix[0] = sMatrixData[0];
    sRawMatrix[1] = sMatrixData[2];
    sRawMatrix[2] = sMatrixData[4] * mScale;
    sRawMatrix[3] = sMatrixData[1];
    sRawMatrix[4] = sMatrixData[3];
    sRawMatrix[5] = sMatrixData[5] * mScale;
    sRawMatrix[6] = 0;
    sRawMatrix[7] = 0;
    sRawMatrix[8] = 1;
    if (mMatrix == null) {
      mMatrix = new Matrix();
    }
    mMatrix.setValues(sRawMatrix);
  }
}
