using System;
using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode]
[ExecuteAlways]
public class BodyRSUV : MonoBehaviour
{
    public enum SkinColor
    {
        Light, 
        Yellow,
        DarkToned, 
        RedToned,
        BrownToned
    }

    public enum ClothesColor
    {
        Neutral,
        LightGray,
        DarkGray,
        Red,
        Green,
        Blue,
        Gold
    }

    public enum HeadGear
    {
        None,
        Helmet,
        Crown

    }
    public enum HairColor
    {
        Black,
        DarkBrown,
        LightBrown,
        MediumRed,
        DeepRed,
        Blond,
        ExtremeBlond,
        White
    }
    public enum HairStyle
    {
        None,
        Bun,
        PonyTail,
        Tintin,
        Long
    }
    public enum EyebrowsStyle
    {
        None,
        Monobrow,
        Thin
    }
    public enum BeardStyle
    {
        None,
        Large,
        Small
    }

    public enum AnimationState
    {
        Idle,
        Jump
    }

    public bool randomizeOnStart = true;
    public SkinColor skinColor = SkinColor.Light;
    public ClothesColor clothesColor = ClothesColor.Neutral;
    public HairColor hairColor = HairColor.Black;

    private HeadGear lastHeadGear = HeadGear.None;
    public HeadGear headgear = HeadGear.None;
    private HairStyle lastHairStyle = HairStyle.None;
    public HairStyle hairStyle = HairStyle.None;
    public EyebrowsStyle eyebrowsStyle = EyebrowsStyle.None;
    public BeardStyle beardStyle = BeardStyle.None;

    public float bellySize = 0;

    private float scaleVariance = 0.1f;

    private int vertexAnimationMaxTransition = 7;  
    private int vertexAnimationTransitionLerpFactor = 0;  
    private int vertexAnimationAnimIndexFrom = 0;  
    private int vertexAnimationAnimIndexTo = 0;  
    private int vertexAnimationFps = 50;  
    private int vertexAnimationFpsOffset = 0;
    private float vertexAnimationTimeOffset = 0; 

    AnimationState animationState = AnimationState.Idle;

    public List<SkinnedMeshRenderer> skinnedMeshRenderers = null;
    public List<MeshRenderer> meshRenderers = null;

    private int frameTimeMax = 127; // FrameTIme is encoded on 7 bits so its range is [0-127]
    private int frameOffset = 0;
    private int frameTime = 0;
    uint data = 0x00000000; // All bits set to 0

    void Start()
    {
        vertexAnimationFpsOffset = UnityEngine.Random.Range(-3, +3);
        vertexAnimationTimeOffset = UnityEngine.Random.Range(-50, 50)/100f;
        vertexAnimationTransitionLerpFactor = vertexAnimationMaxTransition;

        SetAnimationState(AnimationState.Idle);

        if (randomizeOnStart)
            Randomize();

        UpdateData();
    }

    void OnEnable()
    {
        Start();
    }

    void OnValidate()
    {
        UpdateData();
    }

    void Update()
    {
        UpdateVertexAnimationData();

         switch (animationState)
         { 
            case AnimationState.Jump:
                if (frameTime >= frameTimeMax - 1)
                    SetAnimationState(AnimationState.Idle);
                break;
         }

    }

    void UpdateVertexAnimationData()
    {
        float time = Time.realtimeSinceStartup + vertexAnimationTimeOffset;
        float fps = vertexAnimationFps + vertexAnimationFpsOffset;

        if(vertexAnimationTransitionLerpFactor < vertexAnimationMaxTransition)
            vertexAnimationTransitionLerpFactor += 1;

        int adjustedTime = Mathf.FloorToInt(time * fps) + frameOffset;

        // FrameTime should always be in the range [0,127]
        frameTime = (int)Mathf.Repeat(adjustedTime, frameTimeMax);

        data = HelpersRSUV.EncodeData(data, frameTime, 20, 7);
        data = HelpersRSUV.EncodeData(data, vertexAnimationTransitionLerpFactor, 27, 3);
        data = HelpersRSUV.EncodeData(data, vertexAnimationAnimIndexFrom, 30, 1);
        data = HelpersRSUV.EncodeData(data, vertexAnimationAnimIndexTo, 31, 1);

        UpdateRenderers();
    }

