package ip.raul;

import ip.gui.dialog.DoubleLog;
import ip.gui.frames.ImageFrame;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.ColorModel;


public class ObjectView extends ImageFrame
    implements MouseListener, MouseMotionListener, WindowListener {

  MenuBar mb = new MenuBar();
  Menu SettingsMenu = new Menu("Settings");
  Menu ProjectionMenu = new Menu("Projection");
  Menu ParallelProjectionMenu = new Menu("Parallel");
  Menu PerspectiveProjectionMenu = new Menu("Perspective");
  Menu AxonometricParallelProjectionMenu = new Menu("Axonometric");

  MenuItem wireframe_mi = addMenuItem(SettingsMenu, "[w]ireframe...");
  MenuItem textured_mi = addMenuItem(SettingsMenu, "[t]extured...");

  MenuItem orthometric_mi = addMenuItem(ParallelProjectionMenu, "[1] Orthometric");
  MenuItem oblique_mi = addMenuItem(ParallelProjectionMenu, "[2] Oblique");
  MenuItem trimetric_mi = addMenuItem(AxonometricParallelProjectionMenu, "[1] Trimetric");
  MenuItem dimetric_mi = addMenuItem(AxonometricParallelProjectionMenu, "[2] Dimetric");
  MenuItem isometric_mi = addMenuItem(AxonometricParallelProjectionMenu, "[3] Isometric");

  MenuItem _1point_mi = addMenuItem(PerspectiveProjectionMenu, "[1] 1 Point ");
  MenuItem _2point_mi = addMenuItem(PerspectiveProjectionMenu, "[2] 2 Point ");
  MenuItem _3point_mi = addMenuItem(PerspectiveProjectionMenu, "[3] 3 Point ");


  private short rn[][] = new short[0][0];
  private short gn[][] = new short[0][0];
  private short bn[][] = new short[0][0];
  private ColorModel cm = ColorModel.getRGBdefault();
  public Object3D objects[];
  public int maxObjects;
  public String textures[];
  public int maxTextures;
  public int view;
  public int projection = 1;
  public int mode = 1;
  public int index = -1;
  public boolean hidden = true;
  public boolean busy = false;
  private Image img = null;
  private Image img1 = null;
  private Graphics imgG = null;

//projection related variables
  private double theta = 0;
  private double thetaC = 0;
  private double thetaS = 0;
  private double phi = 0;
  private double phiC = 0;
  private double phiS = 0;
  private double xProj = 1;
  private double yProj = 1;
  private double zProj = 1;
  private double depthFactor = 1;
  private DoubleLog DLog = null;
//until here
  private float xCenter = 150;
  private float yCenter = 150;
  private float xK = 100;//the default scaling factors, so that domains of -2..2 will show big enough
  private float yK = 100;//same thing

  public void projectionEvent(ActionEvent e) {
    try {
      Button b = (Button) e.getSource();
      if (b == DLog.setButton) {
        double d[] = DLog.getUserInputAsDouble();
        if (projection == 2) {
          theta = (double) (d[0] * Math.PI / 180d);
          phi = (double) (d[1] * Math.PI / 180d);
          thetaC = Math.cos(theta);
          thetaS = Math.sin(theta);
          phiC = Math.cos(phi);
          phiS = Math.sin(phi);
        }
        if (projection == 3) {
          phi = (double) (45 * Math.PI / 180d);
          phiC = Math.cos(phi);
          phiS = Math.sin(phi);
          theta = (double) (d[0] * Math.PI / 180d);
          thetaC = Math.cos(theta);
          thetaS = Math.sin(theta);
        }
        if (projection == 4) {
          switch ((int) d[0]) {
            case 1:
              theta = (double) (-35d * Math.PI / 180d);
              phi = (double) (45d * Math.PI / 180d);
              thetaC = Math.cos(theta);
              thetaS = Math.sin(theta);
              phiC = Math.cos(phi);
              phiS = Math.sin(phi);
              break;
            case 2:
              theta = (double) (35d * Math.PI / 180d);
              phi = (double) (-45d * Math.PI / 180d);
              thetaC = Math.cos(theta);
              thetaS = Math.sin(theta);
              phiC = Math.cos(phi);
              phiS = Math.sin(phi);
              break;
            case 3:
              theta = (double) (35d * Math.PI / 180d);
              phi = (double) (45d * Math.PI / 180d);
              thetaC = Math.cos(theta);
              thetaS = Math.sin(theta);
              phiC = Math.cos(phi);
              phiS = Math.sin(phi);
              break;
            case 4:
              theta = (double) (-35d * Math.PI / 180d);
              phi = (double) (-45d * Math.PI / 180d);
              thetaC = Math.cos(theta);
              thetaS = Math.sin(theta);
              phiC = Math.cos(phi);
              phiS = Math.sin(phi);
              break;
            default:
              System.out.println("Your option was not understood, 1 assumed");
              theta = (double) (-35d * Math.PI / 180d);
              phi = (double) (45d * Math.PI / 180d);
              thetaC = Math.cos(theta);
              thetaS = Math.sin(theta);
              phiC = Math.cos(phi);
              phiS = Math.sin(phi);
              break;
          }
        }
        if ((projection == 6)) {
          if (d[0] != 0) zProj = d[0];
          yProj = 1;
          xProj = 1;
        }
        if ((projection == 7)) {
          if (d[0] != 0) xProj = d[0];
          if (d[1] != 0) zProj = d[1];
          yProj = 1;
        }
        if ((projection == 8)) {
          if (d[0] != 0) xProj = d[0];
          if (d[1] != 0) yProj = d[1];
          if (d[2] != 0) zProj = d[2];
        }
        repaint();
      }
    } catch (Exception ex) {
    }

    if (match(e, orthometric_mi)) {
      projection = 1;
      repaint();
      return;
    }
    if (match(e, trimetric_mi)) {
      trimetricView();
      return;
    }
    if (match(e, dimetric_mi)) {
      dimetricView();
      return;
    }
    if (match(e, isometric_mi)) {
      isometricView();
      return;
    }
    if (match(e, oblique_mi)) {
      projection = 5;
      repaint();
      return;
    }
    if (match(e, _1point_mi)) {
      _1pointView();
      return;
    }
    if (match(e, _2point_mi)) {
      _2pointView();
      return;
    }
    if (match(e, _3point_mi)) {
      _3pointView();
      return;
    }
  }

  public void trimetricView() {
    String prompts[] = {"Theta (degrees)", "Phi (degrees)"};
    String defaults[] = {"0.0", "0.0"};
    DLog = new DoubleLog(this,
                         "Trimetric Projection Dialog", prompts, defaults, 6);
    DLog.setVisible(true);
    DLog.setButton.addActionListener(this);
    projection = 2;
    repaint();
  }

  public void dimetricView() {
    String prompts[] = {"Theta (degrees)"};
    String defaults[] = {"0.0"};
    DLog = new DoubleLog(this,
                         "Dimetric Projection Dialog", prompts, defaults, 6);
    DLog.setVisible(true);
    DLog.setButton.addActionListener(this);
    projection = 3;
    repaint();
  }

  public void isometricView() {
    System.out.println("Please select from the dialog one of the values:");
    System.out.println("1: Theta=-45, Phi=35");
    System.out.println("2: Theta=45, Phi=-35");
    System.out.println("3: Theta=45, Phi=35");
    System.out.println("4: Theta=-45, Phi=-35");
    String prompts[] = {"Choose 1, 2, 3 or 4"};
    String defaults[] = {"1"};
    DLog = new DoubleLog(this,
                         "Isometric Projection Dialog", prompts, defaults, 6);
    DLog.setVisible(true);
    DLog.setButton.addActionListener(this);
    projection = 4;
    repaint();
  }

  public void _1pointView() {
    String prompts[] = {"Z = "};
    String defaults[] = {"1.0"};
    DLog = new DoubleLog(this,
                         "1 Point Projection Dialog", prompts, defaults, 6);
    DLog.setVisible(true);
    DLog.setButton.addActionListener(this);
    projection = 6;
    repaint();
  }

  public void _2pointView() {
    String prompts[] = {"X = ", "Z = "};
    String defaults[] = {"1.0", "1.0"};
    DLog = new DoubleLog(this,
                         "2 Point Projection Dialog", prompts, defaults, 6);
    DLog.setVisible(true);
    DLog.setButton.addActionListener(this);
    projection = 7;
    repaint();
  }

  public void _3pointView() {
    String prompts[] = {"X = ", "Y = ", "Z = "};
    String defaults[] = {"1.0", "1.0", "1.0"};
    DLog = new DoubleLog(this,
                         "3 Point Projection Dialog", prompts, defaults, 6);
    DLog.setVisible(true);
    DLog.setButton.addActionListener(this);
    projection = 8;
    repaint();
  }

  public void actionPerformed(ActionEvent e) {
    projectionEvent(e);
    if (match(e, wireframe_mi)) {
      mode = 1;
      repaint();
      return;
    }
    if (match(e, textured_mi)) {
      mode = 2;
      repaint();
      return;
    }
    super.actionPerformed(e);
  }

  ObjectView(String title, Object3D _objects[], int _maxObjects, String _textures[], int _maxTextures, int _view) {
    super(title);
    objects = _objects;
    textures = _textures;
    maxTextures = _maxTextures;
    maxObjects = _maxObjects;
    view = _view;
    addMouseListener(this);
    addMouseMotionListener(this);
    addWindowListener(this);

    ProjectionMenu.add(ParallelProjectionMenu);
    ProjectionMenu.add(PerspectiveProjectionMenu);
    ParallelProjectionMenu.add(AxonometricParallelProjectionMenu);
    mb.add(SettingsMenu);
    mb.add(ProjectionMenu);
    setMenuBar(mb);
    setSize(300, 300);
    hidden = false;

  }

  private void init() {
  }

  public void update(Graphics g) {
    paint(g);
  }


  private void Line3D(Point3D p1, Point3D p2, Graphics g) {
    Point q1 = get2Dfrom3D(p1);
    Point q2 = get2Dfrom3D(p2);
    g.drawLine(q1.x, q1.y, q2.x, q2.y);
  }

  private void setPoint3D(Point3D p, short r1, short g1, short b1, Graphics g) {
    Point q = get2Dfrom3D(p);
    g.setColor(new Color(r1, g1, b1));
    g.drawLine(q.x, q.y, q.x, q.y);
  }

  int Dist(Point a, Point b) {
    return (int) (Math.sqrt(((b.x - a.x) * (b.x - a.x)) + ((b.y - a.y) * (b.y - a.y))));
  }

  private Point3D getProjection(Point3D p) {
    float tmp = 0;
    Point3D _p = new Point3D(0, 0, 0);
    switch (projection) {
      case 1:
        _p = p;
        break;
      case 2:
        _p = p;
        if (thetaS != 0) {
          _p.x = p.x + (float) (p.z * phiC * thetaC / thetaS);
          _p.y = p.y + (float) (p.z * phiS * thetaC / thetaS);
        }
        break;
      case 3:
        _p = p;
        if (thetaS != 0) {
          _p.x = p.x + (float) (p.z * phiC * thetaC / thetaS);
          _p.y = p.y + (float) (p.z * phiS * thetaC / thetaS);
        }
        break;
      case 4:
        _p = p;
        if (thetaS != 0) {
          _p.x = p.x + (float) (p.z * phiC * thetaC / thetaS);
          _p.y = p.y + (float) (p.z * phiS * thetaC / thetaS);
        }
        break;
      case 5:
        break;
      case 6:
        depthFactor = -(p.z / zProj) + 1;
        _p.x = (float) (p.x / depthFactor);
        _p.y = (float) (p.y / depthFactor);
        _p.z = p.z;
        break;
      case 7:
        depthFactor = (-p.x / xProj) - (p.z / zProj) + 1;
        _p.x = (float) (p.x / depthFactor);
        _p.y = (float) (p.y / depthFactor);
        _p.z = p.z;
        break;
      case 8:
        depthFactor = -(p.x / xProj) - (p.y / yProj) - (p.z / zProj) + 1;
        _p.x = (float) (p.x / depthFactor);
        _p.y = (float) (p.y / depthFactor);
        _p.z = p.z;
        break;
    }
    return (_p);
  }

  private Point get2Dfrom3D(Point3D p) {
    Point3D pp = new Point3D(0, 0, 0);
    switch (view) {
      case 1://front
        pp.x = p.x;
        pp.y = -p.z;
        pp.z = p.y;
        break;
      case 2://back
        pp.x = -p.x;
        pp.y = -p.z;
        pp.z = -p.y;
        break;
      case 3://top
        pp.x = p.x;
        pp.y = -p.y;
        pp.z = p.z;
        break;
      case 4://bottom
        pp.x = p.x;
        pp.y = p.y;
        pp.z = -p.z;
        break;
      case 5://left
        pp.x = -p.y;
        pp.y = -p.z;
        pp.z = p.x;
        break;
      case 6://right
        pp.x = p.y;
        pp.y = -p.z;
        pp.z = -p.x;
        break;
    }
    pp = getProjection(pp);
    pp.x = xCenter + pp.x * xK;
    pp.y = yCenter + pp.y * yK;
    return new Point((int) pp.x, (int) pp.y);
  }

  private Point3D get3Dfrom2D(Point q) {
    float xx1 = 0;
    float yy1 = 0;
    float zz1 = 0;
    float x = 0;
    float y = 0;
    x = q.x;
    y = q.y;
    x = x - xCenter;
    y = y - yCenter;
    x = (float) (x / xK);
    y = (float) (y / yK);
    switch (view) {
      case 1://front
        xx1 = x;
        zz1 = -y;
        break;
      case 2://back
        xx1 = -x;
        zz1 = -y;
        break;
      case 3://top
        xx1 = x;
        yy1 = -y;
        break;
      case 4://bottom
        xx1 = x;
        yy1 = y;
        break;
      case 5://left
        yy1 = -x;
        zz1 = -y;
        break;
      case 6://right
        yy1 = x;
        zz1 = -y;
        break;
    }
    return new Point3D(xx1, yy1, zz1);
  }

  private void drawSphere(Object3D o, Graphics g1, int mode) {
    float Radius = o.Radius;
    float deltaTheta = (float) ((float) Math.PI / 9f);
    float sini, cosi, sinj, cosj;
    Point3D s[][] = new Point3D[18][18];
    switch (mode) {
      case 1:
        for (int i = 0; i < 18; i++)
          for (int j = -8; j < 10; j++) {
            sini = (float) Math.sin((float) (i * deltaTheta));
            cosi = (float) Math.cos((float) (i * deltaTheta));
            sinj = (float) Math.sin((float) (j * deltaTheta));
            cosj = (float) Math.cos((float) (j * deltaTheta));
            float x = o.Pos.x + (float) (Radius * cosi * sinj);
            float y = o.Pos.y + (float) (Radius * sini * sinj);
            float z = o.Pos.z + (float) (Radius * cosj);
            s[i][j + 8] = new Point3D(x, y, z);
          }
        for (int i = 0; i < 17; i++)
          for (int j = 0; j < 17; j++) {
            Line3D(s[i][j], s[i + 1][j], g1);
            Line3D(s[i + 1][j], s[i + 1][j + 1], g1);
          }
        break;
      case 2:
        break;
    }
  }


  private void drawCylinder(Object3D o, Graphics g1, int mode) {
    float Radius = o.Radius;
    float Height = o.Height;
    float deltaTheta = (float) ((float) Math.PI / 8.5f);
    float sini, cosi;
    Point3D s[][] = new Point3D[18][10];
    switch (mode) {
      case 1:
        for (int i = 0; i < 18; i++)
          for (int j = 0; j < 10; j++) {
            sini = (float) Math.sin((float) (i * deltaTheta));
            cosi = (float) Math.cos((float) (i * deltaTheta));
            float x = o.Pos.x + (float) (Radius * cosi);
            float y = o.Pos.y + (float) (Height * j / 9f);
            float z = o.Pos.z + (float) (Radius * sini);
            s[i][j] = new Point3D(x, y, z);
          }
        for (int i = 0; i < 17; i++) {
          for (int j = 0; j < 9; j++) {
            Line3D(s[i][j], s[i + 1][j], g1);
            Line3D(s[i + 1][j], s[i + 1][j + 1], g1);
          }
          Line3D(s[i][9], s[i + 1][9], g1);
        }
        break;
      case 2:
        break;
    }
  }

  private void drawParalelipiped(Object3D o, Graphics g1, int mode) {
    float W = o.Width;
    float H = o.Height;
    float L = o.Length;
    float x, y, z;
    Point3D s[][] = new Point3D[4][2];
    switch (mode) {
      case 1:
        x = o.Pos.x - (float) (W / 2f);
        y = o.Pos.y - (float) (H / 2f);
        z = o.Pos.z - (float) (L / 2f);
        s[0][0] = new Point3D(x, y, z);
        x = o.Pos.x + (float) (W / 2f);
        s[1][0] = new Point3D(x, y, z);
        z = o.Pos.z + (float) (L / 2f);
        s[2][0] = new Point3D(x, y, z);
        x = o.Pos.x - (float) (W / 2f);
        s[3][0] = new Point3D(x, y, z);

        y = o.Pos.y + (float) (H / 2f);
        s[3][1] = new Point3D(x, y, z);
        x = o.Pos.x + (float) (W / 2f);
        s[2][1] = new Point3D(x, y, z);
        z = o.Pos.z - (float) (L / 2f);
        s[1][1] = new Point3D(x, y, z);
        x = o.Pos.x - (float) (W / 2f);
        s[0][1] = new Point3D(x, y, z);
        //upper face
        Line3D(s[0][0], s[1][0], g1);
        Line3D(s[1][0], s[2][0], g1);
        Line3D(s[2][0], s[3][0], g1);
        Line3D(s[3][0], s[0][0], g1);
        //down face
        Line3D(s[0][1], s[1][1], g1);
        Line3D(s[1][1], s[2][1], g1);
        Line3D(s[2][1], s[3][1], g1);
        Line3D(s[3][1], s[0][1], g1);
        //lateral stuff
        Line3D(s[0][0], s[0][1], g1);
        Line3D(s[1][0], s[1][1], g1);
        Line3D(s[2][0], s[2][1], g1);
        Line3D(s[3][0], s[3][1], g1);
        break;
      case 2:
        break;
    }
  }


  private void drawAxes(Graphics g1, Rectangle r) {
    switch (view) { //Axes
      case 1: //front
        g1.drawLine(30, r.height - 30, 20, r.height - 20);
        g1.drawLine(20, r.height - 20, 20, r.height - 30);
        g1.drawLine(20, r.height - 20, 30, r.height - 20);
        g1.drawString("z", 18, r.height - 32);
        g1.drawString("x", 32, r.height - 18);
        g1.drawString("y", 32, r.height - 32);
        break;
      case 2://back
        g1.drawLine(12, r.height - 12, 20, r.height - 20);
        g1.drawLine(20, r.height - 20, 20, r.height - 30);
        g1.drawLine(20, r.height - 20, 10, r.height - 20);
        g1.drawString("z", 18, r.height - 32);
        g1.drawString("x", 6, r.height - 18);
        g1.drawString("y", 6, r.height - 6);
        break;
      case 3://top
        g1.drawLine(12, r.height - 12, 20, r.height - 20);
        g1.drawLine(20, r.height - 20, 20, r.height - 30);
        g1.drawLine(20, r.height - 20, 30, r.height - 20);
        g1.drawString("y", 18, r.height - 32);
        g1.drawString("x", 32, r.height - 18);
        g1.drawString("z", 6, r.height - 6);
        break;
      case 4://bottom
        g1.drawLine(30, r.height - 30, 20, r.height - 20);
        g1.drawLine(20, r.height - 20, 20, r.height - 10);
        g1.drawLine(20, r.height - 20, 30, r.height - 20);
        g1.drawString("y", 22, r.height - 6);
        g1.drawString("x", 32, r.height - 18);
        g1.drawString("z", 32, r.height - 32);
        break;
      case 5://left
        g1.drawLine(30, r.height - 30, 20, r.height - 20);
        g1.drawLine(20, r.height - 20, 20, r.height - 30);
        g1.drawLine(20, r.height - 20, 10, r.height - 20);
        g1.drawString("z", 18, r.height - 32);
        g1.drawString("y", 6, r.height - 18);
        g1.drawString("x", 32, r.height - 32);
        break;
      case 6://right
        g1.drawLine(12, r.height - 12, 20, r.height - 20);
        g1.drawLine(20, r.height - 20, 20, r.height - 30);
        g1.drawLine(20, r.height - 20, 30, r.height - 20);
        g1.drawString("z", 18, r.height - 32);
        g1.drawString("y", 32, r.height - 18);
        g1.drawString("x", 6, r.height - 6);
        break;
    }
    switch (projection) {
      case 1:
        g1.drawString("orthometric", 40, r.height - 16);
        break;
      case 2:
        g1.drawString("trimetric", 40, r.height - 16);
        break;
      case 3:
        g1.drawString("dimetric", 40, r.height - 16);
        break;
      case 4:
        g1.drawString("isometric", 40, r.height - 16);
        break;
      case 5:
        g1.drawString("oblique", 40, r.height - 16);
        break;
      case 6:
        g1.drawString("1 point", 40, r.height - 16);
        break;
      case 7:
        g1.drawString("2 point", 40, r.height - 16);
        break;
      case 8:
        g1.drawString("3 point", 40, r.height - 16);
        break;
    }
  }

  double linearY(double x1, double x2, double t) {
    double dx = 0;
    dx = (double) (x2 - x1);
    return (double) (x1 + (double) (dx * t));
  }

  public void paint(Graphics g) {
    Rectangle r = getBounds();
    img = createImage(r.width, r.height);
    imgG = img.getGraphics();

    xCenter = (float) (r.width / 2);
    yCenter = (float) (r.height / 2);
    xK = (float) (r.width / 3);
    yK = (float) (r.height / 3);
    for (int i = 0; i <= maxObjects; i++) {
      if ((i == index) && (busy))
        imgG.setColor(new Color(255, 0, 0));
      else
        imgG.setColor(new Color(0, 0, 0));
      switch (objects[i].Type) {
        case 1:
          drawSphere(objects[i], imgG, mode);
          break;
        case 2:
          drawCylinder(objects[i], imgG, mode);
          break;
        case 3:
          drawParalelipiped(objects[i], imgG, mode);
          break;
      }
    }
    imgG.setColor(new Color(0, 0, 0));
    drawAxes(imgG, r);
    if (img != null) {
      g.drawImage(img, 0, 0, r.width, r.height, this);
    }
  }

  public void mouseDragged(MouseEvent e) {
    e.consume();
    double dx = getX(e);
    double dy = getY(e);
    double sx = 0;
    double sy = 0;
    double ray = 100000;
    if (!busy) {
      busy = true;
      index = -1;
      for (int i = 0; i <= maxObjects; i++) {
        Point s = get2Dfrom3D(objects[i].Pos);
        sx = (double) s.x;
        sy = (double) s.y;
        sx = sx - dx;
        sx = sx * sx;
        sy = sy - dy;
        sy = sy * sy;
        if ((sx + sy) < ray) {
          ray = sx + sy;
          index = i;
        }
      }
    }
    if (index != -1) {
      objects[index].Pos = get3Dfrom2D(new Point((int) dx, (int) dy));
      repaint();
      return;
    }
  }

  private int getX(MouseEvent e) {
    return (int) (e.getX());
  }

  private int getY(MouseEvent e) {
    return (int) (e.getY());
  }

  public void mouseReleased(MouseEvent e) {
    busy = false;
    repaint();
  }

  public void mousePressed(MouseEvent e) {
  }

  public void mouseExited(MouseEvent e) {
  }

  public void mouseEntered(MouseEvent e) {
  }

  public void mouseClicked(MouseEvent e) {
  }


  public void mouseMoved(MouseEvent e) {
  }

  public void windowClosing(WindowEvent e) {
    hidden = true;
    dispose();
  }

  public void windowClosed(WindowEvent e) {
  };
  public void windowDeiconified(WindowEvent e) {
  };
  public void windowIconified(WindowEvent e) {
  };
  public void windowActivated(WindowEvent e) {
  };
  public void windowDeactivated(WindowEvent e) {
  };
  public void windowOpened(WindowEvent e) {
  };

}