import logging
import os
import subprocess

import sys
import tempfile

import datetime
import gdal
import shutil

import win32api
from osgeo import ogr
from osgeo import osr
import numpy as np

from gdalconst import *

import random
from shapely.geometry import Point, Polygon

# Path to the Farsite EXE relative to the Python Scripts directory
LCPMAKE_EXE_PATH ="../exe/lcpmake.exe"
FARSITE_EXE_PATH="../exe/TestFARSITE.exe"

def makeFarsiteInputFile( inputs_filename, ignition_filename, config):

    logging.debug('Making Farsite Input file "{0}"'.format(inputs_filename))

    try:
        f = open(inputs_filename, 'w')

        f.write('FARSITE INPUTS FILE VERSION 1.0 produced by FARSITE-STSim Python script {0}\n'.format(datetime.datetime.now()))

        #
        # ii- Generate the Farsite input file including:
        # 1. -Fire start date/time (sampled from a probability distribution)

        fire_start_day =  np.random.uniform(config.fire_season_start_julian_day,config.fire_season_end_julian_day)
        # DEVNOTE: Seems to give us grief if day_start = 1 "Invalid start time Start Date is NOT within usable range of daily weather data", so force to 2 if thats the case
        fire_start_day = max(2,fire_start_day)
        # DEVNOTE: Use a constant non-leap year year, as Farsite doesnt care about year, and we dont want to have date variations
        # due to leap years
        fire_start_datetime = datetime.datetime(2015,1,1) + datetime.timedelta(days=fire_start_day - 1.0)
        f.write('FARSITE_START_TIME: {:%m %d %H%M}\n'.format(fire_start_datetime))

        # 2. - Fire end date/time (use a fixed duration for each ignition for now. Can conduct sensitivity analysis)
        fire_duration_mins  = np.random.poisson(config.mean_fire_duration_hours * 60.0)
        # DEVNOTE: It seems that Farsite wont process if duration less than 10 minutes.
        # TODO: Ask Leo what he want to do with this. Might want to restrict in UI to >=10
        fire_duration_mins = max(fire_duration_mins, 10.0)
        logging.debug('Fire duration (mins)= {}'.format(fire_duration_mins))

        fire_end_day = fire_start_day + fire_duration_mins/(24.0 * 60.0)
        # TODO: Ask Leo - I dont think I want to do the following, becuase you'll end up with a clipping of duration.
        # fire_end_day = min(fire_end_day,config.fire_season_end_julian_day)
        fire_end_datetime = datetime.datetime(2015,1,1) + datetime.timedelta(days= fire_end_day -1.0)
        # Clip within the year
        fire_end_datetime = min(datetime.datetime(2015,12,31,23,59,59),fire_end_datetime)
        f.write('FARSITE_END_TIME: {:%m %d %H%M}\n'.format(fire_end_datetime))

        # 3. - Timestep resolution (minutes)
        #DEVNOTE: In Help file says:
        # Surface Fire/Timber Timestep = 20 to 120 minutes
        # Surface / brush, dry grass = 10 to 20 minutes
        #surface extreme or or torching/crowning/ all fuel = 5 to 10 minutes
        f.write('FARSITE_TIMESTEP: {0}\n'.format(config.time_step_resolution_minutes))

        # 4. - distance and perimeter resolutions - match ST-Sim
        # Match cell size of input rasters
        gdal.UseExceptions()
        ref_raster = gdal.Open(config.primaryStratumFile, gdal.GA_ReadOnly)
        if ref_raster is None:
            logging.error("Cannot open Source Raster file '{0}'".format(config.primaryStratumFile))
            sys.exit(1)

        f.write('FARSITE_DISTANCE_RES: {0}\n'.format(config.distance_resolution))
        f.write('FARSITE_PERIMETER_RES: {0}\n'.format(config.perimeter_resolution))

        # 5. - Fuel moisture by fuel model - need input from the user as a percent moisture by fuel model for 1 hour, 10 hour,
        # 100 hour and live woody and herbaceous fuels.  These numbers are specified as integers and may exceed 100. To begin
        # use hard coded values for extreme conditions.
        # Ex:
        # Fuel mode entry format:
        # Model  FM1   FM10  FM100   FMLiveHerb   FMLiveWoody
        # 0      2      2     3         4           5

        # FUEL_MOISTURES_DATA: 1
        # 0 3 4 8 9 70

        # FUEL_MOISTURE_XXX = 10  # 10% for now
        f.write('FUEL_MOISTURES_DATA: 1\n')
        f.write('0 {0} {1} {2} {3} {4}\n'.format(config.fuel_moisture_1,config.fuel_moisture_10,config.fuel_moisture_100,config.fuel_moisture_live_herb,config.fuel_moisture_live_woody))

        # 6. - weather stream data (wind, temperature, humidity and precip). To begin use hard coded values for extreme conditions.
        #  Example files for the Tucson area are available online.
        # http://fireweather.sc.egov.usda.gov/applications/farsite/farsite_map8.php?usr=lfrid&model=wrf&state=AZ
        # TODO: Website down "RMC Temporarily Unavailable"
        f.write('WEATHER_DATA: 365\n')
        # Weather Data Format:
        # Mth  Day  Pcp  mTH  xTH   mT xT   xH mH   Elv   PST  PET
        # Mth = month,
        # Day = day,
        # Per = precip in hundredths of an inch (integer e.g. 10 = 0.1 inches),
        # mTH = min_temp_hour 0-2400,
        # xTH = max_temp_hour 0 - 2400,
        # mT  = min_temp,
        # xT  = max_temp,
        # mH  = max_humidity
        # xH  = min_humidity,
        # Elv = elevation,
        # PST = precip_start_time 0-2400 * Optional
        # PET = precip_end_time   0-2400 * Optional
        # NOTE: do not leave any blank values

        # Weather Data Example:
        # Mth  Day  Pcp  mTH  xTH   mT xT   xH mH   Elv   PST  PET
        # 6 16 0 500 1500 38 74 58 14 6903 0 0
        # DEVNOTE: Weather data seems to need to be contiguous, and to bookend the Start Time/End Time range. Easist thing to do
        # is just spit out records for complete year
        for day in range(365):
            dt = datetime.date(2015, 1, 1) + datetime.timedelta(days=day)

            # DEVNOTE: Precipitation Times are supposed to optional, but seem to be required if a precip value of <> 0 enter, so hack in 0 and 24
            # Also, if precip = 0, then dont include Start Time/End Time
            if config.weather_precipitation ==0:
                precip_time = ''
            else:
                precip_time = '0 24'

            f.write('{:%m %d} {} {} {} {} {} {} {} {} {}\n'
                    .format(dt,
                            int(config.weather_precipitation * 100),
                            (config.weather_min_temp_hour * 100), (config.weather_max_temp_hour * 100),config.weather_min_temp,config.weather_max_temp,
                            config.weather_max_humidity, config.weather_min_humidity,
                            config.weather_elevation,precip_time
                            )
                    )

        # DEVNOTE: Hardcode units to English, to match the units in the STSim UI boilerplate
        f.write('WEATHER_DATA_UNITS: English\n\n')

        # Wind data
        f.write('WIND_DATA: 365\n')
        # Mth  Day  Hour   Speed Direction CloudCover
        for day in range(365):
            dt = datetime.date(2015, 1, 1) + datetime.timedelta(days=day)
            f.write('{:%m %d} 0 {} {} {}\n'.format(dt,
                                                    config.wind_speed,config.wind_direction, config.cloud_cover_percent))

        # DEVNOTE: Hardcode units to English, to match the units in the STSim UI boilerplate
        f.write('WIND_DATA_UNITS: English\n\n')

        # 7. - Spotting parameters: can probably be ignored or set to non-spotting behaviour defaults as we are not dealing with a forested system.
        #   a. --Spot probability
        #   b. --Spot ignition delay
        #   c. -- Minimum spot distance
        # DEVNOTE: Ignore for now. But maybe a Future
        # These are mandatory switches, so can't be ignored. Set Prob = 0.
        f.write('FARSITE_SPOT_PROBABILITY: 0.0\n')
        f.write('FARSITE_SPOT_IGNITION_DELAY: 0\n')
        f.write('FARSITE_MINIMUM_SPOT_DISTANCE: 0\n')

        # 8. Acceleration
        f.write('FARSITE_ACCELERATION_ON: {}'.format( '1' if config.farsite_acceleration == 'Yes' else '0' ))

        logging.debug("Making Farsite Input file complete.")

    finally:
        f.close()

