﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Linq;
using UnityEngine.EventSystems;

public class SppCameraBehaviour : MonoBehaviour {
	public Transform cameraTarget;
	public float zoomSpeed = 3f;
	public float explodeSpeed = 50f;
	public SppExplosionBehaviour explosionHandler;
	public SppSelectionBehaviour selectionHandler;
	public GameObject[] selectionReceivers;

	private float initialDistance = 0;
	private float initialDiff = 0;
	private float curDistance;
	private float curRotX = 0;
	private float curRotY = 0;
	private float lastDistance;
	private float lastRotX;
	private float lastRotY;

	private float minDistance;
	private float maxDistance;
	private Vector3 mouseDownPos;
	private Vector2 touchDownPos;
	private RaycastHit hit;
	private float rayDistance = Mathf.Infinity;

	private bool  hitAvail;
	private RaycastHit lastHit;
	private bool useMouse = true;
	private float downTime;
	private float explosionValue = 0f;
	private float initialExplosion = 0f;
	private int layerMask = (1 << 0) | (1 << 1); // Default + TransparentFX
	private float lastDblTap = 0f;

	void  Awake (){
		lastDblTap = 0f;
        if (Application.platform == RuntimePlatform.Android || Application.platform == RuntimePlatform.IPhonePlayer)
            useMouse = false;

        curDistance = Mathf.Abs(transform.position.z);
		lastDistance = curDistance;
		downTime = Time.time;
	}

    void Update()
    {
        if (useMouse)
        {
            HandleMouse(Time.deltaTime);
            return;
        }
        else
        {
            HandleTouch(Time.deltaTime);
        }
	}

    private void HandleTouch( float deltaTime )
    {
        // handle touch
        if (Input.touchCount == 0) return;

        Touch touch0 = Input.GetTouch(0);
        float diff;

        try
        {
            if (Input.touchCount == 2)
            {
                Touch touch1 = Input.GetTouch(1);
                diff = Vector2.Distance(touch0.position, touch1.position);
                if (Mathf.Abs(touch0.position.x - touch1.position.x) > Mathf.Abs(touch0.position.y - touch1.position.y))
                {
                    // explode
                    if (touch0.phase == TouchPhase.Began || touch1.phase == TouchPhase.Began)
                    {
                        initialDistance = diff;
                        initialExplosion = explosionValue;
                    }
                    explosionValue = 1f - (initialDistance / diff);
                    explosionValue += initialExplosion;
                    if (explosionValue < 0) explosionValue = 0;
                    if (explosionValue > 1) explosionValue = 1;
                    if (explosionHandler != null)
                    {
                        explosionHandler.Explode(explosionValue);
                    }
                }
                else
                {
                    lastDistance = curDistance;
                    // pinch/zoom
                    // this is the current distance set in relation to the initial distance
                    if (touch0.phase == TouchPhase.Began || touch1.phase == TouchPhase.Began)
                    {
                        initialDistance = curDistance;
                        initialDiff = diff;
                    }
                    curDistance = initialDistance * (initialDiff / diff);
                    ClampDistance();
                    SetPosition();
                }
            }
            else
            {
                if (Input.touchCount == 1 && touch0.tapCount == 2 && ((Time.time - lastDblTap) > 0.3f))
                {
                    lastDblTap = Time.time;
                    // PICKING
                    Ray ray = GetComponent<Camera>().ScreenPointToRay(touch0.position);
                    if (Physics.Raycast(ray, out hit, rayDistance/*, layerMask*/))
                    {
                        Transform t = hit.transform;
                        while (t.parent && t.parent.name != "000")
                        {
                            t = t.parent;
                        }
                        if (t.parent.name == "000")
                        {
                            DoSelection(t.gameObject);
                            return;
                        }
                        else
                            Debug.Log("Unsolved: " + hit.transform.gameObject.name);
                    }
                }
                else
                {
                    if (touch0.phase == TouchPhase.Began)
                    {
                        touchDownPos = touch0.position;
                    }
                    else if (touch0.phase == TouchPhase.Moved)
                    {
                        lastRotX = curRotX;
                        lastRotY = curRotY;

                        Vector2 deltaPosition = touch0.position - touchDownPos;
                        curRotY += (deltaPosition.x / (Camera.main.pixelWidth / 360.0f));
                        curRotX -= (deltaPosition.y / (Camera.main.pixelHeight / 360.0f));
                        SetPosition();
                        touchDownPos = touch0.position;

                    }
                }
            }
        }

        catch (System.Exception e)
        {
            Debug.Log("Camera Update():" + e.Message);
        }

        // Check state 2 fingers to one
        if (Input.touchCount == 2)
        {
            Touch t0 = Input.GetTouch(0);
            Touch t1 = Input.GetTouch(1);
            if (t0.phase == TouchPhase.Ended || t0.phase == TouchPhase.Ended)
            {
                // transition from swipe to rotate
                if (t0.phase == TouchPhase.Ended)
                    touchDownPos = t1.position;
                if (t1.phase == TouchPhase.Ended)
                    touchDownPos = t0.position;
            }
        }

    }

