/******************************************************************************
 * Copyright (C) Leap Motion, Inc. 2011-2017.                                 *
 * Leap Motion proprietary and  confidential.                                 *
 *                                                                            *
 * Use subject to the terms of the Leap Motion SDK Agreement available at     *
 * https://developer.leapmotion.com/sdk_agreement, or another agreement       *
 * between Leap Motion and you, your company or other organization.           *
 ******************************************************************************/

using UnityEngine;
using System.Collections;
using System;
using Leap;

namespace Leap.Unity{
  /** Draws the bones of the finger as a polygonal tube. **/
  public class PolyFinger : FingerModel {

    const int MAX_SIDES = 30;
    const int TRIANGLE_INDICES_PER_QUAD = 6;
    const int VERTICES_PER_QUAD = 4;

    /** The number of sides for finger tube. */
    public int sides = 4;
    /** Whether to smooth the mesh normals, providing a smoother appearance.*/
    public bool smoothNormals = false;
    public float startingAngle = 0.0f;
    /** An array containing the width of the finger at each joint. */
    public float[] widths = new float[NUM_JOINTS];

    protected Vector3[] vertices_;
    protected Vector3[] normals_;
    protected Vector3[] joint_vertices_;

    protected Mesh mesh_;
    protected Mesh cap_mesh_;
    protected Vector3[] cap_vertices_;

    /** Creates the finger mesh. */
    public override void InitFinger() {
      InitJointVertices();
      InitCapsMesh();
      InitMesh();
      GetComponent<MeshFilter>().mesh = new Mesh();

      UpdateFinger();
    }

    /** Updates the mesh.*/
    public override void UpdateFinger() {
      UpdateMesh();
      UpdateCapMesh();

      if (vertices_ == null)
        return;

      mesh_.vertices = vertices_;

      if (smoothNormals)
        mesh_.normals = normals_;
      else
        mesh_.RecalculateNormals();

      cap_mesh_.vertices = cap_vertices_;
      cap_mesh_.RecalculateNormals();

      CombineInstance[] combine = new CombineInstance[2];
      combine[0].mesh = mesh_;
      combine[1].mesh = cap_mesh_;

      GetComponent<MeshFilter>().sharedMesh.CombineMeshes(combine, true, false);
      GetComponent<MeshFilter>().sharedMesh.RecalculateBounds();
    }

    void OnDestroy() {
      Destroy(mesh_);
      Destroy(cap_mesh_);
      Destroy(GetComponent<MeshFilter>().mesh);
    }

    void Update() {

    }

    protected Quaternion GetJointRotation(int joint) {
      if (joint <= 0)
        return GetBoneRotation(joint);
      if (joint >= NUM_BONES)
        return GetBoneRotation(joint - 1);

      return Quaternion.Slerp(GetBoneRotation(joint - 1), GetBoneRotation(joint), 0.5f);
    }

    protected void InitJointVertices() {
      joint_vertices_ = new Vector3[sides];
      for (int s = 0; s < sides; ++s) {
        float angle = startingAngle + s * 360.0f / sides;
        joint_vertices_[s] = Quaternion.AngleAxis(angle, -Vector3.forward) * Vector3.up;
      }
    }

    protected void UpdateMesh() {

      if (joint_vertices_ == null || joint_vertices_.Length != sides) {
        InitJointVertices();
      }
      if (normals_ == null || normals_.Length != VERTICES_PER_QUAD * sides * NUM_BONES
          || vertices_ == null || vertices_.Length != VERTICES_PER_QUAD * sides * NUM_BONES) {
        InitMesh();
      }

      int vertex_index = 0;

      for (int i = 0; i < NUM_BONES; ++i) {
        Vector3 joint_position = transform.InverseTransformPoint(GetJointPosition(i));
        Vector3 next_joint_position = transform.InverseTransformPoint(GetJointPosition(i + 1));
        Quaternion joint_rotation = Quaternion.Inverse(transform.rotation) * (GetJointRotation(i));
        Quaternion next_joint_rotation = Quaternion.Inverse(transform.rotation) *
                                         GetJointRotation(i + 1);

        for (int s = 0; s < sides; ++s) {
          int next_side = (s + 1) % sides;

          if (smoothNormals) {
            Vector3 normal = joint_rotation * joint_vertices_[s];
            Vector3 next_normal = joint_rotation * joint_vertices_[next_side];

            normals_[vertex_index] = normals_[vertex_index + 2] = normal;
            normals_[vertex_index + 1] = normals_[vertex_index + 3] = next_normal;
          }

          Vector3 offset = joint_rotation * (widths[i] * joint_vertices_[s]);
          vertices_[vertex_index++] = joint_position + offset;

          offset = joint_rotation * (widths[i] * joint_vertices_[next_side]);
          vertices_[vertex_index++] = joint_position + offset;

          offset = next_joint_rotation * (widths[i + 1] * joint_vertices_[s]);
          vertices_[vertex_index++] = next_joint_position + offset;

          offset = next_joint_rotation * (widths[i + 1] * joint_vertices_[next_side]);
          vertices_[vertex_index++] = next_joint_position + offset;
        }
      }
    }