def createFarsiteCommandFile(command_filename, lcp_filename, inputs_filename, ignitions_filename, output_dir,
                             output_files_prefix, output_type=1, barrier_filename=0):
    '''
        Create a TestFarsite command file

        TestFARSITE Usage:
        TestFARSITE [commandfile]
        Where:
                [commandfile] is the path to the command file.
        The command file contains command lines for multiple Farsite runs, each run's command on a seperate line.
        Each command expects six parameters, all required
        [LCPName] [InputsFileName] [IgnitionFileName] [BarrierFileName] [outputDirPath] [outputsType]
        Where:
                [LCPName] is the path to the Landscape File
                [InputsFileName] is the path to the FARSITE Inputs File (ASCII Format)
                [IgnitionFileName] is the path to the Ignition shape File
                [BarrierFileName] is the path to the Barrier shape File (0 if no barrier)
                [outputDirPath] is the path to the output files base name (no extension)
                [outputsType] is the file type for outputs (0 = both, 1 = ASCII grid, 2 = FlamMap binary grid
                :param output_files_prefix:
    '''

    logging.debug('Making Farsite Command file "{0}"'.format(command_filename))

    f = open(command_filename, 'w')

    # DEVNOTE: We assume we're running on a Windows machine, as Farsite is only PC/Windows based (?)
    # DEVNOTE: It appears that Farsite command file does NOT support spaces in file paths, or wrapping with quotes, so convert to Windows short name
    f.write('{0} {1} {2} {3} {4} {5}'.format(
        getShortName(lcp_filename),
        getShortName(inputs_filename),
        getShortName(ignitions_filename),
        0 if barrier_filename ==0 else getShortName(barrier_filename),
        getShortName(output_dir) + "\\" + output_files_prefix,
        output_type))
    f.close()

def getShortName(filename):
    '''
    Get the Short Name of the specified file. This function gets the short path, but maintains the full resolution base filename.

    This is a workaround for an issue that Farsite has with the command file containing blanks in the file paths. We can't go to
    full 8.3 syntax, because this confuses dealing with Shapefiles. We were having issues with the 3rd Ignition Points file
    always gave us grief, no matter what the name.

    This is required because:
    1) The command file wont support quotes around file paths that contains spaces. So we need a way to remove spaces, thus Short Names
    2) Unfortunately Short Names aren't prefix consistent. So the short name prefix of FileName.shp isnt neccessarily the same as Filename.dbf.
    Ex:
        IT69BF~1.DBF It0001-Ts2028-ignitionPtsFile.dbf
        IT3ED5~1.PRJ It0001-Ts2028-ignitionPtsFile.prj

    :param filename: The filename we want to get the short path for
    :return: The absolute path of the file, with the directory of the file expressing in short format, but the base filename itself in full "resolution"
    '''

    file_dir = os.path.dirname(filename)
    return os.path.join(win32api.GetShortPathName(file_dir),os.path.basename(filename))

def runFarsite(command_filename):
    # iii - Invoke Farsite and wait for it to complete the run
    # TestFARSITE SampleCommand.txt

    logging.info('Running Farsite...')

    cmdLine  = '"{0}" "{1}" '.format(FARSITE_EXE_PATH,getShortName(command_filename))
    logging.debug(cmdLine)
    proc = subprocess.call(cmdLine)
    if proc <> 0:
        logging.error("Error calling Farsite:{0}".format(cmdLine))
        sys.exit(1)

    logging.info('Farsite run complete.')

def lcpMake(landscape, elevation_filename, slope_filename, aspect_filename, fuel_filename, canopy_filename, latitude):
    """
    Call the compiled lcpmake.exe c++ code to make the landscape file for Farsite from the spatial attribute files exported
    by ST-Sim and the static spatial files above.
    Example:

        lcpmake -latitude 45 -landscape ashleyTest
            -elevation "D:/FARSITE 4/Ashley/ash_elev.asc"
            -slope "D:/FARSITE 4/Ashley/ash_slope.asc"
            -aspect "D:/FARSITE 4/Ashley/ash_aspect.asc"
            -fuel "D:/FARSITE 4/Ashley/ash_fuel.asc"
            -cover "D:/FARSITE 4/Ashley/ash_canopy.asc"

    :param landscape: The name of the Landscape file (LCP), without extension
    :param elevation_filename: The full absolute name of the Elevation ASCII Grid raster file
    :param slope_filename: The full absolute name of the Slope ASCII Grid raster file
    :param aspect_filename: The full absolute name of the Aspect ASCII Grid raster file
    :param fuel_filename: The full absolute name of the Fuel ASCII Grid raster file
    :param canopy_filename: The full absolute name of the Canopy ASCII Grid raster file
    :return: Nothing

    """

    logging.info('Running LCPMake')

    # Convert the TIF raster files to ASC.
    elevation_filename_asc = convertToAAIGrid(elevation_filename)
    slope_filename_asc = convertToAAIGrid(slope_filename)
    aspect_filename_asc = convertToAAIGrid(aspect_filename)
    fuel_filename_asc = convertToAAIGrid(fuel_filename)
    canopy_filename_asc =  convertToAAIGrid(canopy_filename)

    cmdLine = '{0} -latitude {7} ' \
              '-landscape "{1}" ' \
              '-elevation "{2}" ' \
              '-slope "{3}" ' \
              '-aspect "{4}" ' \
              '-fuel "{5}" ' \
              '-cover "{6}"'.format(LCPMAKE_EXE_PATH,
                                    landscape,
                                    elevation_filename_asc,
                                    slope_filename_asc,
                                    aspect_filename_asc,
                                    fuel_filename_asc,
                                    canopy_filename_asc,
                                    latitude)

    logging.debug(cmdLine)
    proc = subprocess.call(cmdLine)
    if proc <> 0:
        logging.error("Error calling Syncrosim.Console:{0}".format(cmdLine))
        sys.exit(1)

    # Now remove the ASC files as we dont need them anymore
    os.remove(elevation_filename_asc)
    os.remove(elevation_filename_asc + '.aux.xml')
    os.remove(slope_filename_asc)
    os.remove(slope_filename_asc + '.aux.xml')
    os.remove(aspect_filename_asc)
    os.remove(aspect_filename_asc + '.aux.xml')
    os.remove(fuel_filename_asc)
    os.remove(fuel_filename_asc + '.aux.xml')
    os.remove(canopy_filename_asc)
    os.remove(canopy_filename_asc + '.aux.xml')

    logging.info('LCPMake run complete.')

def createZeroValRaster(dest_filename, source_filename):
    '''
        Create a zero value raster, using the same characteristics as the source file ( num_rows, num_cols,...)

        DEVNOTE: To generate a zero value raster using gdal_translate
        gdal_translate sclass_100x100.tif squashed.tif -scale 1 1 0 0
        DEVNOTE: To 'flatten' any raster to a single value use:
        gdal_translate sourcefile.tif dest_file.tif -scale 1 1 final_squash_value final_squash_value
        The above command doesnt honor NODATA values

    '''

    tmp_dir = tempfile.mkdtemp()

    try:

        logging.debug("dest_filename:{0}, source_filename:{1}".format(dest_filename,source_filename))

        # Delete the file it is exists
        if os.path.exists(dest_filename):
            os.remove(dest_filename)

        # Convert the source_filename raster into a Int32 datatype, so we can support NoDataValue of -9999
        tmp_raster = os.path.join(tmp_dir, "zv_raster.tif")
        convertToInt32(source_filename,tmp_raster)

        gdal.UseExceptions()
        srcData = gdal.Open(tmp_raster, gdal.GA_ReadOnly)
        if srcData is None:
            logging.error("Cannot open Source Raster file '{0}'".format(tmp_raster))
            sys.exit(1)

        # TODO: srcData.ReadAsArray() is causing Python to crash. It's probably a gdal/python binding mismatch issue. See if
        # any hints at https: // trac.osgeo.org / gdal / wiki / PythonGotchas

        x = np.array(srcData.ReadAsArray())
        # Just in case NoDataValue is 0, change to -9999.
        if srcData.GetRasterBand(1).GetNoDataValue() == 0.0:
            np.putmask(x, x == 0, -9999)

            np.putmask(x, x > 0, 0)

        # we already have the raster with exact parameters that we need
        # so we use CreateCopy() method instead of Create() to save our time
        rasterFormat = "GTiff"
        driver = gdal.GetDriverByName(rasterFormat)
        destData = driver.CreateCopy(dest_filename, srcData, 0)
        band = destData.GetRasterBand(1)
        # Change NoDataValue  to -9999.
        band.SetNoDataValue(-9999)
        band.WriteArray(x)
        logging.debug('Created new Zero Value Raster "{0}"'.format(dest_filename))
        destData = None
        srcData = None

    finally:
        # Clean up the temp files
        shutil.rmtree(tmp_dir)
        pass

