# -------------------------------------------------------------------
# ORTHOGRAPHIC
# Your personal aerial satellite. Always on. At any altitude.*
# Developed by MarStrMind
# License: Open Software License 3.0
# Up to date version always on marstr.online
# -------------------------------------------------------------------
# orthographic.py
# Main class which handles the generation of the ortho tile.
# -------------------------------------------------------------------

import math
import os
import glob
from defines import *
from log import *
from osmxml import *
from maskgen import *
from layergen import *
from photogen import *
from xp_scenery import *


# The main class which handles the rest
class mstr_orthographic:

    # Constructor of class. Takes longitude and latitude.
    def __init__(self, lat, lng, outfolder, pwd):
        self._lat  = lat
        self._long = lng
        self._output = outfolder
        self._pwd = pwd
        self._vstep = self._findVerticalStepping()
        self._latlngfld = self.latlng_folder([lat,lng])
        mstr_msg("orthographic", "Initiated with LAT: " + str(lat) + ", LNG: " + str(lng))


    # It did happen that the generation of photos crashed as, for some reason,
    # a file in _cache was apparently used by another process (hint: it was
    # not). I therefore need this test before deleting a file in _cache, so 
    # that generation of the orthos can move forward.
    def _isFileAccessibleWin(self, src):
        a = False
        if os.path.isfile(src) == True:
            try:
                os.rename(src, src)
                a = True
            except OSError as e:
                a = False
        return a
    
    # Need a same call for POSIX
    def _isFileAccessiblePosix(self, src):
        a = True
        if os.access(src, os.W_OK) == False:
            a = False
        return a


    # This will determine the vertical stepping in degrees in order to generate
    # masks with a 1:1 square ratio. This is important as X-Plane textures for
    # orthos can only be a power of 2, such as 2048x2048
    def _findVerticalStepping(self):
        scale = 1 / math.cos(math.radians(self._lat))
        maxlat = (1 / scale) * mstr_zl_18
        return maxlat
    

    # To write down X-Plane .ter files, we will need to know the exact size
    # of the particular longitude we are in, as this value varies depending
    # on where you are on a sphere.
    # Returned values is in meters.
    # The current latitude is needed.
    def _findWidthOfLongitude(self, lat):
        dm = math.cos(math.radians(lat)) * 111.321 # <- 1 deg width at equator in km
        return round(dm * 1000, 3)


    # Builds and processes the tile with everything required, in one call.
    def _buildTile(self):
        mstr_msg("orthographic", "Beginning construction of tile")

        # We need to know which platform we are on
        os_platform = os.name

        # Create the _cache folder, should it not exist.
        # Temporary images for the ortho tile generation go here
        if not os.path.exists(self._output + "/_cache"):
            os.makedirs(self._output + "/_cache")
            mstr_msg("orthographic", "Created _cache folder.")
        
        # Generate the Tiles/lat-lng folder for the finished tile
        if not os.path.exists(self._output + "/z_orthographic"):
            os.makedirs(self._output + "/z_orthographic")
            mstr_msg("orthographic", "Created z_orthographic folder")

        # Generate the orthos folder
        if not os.path.exists(self._output + "/z_orthographic/orthos"):
            os.makedirs(self._output + "/z_orthographic/orthos")
            mstr_msg("orthographic", "Created tile orthos folder")
        if not os.path.exists(self._output + "/z_orthographic/orthos" + self._latlngfld):
            os.makedirs(self._output + "/z_orthographic/orthos/" + self._latlngfld, exist_ok=True)

        # Generate the database folder
        if not os.path.exists(self._output + "/z_orthographic/data"):
            os.makedirs(self._output + "/z_orthographic/data")
            mstr_msg("orthographic", "Created tile database folder")
        if not os.path.exists(self._output + "/z_orthographic/data/" + self._latlngfld):
            os.makedirs(self._output + "/z_orthographic/data/" + self._latlngfld)

        # X-Plane specific
        if mstr_xp_genscenery == True:
            btnum = self.find_earthnavdata_number()
            btstr = self.latlng_folder(btnum)
            if not os.path.exists(self._output + "/z_orthographic/terrain"):
                os.makedirs(self._output + "/z_orthographic/terrain")
                mstr_msg("orthographic", "[X-Plane] Created terrain files folder")
            if not os.path.exists(self._output + "/z_orthographic/terrain/" + self._latlngfld):
                os.makedirs(self._output + "/z_orthographic/terrain/" + self._latlngfld)
            if not os.path.exists(self._output + "/z_orthographic/Earth nav data"):
                os.makedirs(self._output + "/z_orthographic/Earth nav data")
                mstr_msg("orthographic", "[X-Plane] Created Earth nav folder")
            if not os.path.exists(self._output + "/z_orthographic/Earth nav data/" + btstr):
                os.makedirs(self._output + "/z_orthographic/Earth nav data/" + btstr)
            if mstr_xp_scn_normalmaps == True:
                if not os.path.exists(self._output + "/z_orthographic/normals"):
                    os.makedirs(self._output + "/z_orthographic/normals")
                    mstr_msg("orthographic", "[X-Plane] created tile normal maps folder")
                if not os.path.exists(self._output + "/z_orthographic/normals/" + self._latlngfld):
                    os.makedirs(self._output + "/z_orthographic/normals/" + self._latlngfld)
        
        # The tile is constructed of many smaller parts. We walk through the
        # smallest possible, from which the bigger ones are later built.
        bb_lat = self._lat
        bb_lng = self._long
        bb_lat_edge = self._lat+self._vstep
        bb_lng_edge = self._long+mstr_zl_18
        cur_tile_x = 1
        cur_tile_y = 1
        osmxml = mstr_osmxml(0,0)
        mstr_msg("orthographic", "Set initial coordinates and bounding box for OSM acquisition")

        # The highest encountered tile numbers
        # This is needed to produce the zoom level 16 images
        top_lat = 1
        top_lng = 1

        # We need to know the highest possible latitude and longitude tile numbers,
        # in case we render at the edge
        mlat = 1
        mlng = 1
        while bb_lat < self._lat + 1:
            bb_lat = bb_lat + self._vstep
            mlat = mlat+1
        while bb_lng < self._long + 1:
            bb_lng = bb_lng + mstr_zl_18
            mlng = mlng+1
        mstr_msg("orthographic", "Max lat tile: " + str(mlat) + " - max lng tile: " + str(mlng))
        maxlatlng = [ mlat, mlng ]

        # Reset these two
        bb_lat = self._lat
        bb_lng = self._long

        # For X-Plane scenery generation
        xp_datagroup = 1

        # Previously, I downloaded all XML files in one go - but to ease the
        # stress on OSM servers and my server, we will do acquire the data
        # only for the current processed part of the tile.
        while bb_lat < self._lat + 1:
            while bb_lng < self._long + 1:
                # Adjust bounding box
                osmxml.adjust_bbox(bb_lat, bb_lng, bb_lat_edge, bb_lng_edge)
                mstr_msg("orthographic", "Adjusted bounding box for XML object")

                # Determine what to do... maybe work was interrupted
                if os.path.isfile(mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(cur_tile_y) + "_" + str(cur_tile_x) + ".dds") == False:

                    # Let the user know
                    mstr_msg("orthographic", "Generating missing orthophoto " + str(cur_tile_y) + "-" + str(cur_tile_x))

                    # Get the data
                    osmxml.acquire_osm(cur_tile_y, cur_tile_x) # <- This acquires current OSM info
                    mstr_msg("orthographic", "Acquired current OSM info from marstr.online repository")

                    # Check for work to be done
                    layers = self.determineLayerWork()

                    # We need to walk through the array of layers,
                    # in their z-order.
                    # For each layer, we will generate the mask, the layer image
                    # itself, and finally, compose the ortho photo.
                    mstr_msg("orthographic", "Beginning generation of layers")

                    curlyr = 1
                    for layer in layers:
                        # Let the user know
                        mstr_msg("orthographic", "Processing layer " + str(curlyr) + " of " + str(len(layers)))

                        # Generate the mask
                        mg = mstr_maskgen( [self._lat, cur_tile_y, self._long, cur_tile_x], self._vstep, layer[0], layer[1], layer[2])
                        if mstr_xp_genscenery == True:
                            mg._set_xpscenery_datagroup(xp_datagroup)
                        mg._build_mask()
                        
                        # Generate the layer
                        lg = mstr_layergen(layer[0], layer[1], self._lat, cur_tile_y, self._long, cur_tile_x, layer[2])
                        lg.set_max_latlng_tile(maxlatlng)
                        lg.set_latlng_folder(self._latlngfld)
                        #lg.open_db()
                        lg.open_tile_info()
                        lg.genlayer()
                        curlyr = curlyr+1
                    mstr_msg("orthographic", "All layers created")

                    # We should have all layers now.
                    # Snap a photo with our satellite :)
                    mstr_msg("orthographic", "Generating ortho photo")
                    pg = mstr_photogen(self._lat, self._long, cur_tile_y, cur_tile_x,  maxlatlng[0], maxlatlng[1])
                    pg.genphoto()
                    mstr_msg("orthographic", " -- Ortho photo generated -- ")
                    print("")
                    print("")

                # Adjust longitude coordinates
                cur_tile_x = cur_tile_x+1
                bb_lng = bb_lng + mstr_zl_18
                bb_lng_edge = bb_lng_edge + mstr_zl_18
                mstr_msg("orthographic", "Adjustment of longitude performed")
                # Adjust peak longitude tile number
                if cur_tile_x > top_lng:
                    top_lng = cur_tile_x

                # Clear out cache
                if mstr_clear_cache == True:
                    ch = glob.glob(mstr_datafolder + "_cache/*")
                    for f in ch:
                        if os_platform == "nt":
                            if self._isFileAccessibleWin(f) == True:
                                os.remove(f)
                        if os_platform == "posix":
                            if self._isFileAccessiblePosix(f) == True:
                                os.remove(f)
                    mstr_msg("orthographic", "Cleared cache")
            

            # Adjust latitude and all other values when we get here
            cur_tile_y = cur_tile_y+1
            cur_tile_x = 1
            bb_lng = self._long
            bb_lng_edge = self._long + mstr_zl_18
            bb_lat = bb_lat + self._vstep
            bb_lat_edge = bb_lat_edge + self._vstep
            mstr_msg("orthographic", "Adjustment of latitude performed")
            # Adjust peak latitude number
            if cur_tile_y > top_lat:
                top_lat = cur_tile_y
        
        mstr_msg("orthographic", "Generation of all tiles completed!")


        # Complete scenery
        if mstr_xp_genscenery == True:
            scn = mstr_xp_scenery(self._lat, self._long, mlat, mlng, self._vstep, self._latlngfld)
            scn.acquire_elevation_data()
            scn.acquire_xes_data()
            scn.build_mesh_script()
            scn.build_mesh()
            scn.build_ter_files()
            mstr_msg("orthographic", "[X-Plane] Mesh built, and scenery completed")
        
        mstr_msg("orthographic", "Final step completed.")
        mstr_msg("orthographic", "Tile data in: " + self._output + "z_orthographic/" + self._latlngfld)
        print("")
        mstr_msg("orthographic", "Thanks for using Orthographic! -- Best, Marcus")
        print("")

        # Let's leave this out for the moment
        """
        mstr_msg("orthographic", "Generating ZL16 tiles and keeping airport tiles")
        tg = mstr_tilegen(self._lat, self._lng, self._vstep, top_lat, top_lng)
        tg.genTiles()
        mstr_msg("orthographic", "Final step completed.")
        print("")
        mstr_msg("orthographic", "Tile data in: " + mstr_datafolder + "/Tiles/" + str(self._lat) + "_" + self._lng)
        print("")
        print("")
        mstr_msg("orthographic", "Thanks for using Orthographic! -- Best, Marcus")
        print("")
        """





    # Checks which layers need to be generated, and what kind of layer it is
    def determineLayerWork(self):

        mstr_msg("orthographic", "Checking for work to be performed")

        layers = []

        tilexml = mstr_datafolder + "_cache/tile.xml"
        xml = mstr_osmxml(0,0)
        way = xml.acquire_waypoint_data(tilexml)
        rls = xml.acquire_relations(tilexml)

        for l in mstr_ortho_layers:
            # Check if there is anything to render
            has_way = False
            has_rls = False
            for w in way:
                if w[2] == l[0] and w[3] == l[1]:
                    has_way = True
                    break
            for r in rls:
                if l[0] in r[1] and l[1] in r[1]:
                    has_rls = True
                    break
            
            if has_way == True or has_rls == True:
                mstr_msg("orthographic", "Adding: " + l[0]+":"+l[1])
                is_line = False
                for s in mstr_ortho_layers:
                    if s[0] == l[0] and s[1] == l[1]:
                        if isinstance(s[2], int) == False:
                            is_line = False
                            break
                        if isinstance(s[2], int) == True:
                            is_line = True
                            break
                ly = (l[0], l[1], is_line)
                layers.append(ly)

        mstr_msg("orthographic", "A total of " + str(len(layers)) + " layers were found")
        return layers
    

    # Construct a folder name for latitude and longitude
    def latlng_folder(self, numbers):
        fstr = ""
        if numbers[0] >= 0: fstr = "+"
        if numbers[0] <  0: fstr = "-"
        if abs(numbers[0]) < 10: fstr = fstr + "0" + str(numbers[0])
        if abs(numbers[0]) >= 10 and numbers[0] <= 90: fstr = fstr + str(numbers[0])

        if numbers[1] >= 0: fstr = fstr + "+"
        if numbers[1] <  0: fstr = fstr + "-"
        if abs(numbers[1]) < 10: fstr = fstr + "00" + str(numbers[1])
        if abs(numbers[1]) >= 10 and numbers[0] <= 99: fstr = fstr + "0" + str(numbers[1])
        if abs(numbers[1]) >= 100 : fstr = fstr + str(numbers[1])

        return fstr


    # Find the next "by-ten" numbers for the current latitude and longitude
    def find_earthnavdata_number(self):
        earthnavdata = []
        lat = abs(int(self._lat / 10) * 10)
        lng = abs(int(self._long / 10) * 10)
        earthnavdata.append(lat)
        earthnavdata.append(lng)
        return earthnavdata