from qgis.PyQt.QtCore import (QCoreApplication, QT_TRANSLATE_NOOP)
from qgis.core import (
  QgsCoordinateReferenceSystem,
  QgsProcessingContext,
  QgsProcessingFeedback,
  QgsProcessingParameterExtent,
  QgsProcessingParameterDistance,
  QgsProcessingParameterCrs, 
  QgsProcessingParameterFeatureSink,
  QgsProcessingParameterRasterDestination,
  QgsProcessingParameterString,
  QgsProcessingParameterBoolean,
  QgsRasterLayer
  )
from qgis import processing

from .fetchabstract import fetchabstract

import sys
import math
import os
import requests
import itertools
import re
import shutil
import glob

class fetchdemrastersrtm(fetchabstract):
  
  PARAMETERS = {  
    "FETCH_EXTENT": {
      "ui_func": QgsProcessingParameterExtent,
      "ui_args":{
        "description": QT_TRANSLATE_NOOP("fetchdemrastersrtm","Extent for fetching data")
      }
    },
    "TARGET_CRS": {
      "ui_func": QgsProcessingParameterCrs,
      "ui_args": {
        "description": QT_TRANSLATE_NOOP("fetchdemrastersrtm","Target CRS (Cartesian coordinates)")
      }
    },
    "BUFFER": {
      "ui_func": QgsProcessingParameterDistance,
      "ui_args": {
        "description": QT_TRANSLATE_NOOP("fetchdemrastersrtm","Buffer of the fetch area (using Target CRS)"),
        "defaultValue": 0.0,
        "parentParameterName": "TARGET_CRS"
      }
    },
    "VECTORISE": {
      "ui_func": QgsProcessingParameterBoolean,
      "ui_args": {
        "description": QT_TRANSLATE_NOOP("fetchdemrasterja","Output elevation points as vector layer"),
        "defaultValue": True
      }
    },
    "WEBFETCH_URL": {
      "ui_func": QgsProcessingParameterString,
      "ui_args": {
        "optional": True,
        "description": QT_TRANSLATE_NOOP("fetchdemrastersrtm","Base-URL of the SRTM data"),
        "defaultValue": "https://e4ftl01.cr.usgs.gov/DP133/SRTM/SRTMGL1.003/2000.02.11/"
      }
    },
    "WEBLOGIN_URL": {
      "ui_func": QgsProcessingParameterString,
      "ui_args": {
        "optional": True,
        "description": QT_TRANSLATE_NOOP("fetchdemrastersrtm","Login-URL of the SRTM data"),
        "defaultValue": "https://urs.earthdata.nasa.gov"
      }
    },
    "USERNAME": {
      "ui_func": QgsProcessingParameterString,
      "ui_args": {
        "description": QT_TRANSLATE_NOOP("fetchdemrastersrtm","Username to login SRTM data system")
      }
    },
    "PASSWORD": {
      "ui_func": QgsProcessingParameterString,
      "ui_args": {
        "description": QT_TRANSLATE_NOOP("fetchdemrastersrtm","Password to login SRTM data system")
      }
    },
    "OUTPUT": {
      "ui_func": QgsProcessingParameterFeatureSink,
      "ui_args": {
        "description": QT_TRANSLATE_NOOP("fetchdemrastersrtm","Elevation points (DEM)")
      }
    },
    "OUTPUT_RASTER": {
      "ui_func": QgsProcessingParameterRasterDestination,
      "ui_args": {
        "description": QT_TRANSLATE_NOOP("fetchdemrastersrtm","Elevation raster (DEM)" )
      }
    }
  }  
  
  def checkFetchArea(self):
    # check whether the CRS is long-lat coordinates
    if self.FETCH_AREA.crs().isGeographic() is not True:
      sys.exit(self.tr("The CRS is NOT a Geographic Coordinate System"))
      
    if self.FETCH_AREA.yMinimum() < -56 or self.FETCH_AREA.yMaximum() > 59 : 
      sys.exit(self.tr("The extent is out of SRTM-covered area"))

  def setWebFetchArgs(self, parameters, context, feedback):
    
    self.WEBFETCH_ARGS["API_METHOD"] = "get"
    self.WEBFETCH_ARGS["API_PARAMETERS"] = []
    
    # set url and file names
    lat_min = math.floor(self.FETCH_AREA.yMinimum())
    lat_max = math.ceil(self.FETCH_AREA.yMaximum())
    lng_min = math.floor(self.FETCH_AREA.xMinimum())
    lng_max = math.ceil(self.FETCH_AREA.xMaximum())
    
    base_url = self.parameterAsString(parameters, "WEBFETCH_URL", context)
    
    for lat, lng in itertools.product(list(range(lat_min, lat_max)), list(range(lng_min, lng_max))):
      lng_str = f"{lng:+04}".replace("+","E").replace("-","W")
      lat_str = f"{lat:+03}".replace("+","N").replace("-","S")
      self.WEBFETCH_ARGS["API_PARAMETERS"].append({"url": base_url + f"{lat_str}{lng_str}.SRTMGL1.hgt.zip"})    
    
    # set login data and login
    login_url = self.parameterAsString(parameters, "WEBLOGIN_URL", context)
    session = requests.Session()
    login_html = session.get(login_url).text
    token = re.search(
      r'<form[^>]*id="login".*?>[\s\S]*?<input[^>]*name="authenticity_token"[^>]*value="([^"]*)"[^>]*>', 
      login_html
    ).group(1)
    
    self.WEBFETCH_ARGS["LOGIN"] = {
      "URL": login_url + "/login",
      "PARAMETERS": {
        "utf8": "✓",
        "authenticity_token": token,
        "username": self.parameterAsString(parameters, "USERNAME", context),
        "password": self.parameterAsString(parameters, "PASSWORD", context),      
      }
    }
    
    session.post(
      url = self.WEBFETCH_ARGS["LOGIN"]["URL"], 
      data = self.WEBFETCH_ARGS["LOGIN"]["PARAMETERS"]
    )
    
    self.WEBFETCH_ARGS["LOGIN"]["SESSION"] = session
    
    # set that it is zipped file
    self.WEBFETCH_ARGS["ARCHIVED"] = ".zip"
    
    if self.WEBFETCH_ARGS["API_PARAMETERS"] is not None and self.WEBFETCH_ARGS["LOGIN"] is not None:
          self.WEBFETCH_ARGS["SET"] = True
    
  
  
  # create the raster from downloaded hgt files
  def fetchFeaturesFromWeb(self, parameters: dict, context: QgsProcessingContext, feedback: QgsProcessingFeedback) -> None:
    super().fetchFeaturesFromWeb(parameters, context, feedback)
    
    # unpack zip files
    files_to_be_merged = []
    for zip_dir in self.WEBFETCH_ARGS["DOWNLOADED_FILE"]:
      os.rename(zip_dir, zip_dir + ".zip")
      shutil.unpack_archive(zip_dir + ".zip", zip_dir)
      # archived_files = glob.glob(os.path.join(zip_dir, "*"))
      for unpacked_file in glob.glob(os.path.join(zip_dir, "*")):
        if re.search(".hgt", unpacked_file):
          files_to_be_merged.append(unpacked_file)
    
    # if there are no files
    if len(files_to_be_merged) == 0:
      sys.exit(self.tr("No SRTM files were downloaded!"))
    
    # if there are only a file
    elif len(files_to_be_merged) == 1:
      self.FETCH_FEATURE = QgsRasterLayer(files_to_be_merged[0], "SRTM")
    
    # if there are multiple files
    else:
      rasters = []
      for file in files_to_be_merged:
        rasters.append(QgsRasterLayer(file + "|layername=1", "SRTM"))
      self.FETCH_FEATURE = processing.run(
        "gdal:merge",{
          "INPUT": rasters,
          "OUTPUT": "TEMPORARY_OUTPUT"
        }
      )["OUTPUT"]
    
  # initialize of the algorithm  
  def initAlgorithm(self, config):    
    self.initUsingCanvas()
    self.initParameters()

  # execution of the algorithm
  def processAlgorithm(self, parameters, context, feedback):    
    # set the fetch area, of which CRS is the same as SRTM's
    self.setFetchArea(parameters,context,feedback,QgsCoordinateReferenceSystem("EPSG:4326"))
    self.checkFetchArea()
    
    # set the meta information for obtaining SRTM data
    self.setWebFetchArgs(parameters, context, feedback)
    
    # download files using the session info
    self.fetchFeaturesFromWeb(parameters, context, feedback)
    
    # clip the raster because it is too large as a point vector
    dem_raster_clipped = processing.run(
      "gdal:cliprasterbyextent", 
      {
        "INPUT": self.FETCH_FEATURE,
        "PROJWIN": self.FETCH_AREA,
        "OUTPUT": self.parameterAsOutputLayer(parameters, "OUTPUT_RASTER", context)
      }
    )["OUTPUT"]
    
    if self.parameterAsBoolean(parameters, "VECTORISE", context):
      # create vector layer from the raster
      dem_raw = processing.run(
        "native:pixelstopoints",{
          "INPUT_RASTER": dem_raster_clipped,
          "RASTER_BAND": 1,
          "FIELD_NAME": "alti",
          "OUTPUT": "TEMPORARY_OUTPUT"
        }
      )["OUTPUT"]
      
      # CRS transform    
      dem_transformed = self.transformToTargetCrs(parameters,context,feedback,dem_raw)
        
      # substitute self constant with the fetched vector layer
      dem_final = dem_transformed    
      
      (sink, dest_id) = self.parameterAsSink(
            parameters, "OUTPUT", context,
            dem_final.fields(), dem_final.wkbType(), dem_final.sourceCrs()
            )
      sink.addFeatures(dem_final.getFeatures())
    else:
      dest_id = None
    
    return {"OUTPUT": dest_id, "OUTPUT_RASTER": dem_raster_clipped}   
    
  
  def displayName(self):
    return self.tr("Elevation points (SRTM raster)")

  def group(self):
    return self.tr('Fetch geometries')

  def groupId(self):
    return 'fetchgeometry'

  def createInstance(self):
    return fetchdemrastersrtm()