def createOneValRaster(dest_filename, source_filename):
    '''
        Create a One value raster, using the same characteristics as the source file ( num_rows, num_cols,...)
    '''

    # Delete the file it is exists
    if os.path.exists(dest_filename):
        os.remove(dest_filename)

    gdal.UseExceptions()
    srcData = gdal.Open(source_filename, gdal.GA_ReadOnly)
    if srcData is None:
        logging.error("Cannot open Source Raster file '{0}'".format(source_filename))
        sys.exit(1)

    # TODO: srcData.ReadAsArray() is causing Python to crash. It's probably a gdal/python binding mismatch issue. See if
    # any hints at https: // trac.osgeo.org / gdal / wiki / PythonGotchas

    x = np.array(srcData.ReadAsArray())
    np.putmask(x, x > 0, 1)

    # we already have the raster with exact parameters that we need
    # so we use CreateCopy() method instead of Create() to save our time
    rasterFormat = "GTiff"
    driver = gdal.GetDriverByName(rasterFormat)
    destData = driver.CreateCopy(dest_filename, srcData, 0)
    destData.GetRasterBand(1).WriteArray(x)
    logging.debug('Created new One-value Raster "{0}"'.format(dest_filename))
    destData = None
    srcData = None

def convertFireIntensityRaster(reference_raster_filename, src_intensity_filename, src_ignitions_filename, converted_filename):
    '''
        Convert the Fire Intensity Raster generated by Farsite into a Transition Spatial Multiplier (TSM)
        raster suitable for import into Syncrosim. Use the Fire ignitions raster to mask the intensity.
    '''

    # DEVNOTE: FUTURE:
    # Then convert different fire intensities to different fire transition types (will need a crosswalk between continuous
    # fire intensity and fire transition type - i.e., low, mid high severity).
    # i. - Generate a spatial multiplier for each fire transition type according to the fire intensity of each cell.
    # DEVNOTE: END OF FUTURE

    reference_raster = gdal.Open(reference_raster_filename, gdal.GA_ReadOnly)
    if reference_raster is None:
        logging.error("Cannot open Farsite Reference Raster file '{0}'".format(reference_raster_filename))
        sys.exit(1)

    cols = reference_raster.RasterXSize
    rows = reference_raster.RasterYSize
    # reference_raster = None

    intensity_raster = gdal.Open(src_intensity_filename, gdal.GA_ReadOnly)
    if intensity_raster is None:
        logging.error("Cannot open Farsite Fire Intensity Raster file '{0}'".format(src_intensity_filename))
        sys.exit(1)

    raster_intensity = intensity_raster.ReadAsArray()
    x = np.array(raster_intensity)
    if (rows ,cols) != x.shape :
        xrows, xcols = x.shape
        logging.warning(
            "The Fire Intensity raster was not of the expected size ({},{}) vs ({},{}).".format(
                xrows, xcols, rows, cols))
        if cols > xcols:
            logging.warning("Resizing Fire Intensity raster by adding columns.")
            x = np.hstack((x, np.zeros((x.shape[0], cols - xcols), dtype=x.dtype)))
        else:
            logging.error("Unable to process existing Fire Intensity raster")
            sys.exit(1)

    # Convert cell value > 0 to 1. Any other value, including NO_DATA_VALUE,  = 0
    np.putmask(x, x > 1, 1)
    np.putmask(x, x < 0, 0)

    # Mask the Intensity raster with the Ignitions raster
    ignition_raster = gdal.Open(src_ignitions_filename, gdal.GA_ReadOnly)
    if ignition_raster is None:
        logging.error("Cannot open Farsite Fire Ignitions Raster file '{0}'".format(src_ignitions_filename))
        sys.exit(1)
    raster_ignitions = ignition_raster.ReadAsArray()
    y = np.array(raster_ignitions)

    if (rows ,cols) != y.shape :
        yrows, ycols = y.shape
        logging.warning(
            "The Fire Ignitions raster was not of the expected size ({},{}) vs ({},{}).".format(
                yrows, ycols, rows, cols))
        if cols > ycols:
            logging.warning("Resizing Fire Ignitions raster by adding columns.")
            y = np.hstack((y, np.zeros((y.shape[0], cols - ycols), dtype=y.dtype)))
        else:
            logging.error("Unable to process existing Fire Ignitions raster")
            sys.exit(1)

    # Test that the Ignition  and Intensity raster as the same size - we've seen cases where Farsite produces slightly mismatched
    # raster size, resulting in putmask cratering
    if x.size <> y.size:
        logging.warning("The Fire Intensity and Ignition rasters are different sizes.")
        sys.exit(1)

    # Set any ignitions pts in the massaged intensity raster
    np.putmask(x, y>=1 ,1)

    # we already have the raster with exact parameters that we need, in the reference raster. Cant use createCopy becuase of the change
    # band size different
    # Refer to array2raster(rasterfn,newRasterfn,array) in https://pcjericks.github.io/py-gdalogr-cookbook/raster_layers.html
    geotransform = reference_raster.GetGeoTransform()
    originX = geotransform[0]
    originY = geotransform[3]
    pixelWidth = geotransform[1]
    pixelHeight = geotransform[5]

    rasterFormat = "GTiff"
    driver = gdal.GetDriverByName(rasterFormat)
    outRaster = driver.Create(converted_filename, cols, rows, 1, GDT_Int32)
    outRaster.SetGeoTransform((originX, pixelWidth, 0, originY, 0, pixelHeight))
    outband = outRaster.GetRasterBand(1)
    outband.WriteArray(x)
    outband.SetNoDataValue(-9999)
    outRasterSRS = osr.SpatialReference()
    outRasterSRS.ImportFromWkt(reference_raster.GetProjectionRef())
    outRaster.SetProjection(outRasterSRS.ExportToWkt())
    outband.FlushCache()

    logging.debug('Created new Farsite TSM Raster "{0}"'.format(converted_filename))

def convertToAAIGrid(src_filename):
    # We need to convert tif -> ASC before we can use them in LCPMake
    filename_asc = src_filename.replace('.tif', '.asc')
    cmdLine = 'gdal_translate -of "AAIGrid" "{0}" "{1}"'.format(src_filename,filename_asc)
    logging.debug(cmdLine)
    proc = subprocess.call(cmdLine)
    if proc <> 0:
        logging.error("Error calling gdal_translate:{0}".format(cmdLine))
        sys.exit(1)

    return filename_asc

def convertToInt32(src_filename, dest_filename):

    filename_asc = src_filename.replace('.tif', '.asc')
    cmdLine = 'gdal_translate -ot "Int32" "{0}" "{1}"'.format(src_filename,dest_filename)
    logging.debug(cmdLine)
    proc = subprocess.call(cmdLine)
    if proc <> 0:
        logging.error("Error calling gdal_translate:{0}".format(cmdLine))
        sys.exit(1)

    return dest_filename

def generateIgnitionPoints(barrier_raster_file, ignition_pts_shapefile, num_ignitions ):

    # Generate a boundary file from the specified raster

    tmp_dir = tempfile.mkdtemp()

    try:
        # 1st create a zero value raster with the same characteristics as the specified raster
        zv_raster = os.path.join(tmp_dir, "zv_raster.tif")
        createZeroValRaster( zv_raster,barrier_raster_file)

        # gdal_polygonize
        boundary_file = os.path.join(tmp_dir, "boundary.shp")

        cmdLine = 'gdal_polygonize.bat "{0}" "{1}" -f "ESRI Shapefile"'.format(zv_raster, boundary_file)
        logging.debug(cmdLine)
        proc = subprocess.call(cmdLine)
        if proc <> 0:
            logging.error("Error calling gdal_polygonize:{0}".format(cmdLine))
            sys.exit(1)

        # OK, we've got our boundary file, so lets generate some random points
        createIgnitionPtFile(boundary_file,ignition_pts_shapefile,num_ignitions)
        logging.info('Created Ignition Point file "{0}" with {1} points'.format(ignition_pts_shapefile,num_ignitions))

    finally:
        # Clean up the temp files
        shutil.rmtree(tmp_dir)
        pass

