//
//  Motion.m
//  BlockyFroggy
//
//  Created by John Ryland on 19/9/17.
//  Copyright © 2017 John Ryland. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <CoreMotion/CoreMotion.h>
#import <CoreLocation/CoreLocation.h>
#import <CoreLocation/CLLocationManagerDelegate.h>
#include "Motion.h"

@interface LocationDelegate : NSObject <CLLocationManagerDelegate> //<NSObject>
@end

@interface LocationDelegate ()
  @property (strong, nonatomic) CLLocation *location;
@end

@implementation LocationDelegate
  - (void)locationManager:(CLLocationManager *)manager
      didUpdateLocations:(NSArray<CLLocation *> *)locations
  {
    if ([locations count] > 0)
      _location = locations[0];
  }
@end


class MotionTrackingData
{
public:
  LocationDelegate* m_locationDelegate = nullptr;
  CMMotionManager* m_motionManager = nullptr;
  CLLocationManager* m_locationManager = nullptr;
  NSOperationQueue* m_deviceQueue = nullptr;
};


MotionTracking::MotionTracking()
{
  m_motionTrackingData = new MotionTrackingData;
  m_motionTrackingData->m_locationDelegate = [[LocationDelegate alloc] init];
  m_motionTrackingData->m_motionManager = [[CMMotionManager alloc] init];
  m_motionTrackingData->m_locationManager = [[CLLocationManager alloc] init];
  m_motionTrackingData->m_deviceQueue = [[NSOperationQueue alloc] init];
}


MotionTracking::~MotionTracking()
{
  delete m_motionTrackingData;
  //[m_motionManager release];
}


void MotionTracking::startTracking(float a_frequency, std::function<void(double a_translation[3], float a_rotation[4], float a_rotationMatrix[9], float a_acceleration[3])> a_callback)
{
  CLLocationManager* lm = m_motionTrackingData->m_locationManager;
  CMMotionManager* mm = m_motionTrackingData->m_motionManager;

  [lm setDistanceFilter: 500];
  [lm setDesiredAccuracy: kCLLocationAccuracyBest];
  [lm setDelegate: m_motionTrackingData->m_locationDelegate];
  [lm startUpdatingLocation];
  
  
  CLLocation *l = [m_motionTrackingData->m_locationDelegate location];
  l = [[CLLocation alloc] initWithLatitude: 0.0 longitude: 0.0];
  

#if 0
  [mm startDeviceMotionUpdatesUsingReferenceFrame: CMAttitudeReferenceFrameXTrueNorthZVertical];
  mm.accelerometerUpdateInterval = a_frequency;
  [mm startAccelerometerUpdatesToQueue:
      m_motionTrackingData->m_deviceQueue
      withHandler: ^(CMAccelerometerData *d, NSError *error)
  {
     [[NSOperationQueue mainQueue] addOperationWithBlock:^{
       CMAcceleration accel = [d acceleration];
       double translation[3];
       float rotation[4];
       
       translation[0] = accel.x * 0.1;
       translation[1] = accel.y * 0.1;
       translation[2] = accel.z * 0.1;
       
       CMMotionManager* mm = m_motionTrackingData->m_motionManager;
       CMDeviceMotion* dm = [mm deviceMotion];
       CMAttitude *attitude = [dm attitude];
       CMQuaternion q = attitude.quaternion;
       CMRotationMatrix rot = attitude.rotationMatrix;
       /*
       translation[0] = accel.x*rot.m11 + accel.y*rot.m12 + accel.z*rot.m13;
       translation[1] = accel.x*rot.m21 + accel.y*rot.m22 + accel.z*rot.m23;
       translation[2] = accel.x*rot.m31 + accel.y*rot.m32 + accel.z*rot.m33;
       */
       rotation[0] = q.x;
       rotation[1] = q.y;
       rotation[2] = q.z;
       rotation[3] = q.w;
       a_callback(translation, rotation);
     }];
  }];
#else
  //[mm startDeviceMotionUpdates];
  mm.deviceMotionUpdateInterval = a_frequency;
//  [mm startGyroUpdates];
//  [mm startDeviceMotionUpdatesToQueue:
  [mm startDeviceMotionUpdatesUsingReferenceFrame: CMAttitudeReferenceFrameXArbitraryZVertical toQueue:  //CMAttitudeReferenceFrameXTrueNorthZVertical
   m_motionTrackingData->m_deviceQueue
   withHandler: ^(CMDeviceMotion *dm, NSError *error)
   {
   
       [[NSOperationQueue mainQueue] addOperationWithBlock:^{
       CMAcceleration accel = [dm userAcceleration];
       CMAttitude *attitude = [dm attitude];
       CMQuaternion q = attitude.quaternion;
       CMRotationMatrix rot = attitude.rotationMatrix;
       double translation[3];
       float rotation[4];

       /*
       translation[0] = accel.x*rot.m11 + accel.y*rot.m12 + accel.z*rot.m13;
       translation[1] = accel.x*rot.m21 + accel.y*rot.m22 + accel.z*rot.m23;
       translation[2] = accel.x*rot.m31 + accel.y*rot.m32 + accel.z*rot.m33;
       
       translation[0] = accel.x*rot.m11 + accel.y*rot.m21 + accel.z*rot.m31;
       translation[1] = accel.x*rot.m12 + accel.y*rot.m22 + accel.z*rot.m32;
       translation[2] = accel.x*rot.m13 + accel.y*rot.m23 + accel.z*rot.m33;
       */

       translation[0] = accel.x;
       translation[1] = accel.y;
       translation[2] = accel.z;

       rotation[0] = q.x;
       rotation[1] = q.y;
       rotation[2] = q.z;
       rotation[3] = q.w;
       
       float rotM[9];
       rotM[0] = rot.m11; rotM[1] = rot.m12; rotM[2] = rot.m13;
       rotM[3] = rot.m21; rotM[4] = rot.m22; rotM[5] = rot.m23;
       rotM[6] = rot.m31; rotM[7] = rot.m32; rotM[8] = rot.m33;
       float accelV[3];
       accelV[0] = accel.x;
       accelV[1] = accel.y;
       accelV[2] = accel.z;


       CLLocation *l = [m_motionTrackingData->m_locationDelegate location];
       CLLocationCoordinate2D coord = [l coordinate];
       double alt = [l altitude];
       
       translation[0] = coord.longitude;
       translation[1] = coord.latitude;
       translation[2] = alt;
       
       if (coord.longitude != 0.0)
       {
         translation[0] = coord.longitude;
         translation[1] = coord.latitude;
         translation[2] = alt;
       }

       a_callback(translation, rotation, rotM, accelV);
      }];
    }];
#endif
}