    protected void UpdateCapMesh() {
      Vector3 base_position = transform.InverseTransformPoint(GetJointPosition(0));
      Vector3 tip_position = transform.InverseTransformPoint(GetJointPosition(NUM_JOINTS - 1));
      Quaternion base_rotation = Quaternion.Inverse(transform.rotation) * GetJointRotation(0);
      Quaternion tip_rotation = Quaternion.Inverse(transform.rotation) *
                                GetJointRotation(NUM_JOINTS - 1);

      if (cap_vertices_ == null || cap_vertices_.Length != 2 * sides) {
        InitCapsMesh();
      }

      for (int s = 0; s < sides; ++s) {
        cap_vertices_[s] = base_position + base_rotation * (widths[0] * joint_vertices_[s]);
        cap_vertices_[sides + s] = tip_position + tip_rotation *
                                   (widths[NUM_JOINTS - 1] * joint_vertices_[s]);
      }
    }

    protected void InitMesh() {
      mesh_ = new Mesh();
      mesh_.MarkDynamic();

      int vertex_index = 0;
      int num_vertices = VERTICES_PER_QUAD * sides * NUM_BONES;
      vertices_ = new Vector3[num_vertices];
      normals_ = new Vector3[num_vertices];
      Vector2[] uv = new Vector2[num_vertices];

      int triangle_index = 0;
      int num_triangles = TRIANGLE_INDICES_PER_QUAD * sides * NUM_BONES;
      int[] triangles = new int[num_triangles];

      for (int i = 0; i < NUM_BONES; ++i) {
        for (int s = 0; s < sides; ++s) {

          triangles[triangle_index++] = vertex_index;
          triangles[triangle_index++] = vertex_index + 2;
          triangles[triangle_index++] = vertex_index + 1;

          triangles[triangle_index++] = vertex_index + 2;
          triangles[triangle_index++] = vertex_index + 3;
          triangles[triangle_index++] = vertex_index + 1;

          uv[vertex_index] = new Vector3((1.0f * s) / sides, (1.0f * i) / NUM_BONES);
          uv[vertex_index + 1] = new Vector3((1.0f + s) / sides, (1.0f * i) / NUM_BONES);
          uv[vertex_index + 2] = new Vector3((1.0f * s) / sides, (1.0f + i) / NUM_BONES);
          uv[vertex_index + 3] = new Vector3((1.0f + s) / sides, (1.0f + i) / NUM_BONES);

          vertices_[vertex_index++] = new Vector3(0, 0, 0);
          vertices_[vertex_index++] = new Vector3(0, 0, 0);
          vertices_[vertex_index++] = new Vector3(0, 0, 0);
          vertices_[vertex_index++] = new Vector3(0, 0, 0);
        }
      }
      mesh_.vertices = vertices_;
      mesh_.normals = normals_;
      mesh_.uv = uv;
      mesh_.triangles = triangles;
    }

    protected void InitCapsMesh() {
      cap_mesh_ = new Mesh();
      cap_mesh_.MarkDynamic();

      cap_vertices_ = cap_mesh_.vertices;
      int num_vertices = 2 * sides;
      if (num_vertices != cap_vertices_.Length)
        Array.Resize(ref cap_vertices_, num_vertices);

      Vector2[] uv = cap_mesh_.uv;
      if (uv.Length != num_vertices)
        Array.Resize(ref uv, num_vertices);

      int triangle_index = 0;
      int[] triangles = cap_mesh_.triangles;
      int num_triangles = 2 * 3 * (sides - 2);
      if (num_triangles != triangles.Length)
        Array.Resize(ref triangles, num_triangles);

      for (int i = 0; i < sides; ++i) {
        cap_vertices_[i] = new Vector3(0, 0, 0);
        cap_vertices_[i + sides] = new Vector3(0, 0, 0);
        uv[i] = 0.5f * joint_vertices_[i];
        uv[i] += new Vector2(0.5f, 0.5f);
        uv[i + sides] = 0.5f * joint_vertices_[i];
        uv[i + sides] += new Vector2(0.5f, 0.5f);
      }

      for (int i = 0; i < sides - 2; ++i) {
        triangles[triangle_index++] = 0;
        triangles[triangle_index++] = i + 1;
        triangles[triangle_index++] = i + 2;

        triangles[triangle_index++] = sides;
        triangles[triangle_index++] = sides + i + 2;
        triangles[triangle_index++] = sides + i + 1;
      }

      cap_mesh_.vertices = cap_vertices_;
      cap_mesh_.uv = uv;
      cap_mesh_.triangles = triangles;
    }
  }
  
}