def createIgnitionPtFile(boundary_fn, ignition_points_fn, num_ignition_points):
    """
        Create a new Point Layer containing Randomly placed Ignition points, within the constraints of the Boundary file

    @param boundary_fn: The file name of the Boundary shapefile
    @param ignition_points_fn: The filename of the output file containing the ignition points.
    @return:
    """

    inputDs = ogr.Open(boundary_fn)
    if not inputDs:
        logging.error(("Unable to open input boundary file '{0}'".format(boundary_fn)))
        sys.exit(1)

    inputLyr = inputDs.GetLayer()
    inpSRS = inputLyr.GetSpatialRef()
    (minx, maxx, miny, maxy) = inputLyr.GetExtent()

    shpdriver = ogr.GetDriverByName('ESRI Shapefile')
    if os.path.exists(ignition_points_fn):
        shpdriver.DeleteDataSource(ignition_points_fn)

    if os.path.exists(ignition_points_fn):
        sys.exit("Unable to delete old file '{0}".format(ignition_points_fn))

    output_ds = shpdriver.CreateDataSource(ignition_points_fn)
    if output_ds is None:
        logging.error('Creation of Ignition Points file "{}" failed'.format(ignition_points_fn))
        sys.exit(1)

    pts_lyr = output_ds.CreateLayer('Ignition Points', geom_type=ogr.wkbPoint, srs=inpSRS)
    if pts_lyr is None:
        logging.error("Layer creation failed.")
        sys.exit(1)

    featureDefn = pts_lyr.GetLayerDefn()
    num_created_pts = 0
    while num_created_pts < num_ignition_points:
        ign_pt = ogr.Geometry(ogr.wkbPoint)
        ign_pt.SetPoint_2D(0,random.uniform(minx, maxx), random.uniform(miny, maxy))

        # Make sure the new proposed point falls within valid area of Boundary shapefile
        inputLyr.SetSpatialFilter(ign_pt)
        if inputLyr.GetFeatureCount()==0:
            logging.debug('Skipped creating a Ignition point because outside of Boundary layer')
            continue

        num_created_pts +=1
        outFeature = ogr.Feature(featureDefn)
        outFeature.SetGeometry(ign_pt)
        pts_lyr.CreateFeature(outFeature)

        outFeature.Destroy()

    # TODO: Create a PRJ file for this shapefile
    # pu.createPrjFile(ignition_points_fn,inpSRS)

    inputLyr = None
    inputDs = None

    pts_lyr = None
    output_ds = None

def compareRasterRowsCols(reference_raster_filename, comparison_raster_filename):
    """
        Verify that the raster file specified has the same number of rows and columns as the reference raster.

    @param reference_raster_filename: The filename of the raster we're using as reference
    @:param comparison_raster_filename: The filename of the raster we're comparing the reference to
    @return: True is rows and cols the same
    """

    gdal.UseExceptions()
    raster = gdal.Open(reference_raster_filename, gdal.GA_ReadOnly)
    if raster is None:
        logging.error("Cannot open Reference Raster file '{0}'".format(reference_raster_filename))
        sys.exit(1)

    num_rows = raster.RasterYSize
    num_cols = raster.RasterXSize
    raster = None

    raster = gdal.Open(comparison_raster_filename, gdal.GA_ReadOnly)
    if raster is None:
        errMsg = "Cannot open Comparison Raster file '{0}'".format(comparison_raster_filename)
        logging.error(errMsg)
        sys.exit(errMsg)

    if num_rows != raster.RasterYSize :
        return False

    if num_cols != raster.RasterXSize:
        return False

    return True

def verifyRasterMetadata(config):
    """
        Verify that the raster file specified in the config object have sufficiently matching metadata. If not, do a sys.exit()

    @param config: The configuration object, containing the file names for all the input files
    @return:
    """

    logging.info("Verifying Raster Metadata...")

    gdal.UseExceptions()
    raster = gdal.Open(config.primaryStratumFile, gdal.GA_ReadOnly)
    if raster is None:
        logging.error("Cannot open Source Raster file '{0}'".format(config.primaryStratumFile))
        sys.exit(1)

    num_rows = raster.RasterYSize
    num_cols = raster.RasterXSize
    geotransform = raster.GetGeoTransform()
    originX = geotransform[0]
    originY = geotransform[3]
    pixelWidth = geotransform[1]
    pixelHeight = geotransform[5]

    raster = None

    files = [config.aspect_raster_file, config.elevation_raster_file, config.slope_raster_file]
    for file in files:
        raster = gdal.Open(file, gdal.GA_ReadOnly)
        if raster is None:
            errMsg = "Cannot open Source Raster file '{0}'".format(config.primaryStratumFile)
            logging.error(errMsg)
            sys.exit(errMsg)

        geotransform = raster.GetGeoTransform()

        if num_rows != raster.RasterYSize:
            errMsg = "The raster '{0}' does not match the Number of Rows of the Primary Stratum raster specified in Initial Conditions Spatial.".format(file)
            logging.error(errMsg)
            sys.exit(errMsg)

        if num_cols != raster.RasterXSize:
            errMsg ="The raster '{0} does not match the Number of Columns of the Primary Stratum raster specified in Initial Conditions Spatial.".format(file)
            logging.error(errMsg)
            sys.exit(errMsg)

        # X Origin. Test no more than 1/2 of pixel width
        diff = abs(originX - geotransform[0])
        if diff > (pixelWidth / 2.0):
            errMsg ="The raster '{0}' does not match the X Origin of the Primary Stratum raster specified in Initial Conditions Spatial.".format(file)
            logging.error(errMsg)
            sys.exit(errMsg)

        # Y Origin. Test no more than 1/2 of pixel height
        diff = abs(originY - geotransform[3])
        if diff > abs(pixelHeight / 2.0):
            diff = abs(originY - geotransform[3])
            errMsg ="The raster '{0}' does not match the Y Origin of the Primary Stratum raster specified in Initial Conditions Spatial.".format(file)
            logging.error(errMsg)
            sys.exit(errMsg)

        # Pixel Width. Test for 1% diff
        diff  = abs(pixelWidth - geotransform[1])
        if abs(diff / pixelWidth) > .01:
            errMsg ="The raster '{0}' does not match the Pixel Width of the Primary Stratum raster specified in Initial Conditions Spatial.".format(file)
            logging.error(errMsg)
            sys.exit(errMsg)

        # Pixel Height. Test for 1% diff
        diff = abs(pixelHeight - geotransform[5])
        if abs(diff / pixelHeight) > .01:
            errMsg = "The raster '{0}' does not match the Pixel Height of the Primary Stratum raster specified in Initial Conditions Spatial.".format(file)
            logging.error(errMsg)
            sys.exit(errMsg)

def cleanRuntimeFiles(farsite_output_dir):
    shutil.rmtree(farsite_output_dir)

if __name__ == '__main__':
    pass
    # Test
    # createIgnitionPtFile('D:/ApexRMS/A165/FarsiteSTSim/Test Data/STSim/Small Testing Library/FarsiteTesting.ssim.input/Scenario-1/STSim_InitialConditionsSpatial/sqaushed2.shp',
    #                      'd:/temp/test1.shp',1000)

    # convertFireIntensityRaster("D:\ApexRMS\Syncrosim Data File\BuffleTest\Buffelgrass.ssim.input\Scenario-1190\STSim_InitialConditionsSpatial\RMD_Strata_QuarterHa_Filt100.tif",
    # "D:\ApexRMS\Syncrosim Data File\BuffleTest\Buffelgrass.ssim.output\Scenario-1186\Spatial\Farsite\It0001-Ts2015-FARSITE_Intensity.asc",
    # "D:\ApexRMS\Syncrosim Data File\BuffleTest\Buffelgrass.ssim.output\Scenario-1186\Spatial\Farsite\It0001-Ts2015-FARSITE_Ignitions.asc",
    # "d:\Temp\conv.tif")
    # createZeroValRaster("d:/temp/zero.tif","D:\ApexRMS\Syncrosim Data File\BuffleTest\Buffelgrass.ssim.input\Scenario-1190\STSim_InitialConditionsSpatial\RMD_Strata_QuarterHa_Filt100.tif")


#--------------------------
#Date: 19/12/2019
#Place: Turin, PIC4SeR
#Author: Fra, Vitto
#Project: faceAssistant
#---------------------------

##################################################################
# Networks adapted from https://github.com/davidsandberg/facenet #
##################################################################

import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'  # avoid annoying logs

import tensorflow as tf
import time
import faceNet.detectFace as detectFace
import numpy as np
import cv2

