# ISC License
#
# Copyright (c) 2023, Autonomous Vehicle Systems Lab, University of Colorado at Boulder
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import os, inspect
import numpy as np
import pytest
from matplotlib import pyplot as plt
from Basilisk.utilities import SimulationBaseClass
from Basilisk.utilities import macros
from Basilisk.utilities import RigidBodyKinematics as rbk
from Basilisk.utilities import orbitalMotion
from Basilisk.utilities import simIncludeGravBody
from Basilisk.architecture import messaging
from Basilisk.simulation import pinholeCamera
from Basilisk.simulation import spacecraft
from Basilisk.utilities import unitTestSupport
from Basilisk import __path__
filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))
bskName = 'Basilisk'
splitPath = path.split(bskName)
bskPath = __path__[0]
[docs]def test_visibility():
    """
    Tests whether pinholeCamera:
    1. Computes correctly pixels for center and corners.
    2. Detects correctly that some landmarks are not within field of view.
    :return:
    """
    simTime = 1.
    simTaskName = "simTask"
    simProcessName = "simProcess"
    scSim = SimulationBaseClass.SimBaseClass()
    dynProcess = scSim.CreateNewProcess(simProcessName)
    simulationTime = macros.sec2nano(simTime)
    simulationTimeStep = macros.sec2nano(1.)
    dynProcess.addTask(scSim.CreateNewTask(simTaskName, simulationTimeStep))
    # Set the landmarks distribution to be tested
    # The first five landmarks shall correspond to center and corners of the image (for the prescribed situation)
    # The last five landmarks shall be detected as not within FOV
    FOVx = 38.17124015837933 * np.pi/180  # this is the horizontal FOV, check camera.FOVx
    FOVy = 29.094758030219015 * np.pi/180  # this is the vertical FOV, check camera.FOVy
    n_lmk = 10
    pos_lmk = np.array([[-16*1e3, 0, 0],                        # Image center
                        [-16*1e3, 18*1e3 * np.tan(FOVy/2), 0],  # Image corner (18 km = horizontal distance sc-lmk)
                        [-16*1e3, -18*1e3*np.tan(FOVy/2), 0],   # Image corner
                        [-16*1e3, 0, 18*1e3*np.tan(FOVx/2)],    # Image corner
                        [-16*1e3, 0, -18*1e3*np.tan(FOVx/2)],   # Image corner
                        [16*1e3, 0, 0],                         # Not visible
                        [0, 16*1e3, 0],                         # Not visible
                        [0, -16*1e3, 0],                        # Not visible
                        [0, 0, 16*1e3],                         # Not visible
                        [0, 0, -16*1e3]])                       # Not visible
    normal_lmk = np.array([[-1, 0, 0],
                           [-1, 0, 0],                          # Mock to ensure visibility
                           [-1, 0, 0],                          # Mock to ensure visibility
                           [-1, 0, 0],                          # Mock to ensure visibility
                           [-1, 0, 0],                          # Mock to ensure visibility
                           [1, 0, 0],
                           [0, 1, 0],
                           [0, -1, 0],
                           [0, 0, 1],
                           [0, 0, -1]])
    # Set the pinhole camera module
    camera = pinholeCamera.PinholeCamera()
    camera.f = 25*1e-3
    camera.nxPixel = 2048
    camera.nyPixel = 1536
    camera.wPixel = (17.3*1e-3) / 2048
    dcm_CB = np.array([[0, 0, -1],
                       [0, 1, 0],
                       [1, 0, 0]])
    camera.dcm_CB = dcm_CB.tolist()
    for i in range(n_lmk):
        camera.addLandmark(pos_lmk[i, 0:3], normal_lmk[i, 0:3])
    scSim.AddModelToTask(simTaskName, camera)
    # Write out mock planet ephemeris message
    planet_message = messaging.EphemerisMsgPayload()
    aP = 1.4583 * 149597870.7*1e3
    r_PN_N = np.array([aP, 0, 0])
    v_PN_N = np.array([0, np.sqrt(orbitalMotion.MU_SUN/aP), 0])
    dcm_PN = np.identity(3)
    mrp_PN = rbk.C2MRP(dcm_PN)
    planet_message.r_BdyZero_N = r_PN_N
    planet_message.v_BdyZero_N = v_PN_N
    planet_message.sigma_BN = mrp_PN
    planetMsg = messaging.EphemerisMsg().write(planet_message)
    camera.ephemerisInMsg.subscribeTo(planetMsg)
    # Write out mock spacecraft message (pointing towards planet shall be ensured)
    a = 34 * 1e3
    mu = 4.4631 * 1e5
    r_BP_P = np.array([-a, 0, 0])
    v_BP_P = np.array([0, -np.sqrt(mu/a), 0])
    r_BN_N = r_BP_P + r_PN_N
    v_BN_N = v_BP_P + v_PN_N
    ir = r_BP_P / np.linalg.norm(r_BP_P)
    ih = np.cross(ir, v_BP_P/np.linalg.norm(v_BP_P))
    it = np.cross(ir, ih)
    dcm_BP = np.zeros((3, 3))
    dcm_BP[0:3, 0] = -ir
    dcm_BP[0:3, 1] = ih
    dcm_BP[0:3, 2] = it
    dcm_BN = np.matmul(dcm_BP, dcm_PN)
    mrp_BN = rbk.C2MRP(dcm_BN)
    sc1_message = messaging.SCStatesMsgPayload()
    sc1_message.r_BN_N = r_BN_N
    sc1_message.v_BN_N = v_BN_N
    sc1_message.sigma_BN = mrp_BN
    scMsg = messaging.SCStatesMsg().write(sc1_message)
    camera.scStateInMsg.subscribeTo(scMsg)
    # Log the landmark messages
    numDataPoints = 2
    samplingTime = unitTestSupport.samplingTime(simulationTime, simulationTimeStep, numDataPoints)
    dataLog = []
    for i in range(len(pos_lmk)):
        dataLog.append(camera.landmarkOutMsgs[i].recorder(samplingTime))
        scSim.AddModelToTask(simTaskName, dataLog[i])
    # Run the sim
    scSim.InitializeSimulation()
    scSim.ConfigureStopTime(simulationTime)
    scSim.ExecuteSimulation()
    # Get the logged data
    isvisibleLmk = np.zeros((2, n_lmk))
    pixelLmk = np.zeros((2, n_lmk, 2))
    for i in range(n_lmk):
        isvisibleLmk[:, i] = dataLog[i].isVisible
        pixelLmk[:, i, 0:2] = dataLog[i].pL
    # Define expected values
    # The corners shall correspond to the maximum pixel resolution
    # The default behaviour for a point lying in the origin is +1
    accuracy = 1e-8
    ref_pixel = np.array([[1, 1],
                         [1, 1536/2],
                         [1, -1536/2],
                         [-2048/2, 1],
                         [2048/2, 1],
                         [0, 0],
                         [0, 0],
                         [0, 0],
                         [0, 0],
                         [0, 0]])
    ref_isvisible = np.array([1, 1, 1, 1, 1, 0, 0, 0, 0, 0])
    # Compare to expected values
    pixel_worked = pixelLmk[0, :, :] == pytest.approx(ref_pixel, accuracy)
    isvisible_worked = isvisibleLmk[0, :] == pytest.approx(ref_isvisible, accuracy)
    assert (pixel_worked and isvisible_worked) 
if __name__ == '__main__':
    test_visibility()