    private void  HandleMouse ( float deltaTime  ){

		float diff;
		Ray ray;
		if (Input.GetAxis("Mouse ScrollWheel") != 0) {
			#if UNITY_STANDALONE_OSX 
			if (Input.GetKey (KeyCode.LeftAlt)) {
			#else			
			if (Input.GetKey (KeyCode.LeftShift)) {
			#endif				
				// explode
				explosionValue -= (Input.GetAxis("Mouse ScrollWheel")*deltaTime*explodeSpeed);
				if (explosionValue < 0) explosionValue = 0;
				if (explosionValue > 1) explosionValue = 1;
				if (explosionHandler != null) {
					explosionHandler.Explode(explosionValue);
				}
			} else {
				// pinch/zoom
				diff = Input.GetAxis("Mouse ScrollWheel")*deltaTime*zoomSpeed;
				lastDistance = curDistance;
				curDistance -= (diff*((maxDistance-minDistance)));
				ClampDistance();
				SetPosition();
			}	
		}

		if (IsOverUI()) {
			return;
		}	
		if (Input.GetMouseButtonDown(0)) {
			mouseDownPos = Input.mousePosition;
			// check dblClick
			if ((Time.time - downTime) < 0.4) {
				ray = GetComponent<Camera>().ScreenPointToRay (Input.mousePosition);
				// Ray cast only 0
				if (Physics.Raycast (ray, out hit, Mathf.Infinity, layerMask)) {
					Transform t = hit.transform;
					while (t.parent && t.parent.name!="000") {
						t = t.parent;
					}
					try {
						if (t.parent.name=="000") {
							DoSelection(t.gameObject);
							return;
						}	
						else
							Debug.Log("Unsolved: " +hit.transform.gameObject.name);
					} catch {
						// NOP
					}
				}
				downTime = Time.time;
				return;
			}
			else 
				downTime = Time.time;
		}
		else  if (Input.GetMouseButton(0)) {
			lastRotX = curRotX;
			lastRotY = curRotY;

			Vector2 deltaPosition= Input.mousePosition-mouseDownPos;
			curRotY += (deltaPosition.x/(Camera.main.pixelWidth/360.0f));
			curRotX -= (deltaPosition.y/(Camera.main.pixelHeight/360.0f));
			SetPosition();
			mouseDownPos = Input.mousePosition;
		}
	}

    private void HandleController( float deltaTime)
    {

    }

	private void CollisionReset() {
		curDistance = lastDistance;
		curRotX = lastRotX;
		curRotY = lastRotY;
		transform.rotation = Quaternion.Euler(new Vector3(curRotX, curRotY, 0));
		transform.position = cameraTarget.position-transform.forward*curDistance;
	}

	private void  SetPosition (){
		try {	
			// Spherical collision
			transform.rotation = Quaternion.Euler(new Vector3(curRotX, curRotY, 0));
			transform.position = cameraTarget.position-transform.forward*curDistance;
		} catch (System.Exception e) {
			Debug.Log("SetPosition: "+e.Message);
		}
	}

	void  CamDistance ( float newDistance  ){
		lastDistance = curDistance;
		curDistance = newDistance;
		SetPosition();
	}

	void  SetISO (){
		try {	
			curRotX = 45.0f;
			curRotY = -45.0f;
			lastRotX = curRotX;
			lastRotY = curRotY;
			SetPosition();	
		}		
		catch (System.Exception e) {
			Debug.Log("SetPosition: "+e.Message);
		}
	}

	void  SetISODistance ( float dist  ){
		lastDistance = curDistance;
		curDistance = dist;

		SetISO();
	}	

	void SetDistanceMin(float distance) {
		minDistance = distance;
	}	

	void SetDistanceMax(float distance) {
		maxDistance = distance;
	}	

	void  SetDistanceMinMax (){
		try {
			// minDistance
			float mi = cameraTarget.lossyScale.x;
			if (cameraTarget.lossyScale.y < mi)
				mi = cameraTarget.lossyScale.y;
			if (cameraTarget.lossyScale.z < mi)
				mi = cameraTarget.lossyScale.z;
			minDistance = Mathf.Sqrt(2*mi);
			// maxDistance
			float ma = cameraTarget.lossyScale.x;
			if (cameraTarget.lossyScale.y > ma)
				ma = cameraTarget.lossyScale.y;
			if (cameraTarget.lossyScale.z > ma)
				ma = cameraTarget.lossyScale.z;
			minDistance = Mathf.Sqrt(2*(mi*mi));
			maxDistance = ma*3;
			Camera.main.farClipPlane = 2f*maxDistance;
			if (Application.platform == RuntimePlatform.Android || 	Application.platform == RuntimePlatform.IPhonePlayer) 
				Camera.main.nearClipPlane = minDistance/5f;
			else		
				Camera.main.nearClipPlane = minDistance/100f;
			ClampDistance();
			SetPosition();
		}		
		catch (System.Exception e) {
			Debug.Log("SetPositionMinMax: "+e.Message);
		}
	}

	void  ClampDistance (){
		if (curDistance < minDistance)
			curDistance = minDistance;	
		if (curDistance > maxDistance)
			curDistance = maxDistance;	
	}

	void OnModelLoaded() {
		explosionValue = 0f;
	}

	bool IsOverUI() {
		if (Input.GetMouseButton (0)) {
			PointerEventData pointerData = new PointerEventData (EventSystem.current) { pointerId = -1, };

			pointerData.position = Input.mousePosition;
			List<RaycastResult> results = new List<RaycastResult> ();
			try {
			EventSystem.current.RaycastAll (pointerData, results);
			} catch {
			}
			return results.Count > 0;
		} else {
			return false;
		}	
	}	

	void DoSelection(GameObject go) {
		if (selectionReceivers != null) {
			for (int i = 0; i < selectionReceivers.Length; i++) {
				ISelectionChange intf = selectionReceivers [i].GetComponent<ISelectionChange> ();
				if (intf != null) {
					intf.OnSelectionChange (go);
				}	
			}
		}
		if (selectionHandler != null) {
			selectionHandler.OnSelectionChange (go);
		}	
	}	
}