class faceNet():
    def __init__(self,model_bb='bin/bb/frozen_graph',model_emb='bin/emb/frozen_graph',conf_thr=0.7,
                 fps_bb=False, fps_emb=False,verbose=True):
        self.graph_bb = tf.Graph()
        self.graph_emb = tf.Graph()
        self.import_graph(model_bb,model_emb,verbose)
        self.sess_bb = tf.compat.v1.Session(graph=self.graph_bb)
        self.sess_emb = tf.compat.v1.Session(graph=self.graph_emb)
        self.conf_thr = conf_thr
        self.fps_bb = fps_bb
        self.fps_emb = fps_emb

    def import_graph(self,model_bb,model_emb,verbose):
        if verbose:
            print("[faceNet] Importing bounding boxes graph.")
        with self.graph_bb.as_default():
            with tf.io.gfile.GFile(model_bb,'rb') as f:
                graph_def = tf.compat.v1.GraphDef()
                graph_def.ParseFromString(f.read())
                tf.import_graph_def(graph_def, name='')
        if verbose:
            print("[faceNet] Importing embeddings graph.")
        with self.graph_emb.as_default():
            with tf.io.gfile.GFile(model_emb,'rb') as f:
                graph_def = tf.compat.v1.GraphDef()
                graph_def.ParseFromString(f.read())
                tf.import_graph_def(graph_def, name='')
        if verbose:
            print("[faceNet] Done.\n")

    def get_face_locations(self,frame,minsize=20,factor=0.709,
                           threshold = [ 0.6, 0.7, 0.7 ]):
        if self.fps_bb:
            start = time.time()
        boxes, landmarks = detectFace.detect_face(frame, minsize,
                                                   threshold,factor,self.sess_bb)
        if self.fps_bb:
            delta = time.time()-start
            print('[faceNet] Bounding boxes: time:',delta,'fsp:',1/delta)

        return boxes, np.transpose(landmarks)

    def get_embeddings(self,faces):
        faces = np.array([detectFace.prewhiten(cv2.resize(face,(160,160))) for face in faces])
        if self.fps_emb:
            start = time.time()
        emb = self.sess_emb.run('embeddings:0',
                       feed_dict={'input:0': faces,
                                  'phase_train:0': False})
        if self.fps_emb:
            delta = time.time()-start
            print('[faceNet] Embeddings: time:',delta,'fsp:',1/delta)
        return emb

    def get_faces(self,frame,boxes,margin=60):
        faces = []
        for (left, top, right, bottom, conf) in boxes:
            left = np.maximum(left-margin/2,0)
            top = np.maximum(top-margin/2,0)
            right = np.minimum(right+margin/2,frame.shape[1])
            bottom = np.minimum(bottom+margin/2,frame.shape[0])
            top = int(top); right = int(right);
            bottom = int(bottom); left = int(left)
            face = frame[top:bottom,left:right]
            faces.append(face)
        return faces

    def compare_faces(self,storage_emb,emb,distance_thr=0.6,verbose=False):
        dist = np.sqrt(np.sum(np.square(np.subtract(storage_emb, emb)),axis=-1))
        if verbose:
            print("[faceNet] Distances:",dist)
        return dist < distance_thr


import os
import numpy as np
from tqdm import tqdm
sys.path.insert(0, 'classes')
from DomainSegmentor import *

# TODO make parameters commandline accessible.
eval_dir = 'nhlrc3_set2'
target_class_idx = [8, 9, 10, 11]
include_max = True #  Include results using the max of the selected classes.

# Generate path list.
path_list = []
for root, dirs, files in os.walk(eval_dir):
    for file in files:
        if file.endswith('.pdb'):
            path_list.append(os.path.join(root, file))

# Check that classes are correct.
print("Selected Classes:")
for t in target_class_idx:
    print(idx_to_class[t])

# Evaluate and extract desired probs.
segmentor = DomainSegmentor()
prob_dict = {}
max_prob_dict = {}

print("Starting Evaluation.")
for pdb_path in tqdm(path_list):
    class_probs, res_nums = segmentor.predict(pdb_path, log=True)
    prob_subset = class_probs[[i for i in target_class_idx][:]]
    target_probs = np.mean(prob_subset, axis=1)
    prob_dict[os.path.split(pdb_path)[1]] = target_probs
    if include_max:
        max_prob_subset = np.max(prob_subset, axis=0)
        max_target_prob = np.mean(max_prob_subset)
        max_prob_dict[os.path.split(pdb_path)[1]] = max_target_prob

# Write the output.
if include_max:
    sorted_list = sorted(max_prob_dict.items(), key=lambda x:x[1])[::-1]
    out_lines = [x[0] + '\t' + str(x[1]) + '\n' for x in sorted_list]
    out_file = open(eval_dir + '_max_' + '-'.join(str(t) for t in target_class_idx) +'.prob', 'w')
    out_file.writelines(out_lines)
    out_file.close()
    print("Wrote: " + eval_dir + '_max_' + '-'.join(str(t) for t in target_class_idx) +'.prob')

for i, class_idx in enumerate(target_class_idx):
    temp_dict = {x : prob_dict[x][i] for x in prob_dict}
    sorted_list = sorted(temp_dict.items(), key=lambda x:x[1])[::-1]
    out_lines = [x[0] + '\t' + str(x[1]) + '\n' for x in sorted_list]
    out_file = open(eval_dir + '_' + idx_to_class[class_idx].replace(' ','').lower() + '.prob', 'w')
    out_file.writelines(out_lines)
    out_file.close()
    print("Wrote: " + eval_dir + '_' + idx_to_class[class_idx].replace(' ','').lower() + '.prob')


#!/usr/bin/python

# *****************************************************************************
#
# Author: Aditya Chatterjee
#
# Interweb/ contacts: GitHub.com/AdiChat
#                     Email: aditianhacker@gmail.com
#
# Implementation of the Random Summarization Algorithm
#
# MIT License
#
# To keep up with the latest version, consult repository: GitHub.com/AdiChat/Read-Less
#
# To get an overview of the TextTiling Algorithm, consult wiki: Github.com/AdiChat/Read-Less/wiki
#
# *****************************************************************************

import io
import nltk
import itertools
from operator import itemgetter
import networkx as nx
import os
import random as rr
from ..Segmentation import texttiling
from ..Parse import parse

class Random():

	def __init__(self):
		print "Random: Summarizing textual data"

	def random(self, firstString, secondString):
		'''
		Returns a random similarity score between 2 strings
		Arguments:
			firstString: first input string
			secondString: second input string
		Returns:
			A random integer
		Raises:
			None
		'''
	    return rr.randrange(0, 101, 2)

	def buildGraph(self, nodes):
		'''
		Builds the graph with token of words as a node
		Arguments:
			nodes: list of token of words
		Returns:
			the graph
		Raises:
			None
		'''
	    gr = nx.Graph()
	    gr.add_nodes_from(nodes)
	    nodePairs = list(itertools.combinations(nodes, 2))

	    for pair in nodePairs:
	        firstString = pair[0]
	        secondString = pair[1]
	        levDistance = self.random(firstString, secondString)
	        gr.add_edge(firstString, secondString, weight=levDistance)

	    return gr

	def extractSentences(self, text, size=201):
		'''
		Finds out top token of words from the corpus
		Arguments:
			text: list of token of words
			size: size of summary
		Returns:
			The summary of size (size)
		Raises:
			None
		'''
	    sentenceTokens = text
	    graph = self.buildGraph(sentenceTokens)
	    calculated_page_rank = nx.pagerank(graph, weight='weight')
	    sentences = sorted(calculated_page_rank, key=calculated_page_rank.get, reverse=True)
	    summary = ' '.join(sentences)
	    summaryWords = summary.split()
	    summaryWords = summaryWords[0:size]
	    summary = ' '.join(summaryWords)

	    return summary

	def summarize(self, data):
		'''
		Summarizes a text data
		Arguments:
			data: input textual data
		Returns:
			The summary of input file
		Raises:
			None
		'''
		t = texttiling.TextTiling()
		text = t.run(data)
		return self.extractSentences(text)

	def summarizeFile(self, pathToFile):
		'''
		Summarizes a document
		Arguments:
			pathToFile: path to the file to be summarized
		Returns:
			The summary of the input file
		Raises:
			None
		'''
		p = parse.Parse()
		t = texttiling.TextTiling()
		data = p.dataFromFile(pathToFile)
		text = t.run(data)
		return self.extractSentences(text)


"""
This script will define classes that enable CatSim to interface with GCR
"""
import numpy as np
import os

from desc.sims.GCRCatSimInterface import DESCQAObject
from desc.sims.GCRCatSimInterface import deg2rad_double, arcsec2rad

from lsst.sims.catalogs.db import DBObject

__all__ = ["DESCQAObject_protoDC2",
           "bulgeDESCQAObject_protoDC2",
           "diskDESCQAObject_protoDC2",
           "knotsDESCQAObject_protoDC2",
           "agnDESCQAObject_protoDC2",
           "AGN_postprocessing_mixin",
           "FieldRotator"]

_ALPHA_Q_ADD_ON_IS_AVAILABLE = True
try:
    from GCRCatalogs.alphaq_addon import AlphaQAddonCatalog
except ImportError:
    _ALPHA_Q_ADD_ON_IS_AVAILABLE = False

_LSST_IS_AVAILABLE = True
try:
    from lsst.sims.utils import rotationMatrixFromVectors
    from lsst.sims.utils import cartesianFromSpherical, sphericalFromCartesian
except ImportError:
    _LSST_IS_AVAILABLE = False

