## Wave transmission through a Breakwater
# Author: AJMR
# December 14, 2021

# %%  Setup Project
from mikeio import Dfs0, Dfs1
from mikeio.eum import ItemInfo, EUMUnit, EUMType

import pandas as pd
import pathlib as pl
import numpy as np
import geopandas as gp
import math
import matplotlib.pyplot as plt
import contextily as ctx

# %% Read Model Log

pth = pl.Path("//srv-mad3.baird.com/", "Projects", "13632.101 South Shore Breakwater", "06_Models",
              "13632.101.W.AJMR.Rev0_ModelLog.xlsx")
runLog = pd.read_excel(pth.as_posix(), "ModelLog")

# %% Read in Point Sh
breakwaterPTS = gp.read_file("//srv-mad3.baird.com/Projects/"
                             "13632.101 South Shore Breakwater/08_CADD/Outgoing/"
                             "20211211_Toe Extents (to Alexander)/"
                             "20211211_Toe_Extents_NAD83_WISStatePlaneSZn_USFt_Lines_OffshoreClipSimple_10m_vertexUTM.shp")

# %% Read in Breakwater crest
breakCrest = pd.read_csv(
    '//srv-mad3.baird.com/Projects/13632.101 South Shore Breakwater/08_CADD/Outgoing/20211214_Crest Points (to Alexander)/20211214_Crest_Points_m.csv',
    header=None, names=['x', 'y', 'z'])
breakCrest_gdf = gp.GeoDataFrame(breakCrest, crs='EPSG:32154',
                                 geometry=gp.points_from_xy(breakCrest.x, breakCrest.y))
breakCrest_gdf.to_crs('EPSG:32616', inplace=True)

breakwaterPTS_Crest = breakwaterPTS.sjoin_nearest(breakCrest_gdf)
breakwaterPTS_Crest.rename(columns={'x': 'breakCrest_x', 'y': 'breakCrest_y', 'z': 'Crest'}, inplace=True)

# Add in Default width and slope
breakwaterPTS_Crest['Width'] = 5.0
breakwaterPTS_Crest['Slope'] = 2.6

# %% Modify Breakwater Crest to new elevation
# From email
# X, Y, Crest, Slope, Cotangent
breakSegments = np.zeros((4, 5))
breakSegments[0, :] = [2534702.7417, 372246.1099, 0,     0,   0] # 6+85
breakSegments[1, :] = [2535059.6902, 371628.1622, 180.5, 5.5, 2.6] # 14+00
breakSegments[2, :] = [2535910.4820, 370655.9834, 181.1, 6.7, 2.6] # 27+00
breakSegments[3, :] = [2536821.5682, 369722.4850, 180.5, 5.5, 2.6] # 40+05

breakSegments_gdf = gp.GeoDataFrame(breakSegments,
                        geometry=gp.points_from_xy(breakSegments[:, 0], breakSegments[:, 1]),
                        crs='EPSG:8796')
breakSegments_gdf.to_crs('EPSG:32616', inplace=True)

for idx in range(0, breakwaterPTS_Crest.shape[0]):

    # Check where point is (based on x coordinate) and assign new crest elevation
    if breakSegments_gdf.geometry.x[0] <= breakwaterPTS_Crest.geometry.x[idx] <= breakSegments_gdf.geometry.x[1]:
        breakwaterPTS_Crest.loc[idx, 'Crest'] = breakSegments[1, 2]
        breakwaterPTS_Crest.loc[idx, 'Width'] = breakSegments[1, 3]
        breakwaterPTS_Crest.loc[idx, 'Slope'] = breakSegments[1, 4]
    elif breakSegments_gdf.geometry.x[1] <= breakwaterPTS_Crest.geometry.x[idx] <= breakSegments_gdf.geometry.x[2]:
        breakwaterPTS_Crest.loc[idx, 'Crest'] = breakSegments[2, 2]
        breakwaterPTS_Crest.loc[idx, 'Width'] = breakSegments[2, 3]
        breakwaterPTS_Crest.loc[idx, 'Slope'] = breakSegments[2, 4]
    elif breakSegments_gdf.geometry.x[2] <= breakwaterPTS_Crest.geometry.x[idx] <= breakSegments_gdf.geometry.x[3]:
        breakwaterPTS_Crest.loc[idx, 'Crest'] = breakSegments[3, 2]
        breakwaterPTS_Crest.loc[idx, 'Width'] = breakSegments[3, 3]
        breakwaterPTS_Crest.loc[idx, 'Slope'] = breakSegments[3, 4]

