import os, json, numpy, pygame, time, threading
from renderer import *
from copy import deepcopy

groundControlPath = "GroundControlFiles"
configPath = os.path.join("ConfigFiles", "OrbitSim")
configFilename = os.path.join(configPath, "Universe.cfg")
mapFilename = os.path.join(configPath, "Map.png")

def config():
    """Returns the config dictionary. Generates with default values if no config dictionary exists."""

    if not os.path.exists(configPath):
        os.makedirs(configPath)
    if not os.path.exists(configFilename):
        #generate default
        config_dic = {
                "G": 6.674e-11,
                "earthMass": 5.972e24, #in kg
                "earthRadius": 6378000, #meters
                "timeScale": 1 #higher number go faster wheeeeee
            }
        with open(configFilename, "w") as file:
            json.dump(config_dic, file, indent = 4)
            return config_dic
    else:
        with open(configFilename) as file:
            return json.load(file)

class OrbitingBody:
    """a zero-mass point object parented to a planet"""
    def __init__(self, location:Point, velocity:Point, name, displaySize, parentPlanet):
        self.location = location
        self.velocity = velocity
        self.name = name
        self.displaySize = displaySize #the size of the object on camera in pixels, for visibility reasons
        self.parentPlanet = parentPlanet

class Planet:
    """A massive body at 0,0,0 and a given radius."""
    def __init__(self, name, mass, radius, rotationPeriod, location:Point = deepcopy(Point.zero)):
        """Rotation period given in seconds."""
        self.location = location
        self.name = name
        self.mass = mass
        self.radius = radius
        self.rotationPercentage = 0.00
        self.rotationPeriod = rotationPeriod

    def rotate(self, timeDelta):
        self.rotationPercentage += timeDelta*100/self.rotationPeriod
        if self.rotationPercentage >= 100.0:
            self.rotationPercentage -= 100.0

    def sphericalToLatLong(self, theta, phi):
        """Converts theta and phi spherical coordinates to latitude and longitude. -> lat, long"""
        rotRadian = self.rotationPercentage/100 * 2 * math.pi
        lat = math.degrees(phi - (math.pi/2)) #negative lat is north, positive is south
        long = theta - rotRadian #positive long is east, negative is west
        if long < -math.pi:
            long += math.pi*2
        elif long > math.pi:
            long -= math.pi*2 
        return (lat, math.degrees(long))

class DisplayPoint:
    """A single point of any color"""
    def __init__(self, location, color):
        self.location = location
        self.color = color

class DecayPoint(DisplayPoint):
    """A display point that slowly fades to black"""
    decayTick = 1
    currentDecayTick = 0
    color = (255,255,255,255)

    def update(self):
        self.currentDecayTick += 1
        if self.currentDecayTick >= self.decayTick:
            self.currentDecayTick = 0
            self.color = (self.color[0], self.color[1], self.color[2], (max((self.color[3]-5, 0))))

    def copy(self):
        """returns a distinct copy of the point"""
        return DecayPoint(self.location, self.color)

Planet.Earth = Planet("Earth", config()["earthMass"], config()["earthRadius"], 86400)

def physicsUpdate(objects, orbitlines, deltaTime):
    """updates the positions of all orbiting objects in [objects] with timestep deltaTime"""
    for obj in objects:
        if type(obj).__name__ == "OrbitingBody":
            orbitlines.append(DecayPoint(deepcopy(obj.location), (255,255,255,255)))
            if len(orbitlines) > 100:
                orbitlines.pop(0)
            accel = Point.scalarMult(Point.subtract(obj.location, obj.parentPlanet.location).normalize(),-(config()["G"] * obj.parentPlanet.mass)/(Point.subtract(obj.location, obj.parentPlanet.location).magnitude() ** 2))
            obj.velocity = Point.add(obj.velocity, Point.scalarMult(accel, deltaTime))
            obj.location = Point.add(obj.location, Point.scalarMult(obj.velocity, deltaTime))
        elif type(obj).__name__ == "Planet":
            obj.rotate(deltaTime)
    for line in orbitlines:
        line.update()

if __name__=="__main__":
    pygame.init()
    pygame.display.set_caption("Spinny")

    window = pygame.display.set_mode((900, 900))
    resolutionDownscaling = 2
    pygame.display.flip()

    FPS = 144 #max framerate
    frameTime = 1/144

    running = True
    display = False
    thisEarth = deepcopy(Planet.Earth)
    sat = OrbitingBody(Point(0, config()["earthRadius"], config()["earthRadius"] - 800000), Point(-8900,0,0), "BoSLOO", 5, thisEarth)
    orbitlines = []
    renderObjects = [thisEarth, sat, orbitlines]
    clock = pygame.time.Clock()
    mapThread = threading.Thread()

    save = False
    
    clock.tick(FPS)

    while running:
        clock.tick(FPS)
        if display:
            #deltaTime = frameTime * config()["timeScale"]    
            deltaTime = (clock.get_time()/1000) * config()["timeScale"]      
            physicsUpdate(renderObjects, orbitlines, deltaTime)
            camera.renderFrame(save=save)
            save=False
            pygame.display.flip()

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.MOUSEBUTTONDOWN:
                if not display:
                    display = True
                    camera = Camera(window, Point(10 * config()["earthRadius"], 0, 0), thisEarth, renderObjects)
                    camera.renderFrame()
                    pygame.display.flip()
                else:
                    save = True
                    if not mapThread.is_alive():
                        mapThread = threading.Thread(target=camera.saveGroundTrack())
                        mapThread.start()
        
        #time.sleep(frameTime)

    pygame.quit()

    print("Bye!")