void MotionTracking::startTracking(float a_frequency)
{
  CMMotionManager* mm = m_motionTrackingData->m_motionManager;
  mm.deviceMotionUpdateInterval = a_frequency;
  //[mm startDeviceMotionUpdates];
  [mm startGyroUpdates];
  [mm startDeviceMotionUpdatesUsingReferenceFrame: CMAttitudeReferenceFrameXTrueNorthZVertical];
}


void MotionTracking::getTrackingData(float a_translation[3], float a_rotation[4])
{
  CMMotionManager* mm = m_motionTrackingData->m_motionManager;
  CMDeviceMotion* dm = [mm deviceMotion];
  CMAcceleration accel = [dm userAcceleration];
  CMAttitude *attitude = [dm attitude];
  CMQuaternion q = attitude.quaternion;
  CMRotationMatrix rot = attitude.rotationMatrix;

  a_translation[0] = accel.x*rot.m11 + accel.y*rot.m12 + accel.z*rot.m13;
  a_translation[1] = accel.x*rot.m21 + accel.y*rot.m22 + accel.z*rot.m23;
  a_translation[2] = accel.x*rot.m31 + accel.y*rot.m32 + accel.z*rot.m33;
  
  a_rotation[0] = q.x;
  a_rotation[1] = q.y;
  a_rotation[2] = q.z;
  a_rotation[3] = q.w;

  /*
  a_translation[0] = accel.x;
  a_translation[1] = accel.y;
  a_translation[2] = accel.z;
  */
  /*
  a_rotation[2] = attitude.roll;
  a_rotation[1] = attitude.pitch;
  a_rotation[0] = attitude.yaw;
  */
}