class FieldRotator(object):

    def __init__(self, ra0, dec0, ra1, dec1):
        """
        Parameters
        ----------
        ra0, dec0 are the coordinates of the original field
        center in degrees

        ra1, dec1 are the coordinates of the new field center
        in degrees

        The transform() method of this class operates by first
        applying a rotation that carries the original field center
        into the new field center.  Points are then transformed into
        a basis in which the unit vector defining the new field center
        is the x-axis.  A rotation about the x-axis is applied so that
        a point that was due north of the original field center is still
        due north of the field center at the new location.  Finally,
        points are transformed back into the original x,y,z bases.
        """

        # find the rotation that carries the original field center
        # to the new field center
        xyz = cartesianFromSpherical(np.radians(ra0), np.radians(dec0))
        xyz1 = cartesianFromSpherical(np.radians(ra1), np.radians(dec1))
        if np.abs(1.0-np.dot(xyz, xyz1))<1.0e-10:
            self._transformation = np.identity(3, dtype=float)
            return

        first_rotation = rotationMatrixFromVectors(xyz, xyz1)

        # create a basis set in which the unit vector
        # defining the new field center is the x axis
        xx = np.dot(first_rotation, xyz)
        rng = np.random.RandomState(99)
        mag = np.NaN
        while np.abs(mag)<1.0e-20 or np.isnan(mag):
            random_vec = rng.random_sample(3)
            comp = np.dot(random_vec, xx)
            yy = random_vec - comp*xx
            mag = np.sqrt((yy**2).sum())
            yy /= mag

        zz = np.cross(xx, yy)

        to_self_bases = np.array([xx,
                                  yy,
                                  zz])

        out_of_self_bases =to_self_bases.transpose()

        # Take a point due north of the original field
        # center.  Apply first_rotation to carry it to
        # the new field.  Transform it to the [xx, yy, zz]
        # bases and find the rotation about xx that will
        # make it due north of the new field center.
        # Finally, transform back to the original bases.
        d_dec = 0.1
        north = cartesianFromSpherical(np.radians(ra0),
                                       np.radians(dec0+d_dec))

        north = np.dot(first_rotation, north)

        #print(np.degrees(sphericalFromCartesian(north)))

        north_true = cartesianFromSpherical(np.radians(ra1),
                                            np.radians(dec1+d_dec))

        north = np.dot(to_self_bases, north)
        north_true = np.dot(to_self_bases, north_true)
        north = np.array([north[1], north[2]])
        north /= np.sqrt((north**2).sum())
        north_true = np.array([north_true[1], north_true[2]])
        north_true /= np.sqrt((north_true**2).sum())

        c = north_true[0]*north[0]+north_true[1]*north[1]
        s = north[0]*north_true[1]-north[1]*north_true[0]
        norm = np.sqrt(c*c+s*s)
        c = c/norm
        s = s/norm

        nprime = np.array([c*north[0]-s*north[1],
                           s*north[0]+c*north[1]])

        yz_rotation = np.array([[1.0, 0.0, 0.0],
                                [0.0, c, -s],
                                [0.0, s, c]])

        second_rotation = np.dot(out_of_self_bases,
                                 np.dot(yz_rotation,
                                        to_self_bases))

        self._transformation = np.dot(second_rotation,
                                      first_rotation)

    def transform(self, ra, dec):
        """
        ra, dec are in degrees; return the RA, Dec coordinates
        of the point about the new field center
        """
        xyz = cartesianFromSpherical(np.radians(ra), np.radians(dec)).transpose()
        xyz = np.dot(self._transformation, xyz).transpose()
        ra_out, dec_out = sphericalFromCartesian(xyz)
        return np.degrees(ra_out), np.degrees(dec_out)

class DESCQAObject_protoDC2(DESCQAObject):
    """
    This class is meant to mimic the CatalogDBObject usually used to
    connect CatSim to a database.
    """

    _cat_cache_suffix = '_rotated'
    tableid = 'galaxy'
    database = 'LSSTCATSIM'
    yaml_file_name = 'protoDC2'

    def _rotate_to_correct_field(self, ra_rad, dec_rad):
        """
        Takes arrays of RA and Dec (in radians) centered
        on RA=0, Dec=0 and rotates them to coordinates
        centered on self.field_ra, self.field_dec

        Returns the rotated RA, Dec in radians.
        """

        if not _LSST_IS_AVAILABLE:
            raise RuntimeError("\nCannot use DESCQAObject_protoDC2\n"
                               "The LSST simulations stack is not setup\n")

        if not hasattr(self, '_rotate_ra_in_cache'):
            self._rotate_ra_in_cache = None
            self._rotate_dec_in_cache = None

        if not hasattr(self, '_field_rotator'):
            self._field_rotator = FieldRotator(0.0, 0.0, self.field_ra, self.field_dec)

        if self._rotate_ra_in_cache is None or \
           not np.array_equal(ra_rad, self._rotate_ra_in_cache) or \
           not np.array_equal(dec_rad, self._rotate_dec_in_cache):

            self._rotate_ra_in_cache = ra_rad
            self._rotate_dec_in_cache = dec_rad

            ra, dec = self._field_rotator.transform(np.degrees(ra_rad),
                                                    np.degrees(dec_rad))

            self._ra_rotated = np.radians(ra)
            self._dec_rotated = np.radians(dec)

        return self._ra_rotated, self._dec_rotated

    def _transform_ra(self, ra_deg, dec_deg):
        """
        Transform RA in degrees to RA_rot in radians
        where RA was from a catalog centered on
        RA=0, Dec=0 and RA_rot is from a catalog
        centered on RA=self.field_ra, Dec=self.field_dec
        """
        ra, dec = self._rotate_to_correct_field(deg2rad_double(ra_deg),
                                                deg2rad_double(dec_deg))
        return ra

    def _transform_dec(self, ra_deg, dec_deg):
        """
        Transform Dec in degrees to Dec_rot in radians
        where Dec was from a catalog centered on
        RA=0, Dec=0 and Dec_rot is from a catalog
        centered on RA=self.field_ra, Dec=self.field_dec
        """

        ra, dec = self._rotate_to_correct_field(deg2rad_double(ra_deg),
                                                deg2rad_double(dec_deg))
        return dec

    def _transform_object_coords(self, gc):
        """
        Apply transformations to the RA, Dec of astrophysical sources

        gc is a GCR catalog instance
        """
        gc.add_modifier_on_derived_quantities('raJ2000', self._transform_ra,
                                              'ra_true', 'dec_true')
        gc.add_modifier_on_derived_quantities('decJ2000', self._transform_dec,
                                              'ra_true', 'dec_true')

class bulgeDESCQAObject_protoDC2(DESCQAObject_protoDC2):
    # PhoSim uniqueIds are generated by taking
    # source catalog uniqueIds, multiplying by
    # 1024, and adding objectTypeId.  This
    # components of the same galaxy to have
    # different uniqueIds, even though they
    # share a uniqueId in the source catalog
    objectTypeId = 97
    objid = 'bulge_descqa'

    # some column names require an additional postfix
    _postfix = '::bulge'

class diskDESCQAObject_protoDC2(DESCQAObject_protoDC2):
    objectTypeId = 107
    objid = 'disk_descqa'
    _postfix = '::disk'

class knotsDESCQAObject_protoDC2(DESCQAObject_protoDC2):
    objectTypeId = 127
    objid = 'knots_descqa'
    _postfix = '::knots'

class AGN_postprocessing_mixin(object):

    def _postprocess_results(self, master_chunk):
        """
        query the database specified by agn_params_db to
        find the AGN varParamStr associated with each AGN
        """

        if self.agn_objid is None:
            gid_name = 'galaxy_id'
            varpar_name = 'varParamStr'
            magnorm_name = 'magNorm'
        else:
            gid_name = self.agn_objid + '_' + 'galaxy_id'
            varpar_name = self.agn_objid + '_' + 'varParamStr'
            magnorm_name = self.agn_objid + '_' + 'magNorm'

        if self.agn_params_db is None:
            return(master_chunk)

        if not os.path.exists(self.agn_params_db):
            raise RuntimeError('\n%s\n\ndoes not exist' % self.agn_params_db)

        if not hasattr(self, '_agn_dbo'):
            self._agn_dbo = DBObject(database=self.agn_params_db,
                                     driver='sqlite')

            self._agn_dtype = np.dtype([('galaxy_id', int),
                                        ('magNorm', float),
                                        ('varParamStr', str, 500)])

        gid_arr = master_chunk[gid_name].astype(float)

        gid_min = np.nanmin(gid_arr)
        gid_max = np.nanmax(gid_arr)

        query = 'SELECT galaxy_id, magNorm, varParamStr '
        query += 'FROM agn_params '
        query += 'WHERE galaxy_id BETWEEN %d AND %d ' % (gid_min, gid_max)
        query += 'ORDER BY galaxy_id'

        agn_data_iter = self._agn_dbo.get_arbitrary_chunk_iterator(query,
                                                        dtype=self._agn_dtype,
                                                        chunk_size=1000000)

        m_sorted_dex = np.argsort(gid_arr)
        m_sorted_id = gid_arr[m_sorted_dex]
        for agn_chunk in agn_data_iter:

            # find the indices of the elements in master_chunk
            # that correspond to elements in agn_chunk
            m_elements = np.in1d(m_sorted_id, agn_chunk['galaxy_id'])
            m_dex = m_sorted_dex[m_elements]

            # find the indices of the elements in agn_chunk
            # that correspond to elements in master_chunk
            a_dex = np.in1d(agn_chunk['galaxy_id'], m_sorted_id)

            # make sure we have matched elements correctly
            np.testing.assert_array_equal(agn_chunk['galaxy_id'][a_dex],
                                          master_chunk[gid_name][m_dex])

            if varpar_name in master_chunk.dtype.names:
                master_chunk[varpar_name][m_dex] = agn_chunk['varParamStr'][a_dex]

            if magnorm_name in master_chunk.dtype.names:
                master_chunk[magnorm_name][m_dex] = agn_chunk['magNorm'][a_dex]

        return self._final_pass(master_chunk)