# %% Read Model Results
modelPlot = range(33, 40)

for m in modelPlot:
    dfsIN = Dfs0(pl.Path(runLog['Run Location'][m], str(runLog['Number'][m]) + '_' +
                         runLog['Run Name'][m] + '.sw - Result Files', 'BreakPts.dfs0').as_posix())

    dfsIN_read = dfsIN.read()

    MIKEds = dfsIN_read.to_dataframe()

    # %% Merge with dataframe
    breakwaterPTS_times = None

    for t in range(0, MIKEds.shape[0]):
        breakwaterPTS_merge = None
        breakwaterPTS_merge = breakwaterPTS_Crest

        for i in range(0, MIKEds.shape[1]):
            paramName = MIKEds.columns.values[i][MIKEds.columns.values[i].find('"', 5, -1) + 3:]
            if paramName not in breakwaterPTS_merge.columns:
                breakwaterPTS_merge[paramName] = np.full([breakwaterPTS_merge.shape[0], 1], np.nan)
                # MIKEds_dict[paramName]   = []
                # MIKEid_dict[paramName]   = []
            tmpFID = int(MIKEds.columns.values[i][1:MIKEds.columns.values[i].find('"', 1)])
            tmpIND = int(MIKEds.columns.values[i][5:MIKEds.columns.values[i].find('"', 5)])

            breakwaterPTS_merge.loc[((breakwaterPTS_merge.FID == tmpFID) &
                                     (breakwaterPTS_merge.vertex_ind == tmpIND)),
                                    paramName] = MIKEds.iloc[t, i]

        breakwaterPTS_merge['Time'] = MIKEds.index[t]

        breakwaterPTS_times = pd.concat([breakwaterPTS_times, breakwaterPTS_merge], ignore_index=True)

    breakwaterPTS_times.rename(columns={'index_right': 'index_right_old'}, inplace=True)

    # %% Conversion
    breakwaterPTS_times['transmitted'] = np.full([breakwaterPTS_times.shape[0], 1], np.nan)

    # Identify different overtopping regimes
    breakMask_A = breakwaterPTS_times['Sign. Wave Height'] < 0
    breakMask_B = (breakwaterPTS_times['Width'] / breakwaterPTS_times['Sign. Wave Height']) <=8
    breakMask_C = (breakwaterPTS_times['Width'] / breakwaterPTS_times['Sign. Wave Height']) >=12
    breakMask_Da = (breakwaterPTS_times['Width'] / breakwaterPTS_times['Sign. Wave Height']) >8
    breakMask_Db = (breakwaterPTS_times['Width'] / breakwaterPTS_times['Sign. Wave Height']) <12
    breakMask_D = breakMask_Da & breakMask_Db

    # Updated approach
    breakwaterPTS_times.loc[breakMask_A, 'transmitted'] = 0
    breakwaterPTS_times.loc[breakMask_B, 'transmitted'] = breakwaterPTS_times.loc[breakMask_B, 'Sign. Wave Height']*\
        np.maximum(0, np.minimum(0.8, (-0.4 * (breakwaterPTS_times.loc[breakMask_B, 'Crest'] - breakwaterPTS_times.loc[breakMask_B, 'Surface elevation']) /
        breakwaterPTS_times.loc[breakMask_B, 'Sign. Wave Height'] + 0.64 * (breakwaterPTS_times.loc[breakMask_B, 'Width'] /
        breakwaterPTS_times.loc[breakMask_B, 'Sign. Wave Height']) ** (-0.31) * (1 - np.exp(-0.5 * (1 / breakwaterPTS_times.loc[breakMask_B, 'Slope'] /
        np.sqrt(2 * math.pi * breakwaterPTS_times.loc[breakMask_B, 'Sign. Wave Height'] / 9.81 / breakwaterPTS_times.loc[breakMask_B, 'Peak Wave Period'] ** 2)))))))

    breakwaterPTS_times.loc[breakMask_C, 'transmitted'] = breakwaterPTS_times.loc[breakMask_C, 'Sign. Wave Height'] * \
        np.maximum(0.05, np.minimum(np.maximum(0, -0.006 * breakwaterPTS_times.loc[breakMask_C, 'Width'] / breakwaterPTS_times.loc[breakMask_C, 'Sign. Wave Height'] + 0.93),
          (-0.35 * (breakwaterPTS_times.loc[breakMask_C, 'Crest'] - breakwaterPTS_times.loc[breakMask_C, 'Surface elevation']) /
          breakwaterPTS_times.loc[breakMask_C, 'Sign. Wave Height'] + 0.51 * (breakwaterPTS_times.loc[breakMask_C, 'Width'] /
          breakwaterPTS_times.loc[breakMask_C, 'Sign. Wave Height']) ** (-0.65) * (1 - np.exp(-0.41 * (1 / breakwaterPTS_times.loc[breakMask_C, 'Slope'] /
          np.sqrt(2 * math.pi * breakwaterPTS_times.loc[breakMask_C, 'Sign. Wave Height'] / 9.81 / breakwaterPTS_times.loc[breakMask_C, 'Peak Wave Period'] ** 2)))))))

    breakwaterPTS_times.loc[breakMask_D, 'transmitted'] = breakwaterPTS_times.loc[breakMask_D, 'Sign. Wave Height'] * \
            ((np.maximum(0.05, np.minimum(np.maximum(0, -0.006 * breakwaterPTS_times.loc[breakMask_D, 'Width'] / breakwaterPTS_times.loc[breakMask_D, 'Sign. Wave Height'] + 0.93),
              (-0.35 * (breakwaterPTS_times.loc[breakMask_D, 'Crest'] - breakwaterPTS_times.loc[breakMask_D, 'Surface elevation']) /
              breakwaterPTS_times.loc[breakMask_D, 'Sign. Wave Height'] + 0.51 * (breakwaterPTS_times.loc[breakMask_D, 'Width'] /
              breakwaterPTS_times.loc[breakMask_D, 'Sign. Wave Height']) ** (-0.65) * (1 - np.exp(-0.41 * (1 / breakwaterPTS_times.loc[breakMask_D, 'Slope'] /
              np.sqrt(2 * math.pi * breakwaterPTS_times.loc[breakMask_D, 'Sign. Wave Height'] / 9.81 / breakwaterPTS_times.loc[breakMask_D, 'Peak Wave Period'] ** 2))))))) -
            np.maximum(0, np.minimum(0.8, (-0.4 * (breakwaterPTS_times.loc[breakMask_D, 'Crest'] - breakwaterPTS_times.loc[breakMask_D, 'Surface elevation']) /
              breakwaterPTS_times.loc[breakMask_D, 'Sign. Wave Height'] + 0.64 * (breakwaterPTS_times.loc[breakMask_D, 'Width'] /
              breakwaterPTS_times.loc[breakMask_D, 'Sign. Wave Height']) ** (-0.31) * (1 - np.exp(-0.5 * (1 / breakwaterPTS_times.loc[breakMask_D,'Slope'] /
              np.sqrt(2 * math.pi * breakwaterPTS_times.loc[breakMask_D, 'Sign. Wave Height'] / 9.81 / breakwaterPTS_times.loc[breakMask_D,'Peak Wave Period'] ** 2)))))))) *
            (breakwaterPTS_times.loc[breakMask_D,'Width'] / breakwaterPTS_times.loc[breakMask_D, 'Sign. Wave Height'] - 8) / (12 - 8) +\
              np.maximum(0, np.minimum(0.8, (-0.4 * (breakwaterPTS_times.loc[breakMask_D, 'Crest'] - breakwaterPTS_times.loc[breakMask_D, 'Surface elevation']) /
              breakwaterPTS_times.loc[breakMask_D, 'Sign. Wave Height'] + 0.64 * (breakwaterPTS_times.loc[breakMask_D, 'Width'] /
              breakwaterPTS_times.loc[breakMask_D, 'Sign. Wave Height']) ** (-0.31) * (1 - np.exp(-0.5 * (1 / breakwaterPTS_times.loc[breakMask_D, 'Slope'] /
              np.sqrt(2 * math.pi * breakwaterPTS_times.loc[breakMask_D, 'Sign. Wave Height'] / 9.81 / breakwaterPTS_times.loc[breakMask_D, 'Peak Wave Period'] ** 2))))))))




    # %% Merge with breakwater transformed waves
    breakwaterPTS_end = breakwaterPTS_times['Time'] == breakwaterPTS_times['Time'].max()

    # %% Read Inner Boundary Points North
    innerBoundNorth = pd.read_csv(
        '//srv-mad3.baird.com/Projects/13632.101 South Shore Breakwater/06_Models/02_Mike21SW/InnerBound_Mesh2_North.txt',
        delimiter='\s+', header=None, names=['node', 'val', 'x', 'y'])

    innerBoundNorth.sort_values(by=['x'], inplace=True)
    innerBoundNorth_gdf = gp.GeoDataFrame(innerBoundNorth, crs='EPSG:32616',
                                          geometry=gp.points_from_xy(innerBoundNorth.x, innerBoundNorth.y))
    innerBoundNorth_gdf = innerBoundNorth_gdf.sjoin_nearest(breakwaterPTS_times.loc[breakwaterPTS_end, :])
    # %% Read Inner Boundary Points Mid
    # innerBoundMid = pd.read_csv(
    #     '//srv-mad3.baird.com/Projects/13632.101 South Shore Breakwater/06_Models/02_Mike21SW/InnerBound_Mid.txt',
    #     delimiter='\s+', header=None, names=['x', 'y'])
    innerBoundMid = pd.read_csv(
        '//srv-mad3.baird.com/Projects/13632.101 South Shore Breakwater/06_Models/02_Mike21SW/InnerBound_Mesh2_Mid.txt',
        delimiter='\s+', header=None, names=['node', 'val', 'x', 'y'])

    innerBoundMid.sort_values(by=['x'], inplace=True)
    innerBoundMid_gdf = gp.GeoDataFrame(innerBoundMid, crs='EPSG:32616',
                                        geometry=gp.points_from_xy(innerBoundMid.x, innerBoundMid.y))
    innerBoundMid_gdf = innerBoundMid_gdf.sjoin_nearest(breakwaterPTS_times.loc[breakwaterPTS_end, :])

    # %% Read Inner Boundary Points South
    innerBoundSouth = pd.read_csv(
        '//srv-mad3.baird.com/Projects/13632.101 South Shore Breakwater/06_Models/02_Mike21SW/InnerBound_South.txt',
        delimiter='\s+', header=None, names=['x', 'y'])
    innerBoundSouth.sort_values(by=['x'], inplace=True)
    innerBoundSouth_gdf = gp.GeoDataFrame(innerBoundSouth, crs='EPSG:32616',
                                          geometry=gp.points_from_xy(innerBoundSouth.x, innerBoundSouth.y))
    innerBoundSouth_gdf = innerBoundSouth_gdf.sjoin_nearest(breakwaterPTS_times.loc[breakwaterPTS_end, :])

    # %% Save as DFS1
    boundDataNorth = pd.DataFrame(
        innerBoundNorth_gdf.loc[:, ['Time', 'transmitted', 'Peak Wave Period', 'Mean Wave Direction']])
    boundDataNorth.set_index('Time', inplace=True)
    boundDataNorth.rename(columns={'transmitted': 'Significant wave height', 'Peak Wave Period': 'Peak wave period',
                                   'Mean Wave Direction': 'Mean wave direction'},
                          inplace=True)

    dfsfilename = '//oak-spillway.baird.com/F/13632.101 South Shore Breakwater/SetupFiles/InnerBounds/' + \
                  runLog['Run Name'][m] + 'TestInnerNorth_RevB.dfs1'

    items = [ItemInfo("Significant wave height", EUMType.Significant_wave_height, EUMUnit.meter),
             ItemInfo("Peak wave period", EUMType.Wave_period, EUMUnit.second),
             ItemInfo("Mean wave direction", EUMType.Mean_Wave_Direction, EUMUnit.degree),
             ItemInfo("Directional Standard Deviation, n", EUMType.Directional_Standard_Deviation, EUMUnit.degree)]

    dfs = Dfs1()

    dfs.write(filename=dfsfilename,
              data=[np.array((boundDataNorth['Significant wave height'], boundDataNorth['Significant wave height'])),
                    np.array((boundDataNorth['Peak wave period'], boundDataNorth['Peak wave period'])),
                    np.array((boundDataNorth['Mean wave direction'], boundDataNorth['Mean wave direction'])),
                    np.ones((2, boundDataNorth.shape[0])) * 23.28],
              start_time=breakwaterPTS_times['Time'].min(),
              dt=86400,
              datetimes=None,
              items=items,
              title=None)


    # %% Save as DFS1- Mid
    boundDataMid = pd.DataFrame(
        innerBoundMid_gdf.loc[:, ['Time', 'transmitted', 'Peak Wave Period', 'Mean Wave Direction']])
    boundDataMid.set_index('Time', inplace=True)
    boundDataMid.rename(columns={'transmitted': 'Significant wave height', 'Peak Wave Period': 'Peak wave period',
                                 'Mean Wave Direction': 'Mean wave direction'},
                        inplace=True)

    dfsfilename = '//oak-spillway.baird.com/F/13632.101 South Shore Breakwater/SetupFiles/InnerBounds/' + \
                  runLog['Run Name'][m] + 'TestInnerMid_RevB.dfs1'

    items = [ItemInfo("Significant wave height", EUMType.Significant_wave_height, EUMUnit.meter),
             ItemInfo("Peak wave period", EUMType.Wave_period, EUMUnit.second),
             ItemInfo("Mean wave direction", EUMType.Mean_Wave_Direction, EUMUnit.degree),
             ItemInfo("Directional Standard Deviation, n", EUMType.Directional_Standard_Deviation, EUMUnit.degree)]

    dfs = Dfs1()
    dfs.write(filename=dfsfilename,
              data=[np.array((boundDataMid['Significant wave height'], boundDataMid['Significant wave height'])),
                    np.array((boundDataMid['Peak wave period'], boundDataMid['Peak wave period'])),
                    np.array((boundDataMid['Mean wave direction'], boundDataMid['Mean wave direction'])),
                    np.ones((2, boundDataMid.shape[0])) * 23.28],
              start_time=breakwaterPTS_times['Time'].min(),
              dt=86400,
              items=items)

    # %% Save as DFS1- South
    boundDataSouth = pd.DataFrame(
        innerBoundSouth_gdf.loc[:, ['Time', 'transmitted', 'Peak Wave Period', 'Mean Wave Direction']])
    boundDataSouth.set_index('Time', inplace=True)
    boundDataSouth.rename(columns={'transmitted': 'Significant wave height', 'Peak Wave Period': 'Peak wave period',
                                   'Mean Wave Direction': 'Mean wave direction'},
                          inplace=True)

    dfsfilename = '//oak-spillway.baird.com/F/13632.101 South Shore Breakwater/SetupFiles/InnerBounds/' + \
                  runLog['Run Name'][m] + 'ProdInnerSouth.dfs1'

    items = [ItemInfo("Significant wave height", EUMType.Significant_wave_height, EUMUnit.meter),
             ItemInfo("Peak wave period", EUMType.Wave_period, EUMUnit.second),
             ItemInfo("Mean wave direction", EUMType.Mean_Wave_Direction, EUMUnit.degree),
             ItemInfo("Directional Standard Deviation, n", EUMType.Directional_Standard_Deviation, EUMUnit.degree)]

    dfs = Dfs1()
    dfs.write(filename=dfsfilename,
              data=[np.array((boundDataSouth['Significant wave height'], boundDataSouth['Significant wave height'])),
                    np.array((boundDataSouth['Peak wave period'], boundDataSouth['Peak wave period'])),
                    np.array((boundDataSouth['Mean wave direction'], boundDataSouth['Mean wave direction'])),
                    np.ones((2, boundDataSouth.shape[0])) * 23.28],
              start_time=breakwaterPTS_times['Time'].min(),
              dt=86400,
              items=items)


# %% Test Plot
mapbox = 'https://api.mapbox.com/styles/v1/alexander0042/ckemxgtk51fgp19nybfmdcb1e/tiles/256/{z}/{x}/{y}@2x?access_token=pk.eyJ1IjoiYWxleGFuZGVyMDA0MiIsImEiOiJjazVmdG4zbncwMHY4M2VrcThwZGUzZDFhIn0.w6oDHoo1eCeRlSBpwzwVtw'

fig, axes = plt.subplots(nrows=1, ncols=1, figsize=(9, 9), constrained_layout=True)

breakwaterPTS_merge.plot(ax=axes, column='Sign. Wave Height', cmap='viridis')
# breakwaterPTS_merge.plot(ax=axes, column='Crest', cmap='viridis', legend=True)
innerBound_gdf.plot(ax=axes, column='transmitted', cmap='viridis')

ctx.add_basemap(axes, source=mapbox, crs='EPSG:32616')

plt.show()
