using UnityEngine;

using System.Collections;

public class BodyDeform : MonoBehaviour {

    float lowerbody = 1f;
    public float Lowerbody = 1f;

    float upperbody = 1f;
    public float Upperbody = 1f;

    float fatness = 1f;
    public float Fatness = 1f;

    float height = 1f;
    public float Height = 1f;

    float shoulders = 1f;
    public float Shoulders = 1f;

    float head = 1f;
    public float Head = 1f;

    Mesh mesh = null;
    Matrix4x4[] original = null;
    Matrix4x4[] poses = null;
    Matrix4x4[] bonepos = null;
    Matrix4x4 localToWorld = Matrix4x4.identity;

    // Use this for initialization
    void Awake () {
        SkinnedMeshRenderer smr = GetComponent<SkinnedMeshRenderer>();
        mesh = smr.sharedMesh;

        poses = new Matrix4x4[mesh.bindposes.Length];
        original = new Matrix4x4[mesh.bindposes.Length];
        bonepos = new Matrix4x4[mesh.bindposes.Length];
        for (int i = 0; i < mesh.bindposes.Length; i++) {
            bonepos[i] = smr.bones[i].worldToLocalMatrix;
        }
        localToWorld = transform.localToWorldMatrix;

        mesh = new Mesh();
        smr.sharedMesh = this.mesh;

        //SetMesh(smr.sharedMesh);
    }

    void OnApplicationQuit() {
        if (this.mesh != null)
            mesh.bindposes = original;
    }

    public void SetMesh (Mesh mesh) {
        if (this.mesh != null) {
            //Destroy(this.mesh);
            //this.mesh.bindposes = original;
        }

        this.mesh.vertices = mesh.vertices;
        this.mesh.triangles = mesh.triangles;
        this.mesh.uv = mesh.uv;
        this.mesh.colors = mesh.colors;
        this.mesh.bindposes = mesh.bindposes;
        this.mesh.boneWeights = mesh.boneWeights;
        this.mesh.colors32 = mesh.colors32;
        this.mesh.normals = mesh.normals;

        SkinnedMeshRenderer smr = GetComponent<SkinnedMeshRenderer>();
        smr.sharedMesh = this.mesh;
        //smr.sharedMesh = this.mesh = Instantiate(mesh) as Mesh;

        //AssetDatabase.CreateAsset(this.mesh, AssetDatabase.GetAssetPath(mesh) +  copy.asset);

        Debug.Log(Bind Poses:  + this.mesh.bindposes.Length);

        //Debug.Log(this.name);

        for (int i = 0; i < this.mesh.bindposes.Length; i++) {
            poses[i] = mesh.bindposes[i];
            original[i] = mesh.bindposes[i];
        }

        ApplyMeshDeform();
    }

    void ApplyMeshDeform () {

        height = Mathf.Clamp(Height, 0.2f, 5f);
        fatness = Mathf.Clamp(Fatness, 0.8f, 2.0f);
        float dFat = fatness  1f;
        lowerbody = Mathf.Clamp(Lowerbody * fatness, 0.6f, 1.6f);
        upperbody = Mathf.Clamp(Upperbody * fatness, 0.6f, 2.0f);
        shoulders = Mathf.Clamp(Shoulders, 0.8f, 1.4f);
        head = Mathf.Clamp(Head, 0.5f, 01.5f);

        transform.parent.localScale = Vector3.one * height;

        UpdateSkeleton();
    }