class agnDESCQAObject_protoDC2(AGN_postprocessing_mixin, DESCQAObject_protoDC2):
    objectTypeId = 117
    objid = 'agn_descqa'
    _columns_need_postfix = False

    descqaDefaultValues = {'is_sprinkled': (0, int),
                           'varParamStr': (None, (str, 500)),
                           'magNorm': (np.NaN, np.float),
                           'sedFilename': ('agn.spec', (str, 500))}

    agn_params_db = None
    agn_objid = None


# import the necessary packages
import numpy as np
import argparse
import imutils
import time
import cv2
import os
import requests

# pylint: disable=no-member
# example url = 'http://192.168.1.2:8080/shot.jpg'

# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()

ap.add_argument("-y", "--yolo", required=True,
    help="base path to YOLO directory")

ap.add_argument("-u", "--url", required=True,
    help="The ip camera complete path to image")

ap.add_argument("-c", "--confidence", type=float, default=0.5,
    help="minimum probability to filter weak detections")

ap.add_argument("-t", "--threshold", type=float, default=0.3,
    help="threshold when applyong non-maxima suppression")

args = vars(ap.parse_args())
url = args['url']

# load the COCO class labels our YOLO model was trained on
labelsPath = os.path.sep.join([args["yolo"], "coco.names"])
LABELS = open(labelsPath).read().strip().split("\n")

# derive the paths to the YOLO weights and model configuration
weightsPath = os.path.sep.join([args["yolo"], "yolov3.weights"])
configPath = os.path.sep.join([args["yolo"], "yolov3.cfg"])

# load our YOLO object detector trained on COCO dataset (80 classes)
# and determine only the *output* layer names that we need from YOLO
print("[INFO] loading YOLO from disk...")
net = cv2.dnn.readNetFromDarknet(configPath, weightsPath)
ln = net.getLayerNames()
print(ln)
ln = [ln[i[0] - 1] for i in net.getUnconnectedOutLayers()]
print(ln)

# initialize the video stream, pointer to output video file, and
# frame dimensions
writer = None
(W, H) = (None, None)

cv2.namedWindow('live', cv2.WINDOW_NORMAL)

if 'http' in url:
    LOCAL = False

else:
    LOCAL = True
    vs = cv2.VideoCapture(int(url))

# loop over frames from the video stream
while True:

    if LOCAL:

        (grabbed, frame) = vs.read()

        if not grabbed:
            print('[error] frame not grabbed')
            break

    else:

        try:
            print('about to read frame')
            img_res = requests.get(url,timeout=5)
            print('read frame')
            img_arr = np.array(bytearray(img_res.content), dtype = np.uint8)
            print('decoding img')
            frame = cv2.imdecode(img_arr,1)
            #frame = frame[:,:,:] H W RGB
            print(frame.shape)
        except Exception:
            print('request timed out or decode error')
            time.sleep(1)
            continue

    # if the frame dimensions are empty, grab them
    if W is None or H is None:
        (H, W) = frame.shape[:2]
        primo = W/3
        secondo = 2*W/3

    # construct a blob from the input frame and then perform a forward
    # pass of the YOLO object detector, giving us our bounding boxes
    # and associated probabilities
    blob = cv2.dnn.blobFromImage(frame, 1 / 255.0, (416, 416),
        swapRB=True, crop=False)
    net.setInput(blob)
    start = time.time()
    layerOutputs = net.forward(ln)
    end = time.time()

    # initialize our lists of detected bounding boxes, confidences,
    # and class IDs, respectively
    boxes = []
    confidences = []
    classIDs = []

    # loop over each of the layer outputs
    for output in layerOutputs:
        # loop over each of the detections
        for detection in output:
            # extract the class ID and confidence (i.e., probability)
            # of the current object detection
            scores = detection[5:]
            classID = np.argmax(scores)
            confidence = scores[classID]

            # filter out weak predictions by ensuring the detected
            # probability is greater than the minimum probability
            if confidence > args["confidence"]:
                # scale the bounding box coordinates back relative to
                # the size of the image, keeping in mind that YOLO
                # actually returns the center (x, y)-coordinates of
                # the bounding box followed by the boxes' width and
                # height
                box = detection[0:4] * np.array([W, H, W, H])
                (centerX, centerY, width, height) = box.astype("int")

                # use the center (x, y)-coordinates to derive the top
                # and and left corner of the bounding box
                x = int(centerX - (width / 2))
                y = int(centerY - (height / 2))

                # update our list of bounding box coordinates,
                # confidences, and class IDs
                boxes.append([x, y, int(width), int(height)])
                confidences.append(float(confidence))
                classIDs.append(classID)

    # apply non-maxima suppression to suppress weak, overlapping
    # bounding boxes
    idxs = cv2.dnn.NMSBoxes(boxes, confidences, args["confidence"],
        args["threshold"])

    # ensure at least one detection exists
    if len(idxs) > 0:
        print('ids:',idxs.flatten())
        print('classes',classIDs)
        print('confidences',confidences)
        # array index with id == 67 --> phones
        phone_ids = [i for i, class_id in enumerate(classIDs) if class_id == 67]
        person_ids = [i for i, class_id in enumerate(classIDs) if class_id == 0]
        # loop over the indexes we are keeping

        #print(person_ids, phone_ids)

        for i in person_ids:
            if i in idxs.flatten():
            # extract the bounding box coordinates
                (x, y) = (boxes[i][0], boxes[i][1])
                (w, h) = (boxes[i][2], boxes[i][3])
                phone = False

                for j in phone_ids: #let's check other ids
                    # extract the bounding box
                    (x1, y1) = (boxes[j][0], boxes[j][1])
                    (w1, h1) = (boxes[j][2], boxes[j][3])

                    if  (x <= x1 <= x+w) and (y <= y1 <= y+h): #there is a phone inside the person bbox
                        phone = True

                if phone:
                    text = 'Access Denied'
                    col = (0,0,255) #BGR red

                elif primo <= x+w/2 <= secondo:
                    text = 'OK'
                    col = (0,255,0) #BGR green

                else:
                    text = 'Carefull'
                    col = (0,255,255) #BGR yellow

                # draw text message for person
                cv2.putText(frame,text, (x, y - 25 ),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5,col, 2)
                # draw a bounding box rectangle and label on the frame phone
                cv2.rectangle(frame, (x, y), (x + w, y + h), col, 2)

                # draw a bounding box rectangle and label on the frame phone
                if phone:
                    cv2.rectangle(frame, (x1, y1), (x1 + w1, y1 + h1), (0,0,255), 2)
                    cv2.putText(frame, 'No phones allowed', (x1, y1 - 5),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.5,(0,0,255), 2)
                    phone = False

            cv2.line(frame, (int(primo), 0), (int(primo), H), (0,255,0), 2)
            cv2.line(frame, (int(secondo), 0), (int(secondo), H), (0,255,0), 2)

    print('total_time: ' + str(time.time() - start))

    # show frame
    #cv2.resizeWindow('live', 1920, 1080)   # optional resize
    cv2.imshow('live', frame)

    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# release the file pointers
print("[INFO] cleaning up...")


'''
 * Copyright (c) 2022, salesforce.com, inc.
 * All rights reserved.
 * SPDX-License-Identifier: BSD-3-Clause
 * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
 * By Junnan Li
'''
import argparse
import os
import ruamel_yaml as yaml
import numpy as np
import random
import time
import datetime
import json
from pathlib import Path

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.backends.cudnn as cudnn
import torch.distributed as dist
from torch.utils.data import DataLoader

from models.blip_retrieval import blip_retrieval
import utils
from data.video_dataset import VideoDataset

