// Copyright 2017 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Gvr;
using UnityEngine;

namespace Gvr.Internal {
  /// Mocks controller input by using the mouse.
  /// The controller is connected when holding left shift.
  /// Move the mouse to control gyroscope and orientation.
  /// The left mouse button is used for the clickButton.
  /// The right mouse button is used for the appButton.
  /// The middle mouse button is used for the homeButton.
  class MouseControllerProvider : IControllerProvider {
    private const string AXIS_MOUSE_X = "Mouse X";
    private const string AXIS_MOUSE_Y = "Mouse Y";

    private ControllerState state = new ControllerState();

    private Vector2 mouseDelta = new Vector2();

    /// Need to store the state of the buttons from the previous frame.
    /// This is because Input.GetMouseButtonDown and Input.GetMouseButtonUp
    /// don't work when called after WaitForEndOfFrame, which is when ReadState is called.
    private bool wasTouching;
    private GvrControllerButton lastButtonsState;

    private const float ROTATE_SENSITIVITY = 4.5f;
    private const float TOUCH_SENSITIVITY = .12f;
    private static readonly Vector3 INVERT_Y = new Vector3(1, -1, 1);

    public static bool IsMouseAvailable {
      get {
        return Input.mousePresent && IsActivateButtonPressed;
      }
    }

    public static bool IsActivateButtonPressed {
      get {
        return Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
      }
    }

    public static bool IsClickButtonPressed {
      get {
        return Input.GetMouseButton(0);
      }
    }

    public static bool IsAppButtonPressed {
      get {
        return Input.GetMouseButton(1);
      }
    }

    public static bool IsHomeButtonPressed {
      get {
        return Input.GetMouseButton(2);
      }
    }

    public static bool IsTouching {
      get {
        return Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl);
      }
    }

    public bool SupportsBatteryStatus {
      get { return false; }
    }

    public int MaxControllerCount {
      get { return 1; }
    }

    internal MouseControllerProvider() {}

    public void Dispose() {}

    public void ReadState(ControllerState outState, int controller_id) {
      if (controller_id != 0) {
        return;
      }
      lock (state) {
        UpdateState();

        outState.CopyFrom(state);
      }
      state.ClearTransientState();
    }

    public void OnPause() {}
    public void OnResume() {}

    private void UpdateState() {
      GvrCursorHelper.ControllerEmulationActive = IsMouseAvailable;

      if (!IsMouseAvailable) {
        ClearState();
        return;
      }

      state.connectionState = GvrConnectionState.Connected;
      state.apiStatus = GvrControllerApiStatus.Ok;
      state.isCharging = false;
      state.batteryLevel = GvrControllerBatteryLevel.Full;

      UpdateButtonStates();

      mouseDelta.Set(
        Input.GetAxis(AXIS_MOUSE_X),
        Input.GetAxis(AXIS_MOUSE_Y)
      );

      if (0 != (state.buttonsState & GvrControllerButton.TouchPadTouch)) {
        UpdateTouchPos();
      } else {
        UpdateOrientation();
      }
    }

    private void UpdateTouchPos() {
      Vector3 currentMousePosition = Input.mousePosition;
      Vector2 touchDelta = mouseDelta * TOUCH_SENSITIVITY;
      touchDelta.y *= -1.0f;

      state.touchPos += touchDelta;
      state.touchPos.x = Mathf.Clamp01(state.touchPos.x);
      state.touchPos.y = Mathf.Clamp01(state.touchPos.y);
    }

    private void UpdateOrientation() {
      Vector3 deltaDegrees = Vector3.Scale(mouseDelta, INVERT_Y) * ROTATE_SENSITIVITY;

      state.gyro = deltaDegrees * (Mathf.Deg2Rad / Time.deltaTime);

      Quaternion yaw = Quaternion.AngleAxis(deltaDegrees.x, Vector3.up);
      Quaternion pitch = Quaternion.AngleAxis(deltaDegrees.y, Vector3.right);
      state.orientation = state.orientation * yaw * pitch;
    }

    private void UpdateButtonStates() {
      state.buttonsState = 0;
      if (IsClickButtonPressed) {
        state.buttonsState |= GvrControllerButton.TouchPadButton;
      }
      if (IsAppButtonPressed) {
        state.buttonsState |= GvrControllerButton.App;
      }
      if (IsHomeButtonPressed) {
        state.buttonsState |= GvrControllerButton.System;
      }
      if (IsTouching) {
        state.buttonsState |= GvrControllerButton.TouchPadTouch;
      }

      state.SetButtonsUpDownFromPrevious(lastButtonsState);
      lastButtonsState = state.buttonsState;

      if (0 != (state.buttonsUp & GvrControllerButton.TouchPadTouch)) {
        ClearTouchPos();
      }

      if (0 != (state.buttonsUp & GvrControllerButton.System)) {
        Recenter();
      }
    }

    private void Recenter() {
      Quaternion yawCorrection = Quaternion.AngleAxis(-state.orientation.eulerAngles.y, Vector3.up);
      state.orientation = state.orientation * yawCorrection;
      state.recentered = true;
    }

    private void ClearTouchPos() {
      state.touchPos = new Vector2(0.5f, 0.5f);
    }

    private void ClearState() {
      state.connectionState = GvrConnectionState.Disconnected;
      state.buttonsState = 0;
      state.buttonsDown = 0;
      state.buttonsUp = 0;
      ClearTouchPos();
    }
  }
}