    void UpdateData()
    {

        bellySize = Mathf.Clamp01(bellySize);
        float step = Mathf.CeilToInt(3 * (bellySize + 0.001f * bellySize)) / 4f;
        int bellySize03 = Mathf.RoundToInt(step * 3);

        CheckInconsistencies();

        data = HelpersRSUV.EncodeData(data, (int)skinColor, 0, 3);
        data = HelpersRSUV.EncodeData(data, (int)clothesColor, 3, 3);
        data = HelpersRSUV.EncodeData(data, bellySize03, 6, 2);
        data = HelpersRSUV.EncodeData(data, (int)hairColor, 8, 3);
        data = HelpersRSUV.EncodeData(data, (int)hairStyle, 11, 3);
        data = HelpersRSUV.EncodeData(data, (int)eyebrowsStyle, 14, 2);
        data = HelpersRSUV.EncodeData(data, (int)beardStyle, 16, 2);
        data = HelpersRSUV.EncodeData(data, (int)headgear, 18, 2);

        UpdateRenderers();

    }

    private void UpdateRenderers()
    {
        if (skinnedMeshRenderers.Count + meshRenderers.Count == 0)
            throw new NullReferenceException("The renderers has not been set properly.");

        foreach (SkinnedMeshRenderer smr in skinnedMeshRenderers)
            smr.SetShaderUserValue(data);

        foreach (MeshRenderer mr in meshRenderers)
            mr.SetShaderUserValue(data);
    }

    public void Randomize()
    {
        SetSkinColor((SkinColor) UnityEngine.Random.Range(0, System.Enum.GetValues(typeof(SkinColor)).Length));
        SetClothColor((ClothesColor)UnityEngine.Random.Range(0, System.Enum.GetValues(typeof(ClothesColor)).Length - 1)); // - 1 To Avoid having the gold color reserved for the king
        SetBellySize(UnityEngine.Random.Range(0f, 100f)/100f);
        SetHairColor((HairColor)UnityEngine.Random.Range(0, System.Enum.GetValues(typeof(HairColor)).Length));
        SetEyeBrowsStyle((EyebrowsStyle)UnityEngine.Random.Range(0, System.Enum.GetValues(typeof(EyebrowsStyle)).Length));
        SetBeardStyle((BeardStyle)UnityEngine.Random.Range(0, System.Enum.GetValues(typeof(BeardStyle)).Length));
        SetHeadGear((HeadGear)UnityEngine.Random.Range(0, System.Enum.GetValues(typeof(HeadGear)).Length - 1)); //-1 because we want to make sure the crown is not selected when randomizing
        SetHairStyle((HairStyle)UnityEngine.Random.Range(1, System.Enum.GetValues(typeof(HairStyle)).Length));

        CheckInconsistencies();

        this.transform.localScale = Vector3.one * (1 + UnityEngine.Random.Range(-scaleVariance, scaleVariance));
       
        UpdateData();
    }

    public void SetAnimationState(AnimationState newAnimationState)
    {

        vertexAnimationAnimIndexFrom = (int)animationState;
        vertexAnimationAnimIndexTo = (int)newAnimationState;
        if(newAnimationState != animationState)
        {
            if(newAnimationState == AnimationState.Jump)
                frameOffset += (frameTimeMax - frameTime);

            vertexAnimationTransitionLerpFactor = 0;
        }

        animationState = newAnimationState;

        UpdateVertexAnimationData();

    }

    public void SetSkinColor(SkinColor skinColor)
    {
        this.skinColor = skinColor;
    }

    public void SetClothColor(ClothesColor clothColor)
    {
        this.clothesColor = clothColor;
    }

    public void SetBellySize(float bellySize)
    {
        this.bellySize = bellySize;
    }

    public void SetHairColor(HairColor hairColor)
    {
        this.hairColor = hairColor;
    }

    public void SetHairStyle(HairStyle hairStyle)
    {
        lastHairStyle = this.hairStyle;
        this.hairStyle = hairStyle;
    }

    public void SetHeadGear(HeadGear headGear)
    {
        lastHeadGear = this.headgear;
        this.headgear = headGear;
    }

    public void SetEyeBrowsStyle(EyebrowsStyle eyebrowsStyle)
    {
        this.eyebrowsStyle = eyebrowsStyle;
    }

    public void SetBeardStyle(BeardStyle beardStyle)
    {
        this.beardStyle = beardStyle;
    }

    public void CheckInconsistencies()
    {
        if(lastHairStyle != this.hairStyle)
        {            
            if (hairStyle != HairStyle.None)
                SetHeadGear(HeadGear.None);

            SetHairStyle(this.hairStyle);
            SetHeadGear(this.headgear);
            return;

        }

        if (lastHeadGear != this.headgear)
        {
            if (headgear != HeadGear.None)
                SetHairStyle(HairStyle.None);

            SetHeadGear(this.headgear);
            SetHairStyle(this.hairStyle);
            return;
        }
        

    }


}