@torch.no_grad()
def evaluation(model, data_loader, tokenizer, device, config):
    # test
    model.eval()

    metric_logger = utils.MetricLogger(delimiter="  ")
    header = 'Evaluation:'

    print('Computing features for evaluation...')
    start_time = time.time()

    texts = data_loader.dataset.text
    num_text = len(texts)
    text_bs = 256
    text_ids = []
    text_embeds = []
    text_atts = []
    for i in range(0, num_text, text_bs):
        text = texts[i: min(num_text, i+text_bs)]
        text_input = tokenizer(text, padding='max_length', truncation=True, max_length=35, return_tensors="pt").to(device)
        text_output = model.text_encoder(text_input.input_ids, attention_mask = text_input.attention_mask, mode='text')
        text_embed = F.normalize(model.text_proj(text_output.last_hidden_state[:,0,:]))
        text_embeds.append(text_embed)
        text_ids.append(text_input.input_ids)
        text_atts.append(text_input.attention_mask)

    text_embeds = torch.cat(text_embeds,dim=0)
    text_ids = torch.cat(text_ids,dim=0)
    text_atts = torch.cat(text_atts,dim=0)
    text_ids[:,0] = tokenizer.additional_special_tokens_ids[0]

    video_feats = []
    video_embeds = []
    for video, video_id in data_loader:

        B,N,C,W,H = video.size()
        video = video.view(-1,C,W,H)
        video = video.to(device,non_blocking=True)
        video_feat = model.visual_encoder(video)
        video_embed = model.vision_proj(video_feat[:,0,:])
        video_embed = video_embed.view(B,N,-1).mean(dim=1)
        video_embed = F.normalize(video_embed,dim=-1)

        video_feat = video_feat.view(B,-1,video_feat.shape[-1])
        video_feats.append(video_feat.cpu())
        video_embeds.append(video_embed)

    video_feats = torch.cat(video_feats,dim=0)
    video_embeds = torch.cat(video_embeds,dim=0)

    sims_matrix = video_embeds @ text_embeds.t()
    score_matrix_v2t = torch.full((len(texts),len(texts)),-100.0).to(device)

    num_tasks = utils.get_world_size()
    rank = utils.get_rank()
    step = sims_matrix.size(0)//num_tasks + 1
    start = rank*step
    end = min(sims_matrix.size(0),start+step)

    for i,sims in enumerate(metric_logger.log_every(sims_matrix[start:end], 50, header)):
        topk_sim, topk_idx = sims.topk(k=config['k_test'], dim=0)

        encoder_output = video_feats[start+i].repeat(config['k_test'],1,1).to(device,non_blocking=True)
        encoder_att = torch.ones(encoder_output.size()[:-1],dtype=torch.long).to(device,non_blocking=True)
        output = model.text_encoder(text_ids[topk_idx],
                                    attention_mask = text_atts[topk_idx],
                                    encoder_hidden_states = encoder_output,
                                    encoder_attention_mask = encoder_att,
                                    return_dict = True,
                                   )
        score = model.itm_head(output.last_hidden_state[:,0,:])[:,1]
        score_matrix_v2t[start+i,topk_idx] = score + topk_sim

    sims_matrix = sims_matrix.t()
    score_matrix_t2v = torch.full((len(texts),len(texts)),-100.0).to(device)

    step = sims_matrix.size(0)//num_tasks + 1
    start = rank*step
    end = min(sims_matrix.size(0),start+step)

    for i,sims in enumerate(metric_logger.log_every(sims_matrix[start:end], 50, header)):

        topk_sim, topk_idx = sims.topk(k=config['k_test'], dim=0)
        encoder_output = video_feats[topk_idx].to(device,non_blocking=True)
        encoder_att = torch.ones(encoder_output.size()[:-1],dtype=torch.long).to(device,non_blocking=True)
        output = model.text_encoder(text_ids[start+i].repeat(config['k_test'],1),
                                    attention_mask = text_atts[start+i].repeat(config['k_test'],1),
                                    encoder_hidden_states = encoder_output,
                                    encoder_attention_mask = encoder_att,
                                    return_dict = True,
                                   )
        score = model.itm_head(output.last_hidden_state[:,0,:])[:,1]
        score_matrix_t2v[start+i,topk_idx] = score + topk_sim

    if args.distributed:
        dist.barrier()
        torch.distributed.all_reduce(score_matrix_v2t, op=torch.distributed.ReduceOp.SUM)
        torch.distributed.all_reduce(score_matrix_t2v, op=torch.distributed.ReduceOp.SUM)

    total_time = time.time() - start_time
    total_time_str = str(datetime.timedelta(seconds=int(total_time)))
    print('Evaluation time {}'.format(total_time_str))

    return score_matrix_v2t.cpu().numpy(), score_matrix_t2v.cpu().numpy()

@torch.no_grad()
def itm_eval(scores_v2t, scores_t2v, txt2vmg, vid2txt):

    #Video->Text
    ranks = np.zeros(scores_v2t.shape[0])
    for index,score in enumerate(scores_v2t):
        inds = np.argsort(score)[::-1]
        ranks[index] = np.where(inds == vid2txt[index])[0][0]

    # Compute metrics
    tr1 = 100.0 * len(np.where(ranks < 1)[0]) / len(ranks)
    tr5 = 100.0 * len(np.where(ranks < 5)[0]) / len(ranks)
    tr10 = 100.0 * len(np.where(ranks < 10)[0]) / len(ranks)

    #Text->Video
    ranks = np.zeros(scores_t2v.shape[0])

    for index,score in enumerate(scores_t2v):
        inds = np.argsort(score)[::-1]
        ranks[index] = np.where(inds == txt2vmg[index])[0][0]

    mdR = np.median(ranks+1)

    # Compute metrics
    vr1 = 100.0 * len(np.where(ranks < 1)[0]) / len(ranks)
    vr5 = 100.0 * len(np.where(ranks < 5)[0]) / len(ranks)
    vr10 = 100.0 * len(np.where(ranks < 10)[0]) / len(ranks)

    tr_mean = (tr1 + tr5 + tr10) / 3
    vr_mean = (vr1 + vr5 + vr10) / 3
    r_mean = (tr_mean + vr_mean) / 2

    eval_result =  {'txt_r1': tr1,
                    'txt_r5': tr5,
                    'txt_r10': tr10,
                    'txt_r_mean': tr_mean,
                    'vid_r1': vr1,
                    'vid_r5': vr5,
                    'vid_r10': vr10,
                    'vid_r_mean': vr_mean,
                    'vid_mdR': mdR,
                    'r_mean': r_mean}
    return eval_result

def main(args, config):
    utils.init_distributed_mode(args)

    device = torch.device(args.device)

    # fix the seed for reproducibility
    seed = args.seed + utils.get_rank()
    torch.manual_seed(seed)
    np.random.seed(seed)
    random.seed(seed)
    cudnn.benchmark = True

    #### Dataset ####
    print("Creating retrieval dataset")
    test_dataset = VideoDataset(config['video_root'],config['ann_root'],num_frm=config['num_frm_test'],
                                max_img_size=config['image_size'], frm_sampling_strategy='uniform')

    test_loader = DataLoader(
        test_dataset,
        batch_size=config['batch_size'],
        num_workers=4,
        pin_memory=True,
        drop_last=False,
        shuffle=False,
    )

    #### Model ####
    print("Creating model")
    model = blip_retrieval(pretrained=config['pretrained'], image_size=config['image_size'], vit=config['vit'])

    model = model.to(device)

    model_without_ddp = model
    if args.distributed:
        model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[args.gpu])
        model_without_ddp = model.module

    score_v2t, score_t2v, = evaluation(model_without_ddp, test_loader, model_without_ddp.tokenizer, device, config)

    if utils.is_main_process():

        test_result = itm_eval(score_v2t, score_t2v, test_loader.dataset.txt2video, test_loader.dataset.video2txt)
        print(test_result)

        log_stats = {**{f'{k}': v for k, v in test_result.items()},}
        with open(os.path.join(args.output_dir, "test_result.txt"),"a") as f:
            f.write(json.dumps(log_stats) + "\n")

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--config', default='./configs/retrieval_msrvtt.yaml')
    parser.add_argument('--output_dir', default='output/Retrieval_msrvtt')
    parser.add_argument('--device', default='cuda')
    parser.add_argument('--seed', default=42, type=int)
    parser.add_argument('--world_size', default=1, type=int, help='number of distributed processes')
    parser.add_argument('--dist_url', default='env://', help='url used to set up distributed training')
    parser.add_argument('--distributed', default=True, type=bool)
    args = parser.parse_args()

    config = yaml.load(open(args.config, 'r'), Loader=yaml.Loader)

    Path(args.output_dir).mkdir(parents=True, exist_ok=True)

    yaml.dump(config, open(os.path.join(args.output_dir, 'config.yaml'), 'w'))

    main(args, config)