"""
GTSAM Copyright 2010-2020, Georgia Tech Research Corporation,
Atlanta, Georgia 30332-0415
All Rights Reserved
Authors: Frank Dellaert, et al. (see THANKS for the full author list)

See LICENSE for the license information

Track a moving object "Time of Arrival" measurements at 4 microphones.
Author: Frank Dellaert
"""
# pylint: disable=invalid-name, no-name-in-module

from gtsam import (LevenbergMarquardtOptimizer, LevenbergMarquardtParams,
                   NonlinearFactorGraph, Point3, Values, noiseModel)
from gtsam_unstable import Event, TimeOfArrival, TOAFactor

# units
MS = 1e-3
CM = 1e-2

# Instantiate functor with speed of sound value
TIME_OF_ARRIVAL = TimeOfArrival(330)


def define_microphones():
    """Create microphones."""
    height = 0.5
    microphones = []
    microphones.append(Point3(0, 0, height))
    microphones.append(Point3(403 * CM, 0, height))
    microphones.append(Point3(403 * CM, 403 * CM, height))
    microphones.append(Point3(0, 403 * CM, 2 * height))
    return microphones


def create_trajectory(n):
    """Create ground truth trajectory."""
    trajectory = []
    timeOfEvent = 10
    # simulate emitting a sound every second while moving on straight line
    for key in range(n):
        trajectory.append(
            Event(timeOfEvent, 245 * CM + key * 1.0, 201.5 * CM, (212 - 45) * CM))
        timeOfEvent += 1

    return trajectory


def simulate_one_toa(microphones, event):
    """Simulate time-of-arrival measurements for a single event."""
    return [TIME_OF_ARRIVAL.measure(event, microphones[i])
            for i in range(len(microphones))]


def simulate_toa(microphones, trajectory):
    """Simulate time-of-arrival measurements for an entire trajectory."""
    return [simulate_one_toa(microphones, event)
            for event in trajectory]


def create_graph(microphones, simulatedTOA):
    """Create factor graph."""
    graph = NonlinearFactorGraph()

    # Create a noise model for the TOA error
    model = noiseModel.Isotropic.Sigma(1, 0.5 * MS)

    K = len(microphones)
    key = 0
    for toa in simulatedTOA:
        for i in range(K):
            factor = TOAFactor(key, microphones[i], toa[i], model)
            graph.push_back(factor)
        key += 1

    return graph


def create_initial_estimate(n):
    """Create initial estimate for n events."""
    initial = Values()
    zero = Event()
    for key in range(n):
        TOAFactor.InsertEvent(key, zero, initial)
    return initial


def toa_example():
    """Run example with 4 microphones and 5 events in a straight line."""
    # Create microphones
    microphones = define_microphones()
    K = len(microphones)
    for i in range(K):
        print("mic {} = {}".format(i, microphones[i]))

    # Create a ground truth trajectory
    n = 5
    groundTruth = create_trajectory(n)
    for event in groundTruth:
        print(event)

    # Simulate time-of-arrival measurements
    simulatedTOA = simulate_toa(microphones, groundTruth)
    for key in range(n):
        for i in range(K):
            print("z_{}{} = {} ms".format(key, i, simulatedTOA[key][i] / MS))

    # create factor graph
    graph = create_graph(microphones, simulatedTOA)
    print(graph.at(0))

    # Create initial estimate
    initial_estimate = create_initial_estimate(n)
    print(initial_estimate)

    # Optimize using Levenberg-Marquardt optimization.
    params = LevenbergMarquardtParams()
    params.setAbsoluteErrorTol(1e-10)
    params.setVerbosityLM("SUMMARY")
    optimizer = LevenbergMarquardtOptimizer(graph, initial_estimate, params)
    result = optimizer.optimize()
    print("Final Result:\n", result)


if __name__ == '__main__':
    toa_example()
    print("Example complete")