    void UpdateSkeleton() { 
        float delta = 1f;
        // LOWER BODY (up, wide, out)
        delta = lowerbody  1f;
        SetSkin(LegUpper, new Vector3(1f, 1f+delta*0.8f, 1f+delta*0.8f));
        SetSkin(LegLower, new Vector3(1f, 1f+delta*0.6f, 1f+delta*0.6f));
        SetSkin(Hip, new Vector3(1f, 1f+delta*0.6f, 1f+hippiness*0.6f));

        // UPPER BODY (up, wide, out)
        delta = upperbody  1f;
        SetSkin(ArmUpper, new Vector3(1f, 1f+delta, 1f+delta));
        SetSkin(ArmLower, new Vector3(1f, 1f+delta*0.5f, 1f+delta*0.5f));
        SetSkin(Hand, new Vector3(1f, 1f+delta*0.2f, 1f+delta*0.2f));
        SetSkin(BackUpper, new Vector3(1f, 1f+delta*0.2f, 1f+delta*0.2f));
        SetBone(Shoulder, new Vector3(1f, shoulders, 1f));

        // down, wide, out
        SetSkin(Chest, new Vector3(1f+delta*0.1f, 1f+delta*0.4f, 1f+delta*0.4f));

        // CENTER BODY (up, wide, out)
        delta = fatness  1f;
        SetSkin(BackLower, new Vector3(1f+delta*0.2f, 1f+delta*0.4f, 1f+delta*0.7f));
        //SetSkin(BackMiddle, new Vector3(1f+delta*0.2f, 1f+delta*0.7f, 1f+delta*1.0f));
        //SetSkin(BackUpper, new Vector3(1f+delta*0.2f, 1f+delta*0.7f, 1f+delta*1.0f));
        //SetSkin(Chest, new Vector3(1f+delta*0.2f, 1f+delta*0.7f, 1f+delta*1.0f));
        //SetSkin(Stomach, new Vector3(1f+delta*0.3f, 1f+delta*0.5f, 1f+delta*1.0f));
        SetSkin(BackMiddle, new Vector3(1f+delta*0.2f, 1f+delta*0.2f, 1f+delta*0.7f));
        SetSkin(Pelvis, new Vector3(1f+delta*0.4f, 1f+delta*0.3f, 1f+delta*0.3f));

        // HEAD
        delta = head  1f;
        SetSkin(Head, new Vector3(1f+delta, 1f+delta, 1f+delta));
        SetSkin(Hair, new Vector3(1f+delta, 1f+delta, 1f+delta));
        SetSkin(Jaw, new Vector3(1f+delta, 1f+delta, 1f+delta));

        mesh.bindposes = poses;
    }

    
    public bool _SetSkin(string name, Vector3 scale) {
        SkinnedMeshRenderer smr = GetComponent<SkinnedMeshRenderer>();

        Transform[] bones = smr.bones;

        int i = 0;
        for (i = 0; i < bones.Length; i++) {
            if (bones[i].name == name)
                break;
        }
        if (i == bones.Length) {
            //Debug.Log(Did not find:  + name +  in object  + this.name);
            return false;
        }

        //Debug.Log(Found  + name +  at index:  + i +  for scale:  + scale);

        Matrix4x4 r = Matrix4x4.identity;
        r.SetTRS(Vector3.zero, 
                        Quaternion.AngleAxis(90f, Vector3.left),
                        Vector3.one);

        Matrix4x4 s = Matrix4x4.Scale(scale);

        //Debug.Log(Pose:  + poses +  Bone:  + bonepos);

        poses[i] = s * bonepos[i] * localToWorld * r;

        return true;
    }

    public bool _SetBone(string name, Vector3 scale) {
        Transform bone = FindBone(transform.parent, name);
        if (bone != null) {
            bone.localScale = scale;
            //Debug.Log(Overriding local scale for:  + name +  to:  + scale) ;
            return true;
        }
        //Debug.Log(Unable to find:  + name);
        return false;
    }

    public void SetBone(string name, Vector3 scale) {
        if (!_SetBone(name, scale)) {
            _SetBone(name+_L, scale);
            _SetBone(name+_R, scale);
        }
    }

    public void SetSkin(string name, Vector3 scale) {
        if (!_SetSkin(name, scale)) {
            _SetSkin(name+_L, scale);
            _SetSkin(name+_R, scale);
        }
    }

    public Transform FindBone (Transform transform, string name)
    {
        foreach (Transform child in transform)
        {
            if (child.name == name)
                return child;
            else
            {
                Transform test = FindBone(child, name);
                
                if (test != null)
                    return test;
            }
        }
        return null;
    }
}