/* $RCSfile$
 * $Author: hansonr $
 * $Date: 2024-10-08 18:58:53 -0500 (Tue, 08 Oct 2024) $
 * $Revision: 22635 $
 *
 * Copyright (C) 2002-2006  Miguel, Jmol Development, www.jmol.org
 *
 * Contact: jmol-developers@lists.sf.net
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package org.jmol.render;

import org.jmol.api.SymmetryInterface;
import org.jmol.script.T;
import org.jmol.shape.Axes;
import org.jmol.util.GData;
import org.jmol.viewer.JC;
import org.jmol.viewer.StateManager;

import javajs.util.P3;


public class AxesRenderer extends CageRenderer {

  private final P3 originScreen = new P3();
  
  private short[] colixes = new short[3];

  private P3 pt000;

  @Override
  protected void initRenderer() {
    endcap = GData.ENDCAPS_FLAT; 
    draw000 = false;
  }

  @Override
  protected boolean render() {
    Axes axes = (Axes) shape;
    int mad10 = vwr.getObjectMad10(StateManager.OBJ_AXIS1);
    boolean isXY = (axes.axisXY.z != 0);
    // no translucent axes
    if (mad10 == 0 || !g3d.checkTranslucent(false))
      return false;
    if (isXY ? exportType == GData.EXPORT_CARTESIAN
        : tm.isNavigating() && vwr.getBoolean(T.navigationperiodic))
      return false;
    // includes check here for background model present
    int modelIndex = vwr.am.cmi;
    if (ms.isJmolDataFrameForModel(modelIndex)
        && !ms.getJmolFrameType(modelIndex).equals("plot data"))
      return false;
    boolean isUnitCell = (vwr.g.axesMode == T.axesunitcell);
    SymmetryInterface unitcell = (isUnitCell ? vwr.getCurrentUnitCell() : null);
    if (isUnitCell && (unitcell == null || modelIndex < 0))
      return false;
    imageFontScaling = vwr.imageFontScaling;
    if (vwr.areAxesTainted())
      axes.reinitShape();
    font3d = vwr.gdata.getFont3DScaled(axes.font3d, imageFontScaling);
    isUnitCell = isUnitCell && (unitcell != null && ms.unitCells != null);
    String axisType = (isUnitCell ? axes.axisType : null);
    boolean isabcxyz = (isXY && isUnitCell && axes.axes2 != null);
    isPolymer = isUnitCell && unitcell.isPolymer();
    isSlab = isUnitCell && unitcell.isSlab();
    nDims = (isUnitCell ? unitcell.getDimensionality() : 3);
    periodicity = (isUnitCell ? unitcell.getPeriodicity() : 0x7);
    setShifts();
    float scale = axes.scale;
    if (isabcxyz) {
      // both abc and xyz
      if ("xyzabc".equals(axes.axes2))
      render1(axes, mad10, false, axisType, isUnitCell, 2, null);
      if (!"abc".equals(axes.axes2))
      vwr.setBooleanProperty("axesmolecular", true);
      axes.reinitShape();
      render1(axes, mad10, true, null, false, scale, axes.axes2);
      vwr.setBooleanProperty("axesunitcell", true);
    } else {
      render1(axes, mad10, isXY, axisType, isUnitCell, scale, null);
    }
    return true;
  }
  
  private final P3 ptTemp = new P3();
  private void render1(Axes axes,int mad10, boolean isXY,
                       String axisType, boolean isUnitCell, float scale, String labels2) {
    boolean isDataFrame = vwr.isJmolDataFrame();
    pt000 = (isDataFrame ? pt0 : axes.originPoint);
    int nPoints = 6;
    int labelPtr = 0;
    if (isUnitCell) {
      nPoints = 3;
      labelPtr = 6;
    } else if (isXY) {
      nPoints = 3;
      labelPtr = 9;
    } else if (vwr.g.axesMode == T.axeswindow) {
      nPoints = 6;
      labelPtr = (vwr.getBoolean(T.axesorientationrasmol) ? 15 : 9);
    }
    if (axes.labels != null) {
      if (nPoints != 3)
        nPoints = (axes.labels.length < 6 ? 3 : 6);
      labelPtr = -1;
    }
    int slab = vwr.gdata.slab;
    int diameter = mad10;
    boolean drawTicks = false;
    ptTemp.setT(originScreen);
    boolean checkAxisType = (labels2 == null && axisType != null && (isXY 
    		|| vwr.getFloat(T.axesoffset) != 0 || axes.fixedOrigin != null));
    if (isXY) {
      if (mad10 >= 20) {
        // width given in angstroms as mAng.
        // max out at 500
        diameter = (mad10 > 500 ? 3 : mad10 / 200);
        if (diameter == 0)
          diameter = 2;
      }
      if (g3d.isAntialiased())
        diameter += diameter;
      g3d.setSlab(0);
      ptTemp.setT(axes.axisXY);
      pt0i.setT(tm.transformPt2D(ptTemp));
      if (ptTemp.x < 0) {
        // window origin
        int offx = (int) ptTemp.x;
        int offy = (int) ptTemp.x;
        // offset is from {0 0 0}
        pointT.setT(pt000);
        for (int i = 0; i < 3; i++)
          pointT.add(axes.getAxisPoint(i, false, ptTemp)); 
        pt0i.setT(tm.transformPt(pt000));
        pt2i.scaleAdd(-1, pt0i, tm.transformPt(pointT));
        if (pt2i.x < 0)
          offx = -offx;
        if (pt2i.y < 0)
          offy = -offy;
        pt0i.x += offx;
        pt0i.y += offy;
      }
      ptTemp.set(pt0i.x, pt0i.y, pt0i.z);
      float zoomDimension = vwr.getScreenDim();
      float scaleFactor = zoomDimension / 10f * scale;
      if (g3d.isAntialiased())
        scaleFactor *= 2;
      if ((isUnitCell || "abc".equals(axes.axes2)) && isXY)
        scaleFactor /= 2;
      for (int i = 0; i < 3; i++) {
        P3 pt = p3Screens[i];
        tm.rotatePoint(axes.getAxisPoint(i, false, pointT), pt);
        pt.z *= -1;
        pt.scaleAdd2(scaleFactor, pt, ptTemp);
      }
    } else {
      // !isXY
      drawTicks = (axes.tickInfos != null);
      if (drawTicks) {
        checkTickTemps();
        tickA.setT(pt000);
      }
      tm.transformPtNoClip(pt000, ptTemp);
      diameter = getDiameter((int) ptTemp.z, mad10);
      for (int i = nPoints; --i >= 0;) {
        P3 p = axes.getAxisPoint(i, !isDataFrame, pointT);
        tm.transformPtNoClip(p, p3Screens[i]);
      }
      if (shifting) {
        if (vvert == null) {
          vvert = new P3[] { new P3(), new P3(), new P3(), new P3(),
              new P3(), new P3() };
        }
        if (shiftA) {
          setVVert(axes, 0);
        }
        if (shiftB) {
          setVVert(axes, 1);
        }
        if (shiftC) {
          setVVert(axes, 2);
        }
      }
    }
    float xCenter = ptTemp.x;
    float yCenter = ptTemp.y;
    colixes[0] = vwr.getObjectColix(StateManager.OBJ_AXIS1);
    colixes[1] = vwr.getObjectColix(StateManager.OBJ_AXIS2);
    colixes[2] = vwr.getObjectColix(StateManager.OBJ_AXIS3);
    boolean showOrigin = (nDims == 3 && !isXY && nPoints == 3
        && (scale == 2 || isUnitCell));
    if (nDims == 2)
      nPoints = 2; // plane group
    for (int i = nPoints; --i >= 0;) {
      if (labels2 != null && i >= labels2.length()
          || checkAxisType && !axisType.contains(JC.axesTypes[i])
          || exportType != GData.EXPORT_CARTESIAN && 
          (Math.abs(xCenter - p3Screens[i].x)
              + Math.abs(yCenter - p3Screens[i].y) <= 2)
          && (!(showOrigin = false)) // setting showOrigin here
      ) {
        continue;
      }
      colix = colixes[i % 3];
      g3d.setC(colix);
      String label = (labels2 != null ? labels2.substring(i, i + 1) 
          : axes.labels == null ? JC.axisLabels[i + labelPtr]
          : i < axes.labels.length ? axes.labels[i] : null);
      if (label != null && label.length() > 0) {
        P3 p = (i == 0 && shiftA ? vvert[1] : i == 1 && shiftB ? vvert[3]
            : i == 2 && shiftC ? vvert[5] : p3Screens[i]);
        renderLabel(label, p.x, p.y, p.z, xCenter, yCenter);
      }
      if (drawTicks) {
        tickInfo = axes.tickInfos[(i % 3) + 1];
        if (tickInfo == null)
          tickInfo = axes.tickInfos[0];
        if (tickInfo != null) {
          tickB.setT(axes.getAxisPoint(i, isDataFrame || isUnitCell, pointT));
          tickInfo.first = 0;
          tickInfo.signFactor = (i % 6 >= 3 ? -1 : 1);
        }
      }      
      // assume slab is a,b and polymer is a only
      int d = (isSlab && i == 2 || isPolymer && i > 0 ? -4 // dashed line
          : diameter);
      P3 p1 = ptTemp;
      P3 p2 = p3Screens[i];
      if (i == 2 && shiftC) {
        p1 = vvert[4];
        p2 = vvert[5];
      } else if (i == 1 && shiftB) {
        p1 = vvert[2];
        p2 = vvert[3];
      } else if (i == 0 && shiftA) {
        p1 = vvert[0];
        p2 = vvert[1];
      }
      renderLine(p1, p2, d, drawTicks && tickInfo != null);
    }
    if (showOrigin) { // a b c [orig]
      String label0 = (axes.labels == null || axes.labels.length == 3
          || axes.labels[3] == null ? "0" : axes.labels[3]);
      if (label0 != null && label0.length() != 0) {
        colix = vwr.cm.colixBackgroundContrast;
        g3d.setC(colix);
        renderLabel(label0, xCenter, yCenter, ptTemp.z, xCenter, yCenter);
      }
    }
    if (isXY)
      g3d.setSlab(slab);
  }
  
  private void setVVert(Axes axes, int i) {
	    int vpt = i * 2;
	    vvert[vpt].setT(axes.originPoint);
	    axes.getAxisPoint(i, true, pt).scale(0.5f);
	    vvert[vpt].sub(pt);
	    pt.add(axes.originPoint);
	    tm.transformPtNoClip(vvert[vpt], vvert[vpt]);
	    tm.transformPtNoClip(pt, vvert[vpt + 1]);
	  }

  private void renderLabel(String str, float x, float y, float z, float xCenter, float yCenter) {
    int strAscent = font3d.getAscent();
    int strWidth = font3d.stringWidth(str);
    float dx = x - xCenter;
    float dy = y - yCenter;
    if ((dx != 0 || dy != 0)) {
      float dist = (float) Math.sqrt(dx * dx + dy * dy);
      dx = (strWidth * 0.75f * dx / dist);
      dy = (strAscent * 0.75f * dy / dist);
      x += dx;
      y += dy;
    }
    double xStrBaseline = Math.floor(x - strWidth / 2f);
    double yStrBaseline = Math.floor(y + strAscent / 2f);
    g3d.drawString(str, font3d, (int) xStrBaseline, (int) yStrBaseline, (int) z, (int) z, (short) 0);
  }
}
