"use strict"

loadFile = (url, callback, progress) ->
  xhr = new XMLHttpRequest
  xhr.open 'GET', url, true
  xhr.onreadystatechange = ->
    if xhr.readyState == 4
      if xhr.status >= 200 and xhr.status <= 299
        callback xhr.responseText.split "\n"
      else if xhr.status == 404
        callback [ ]
      else
        Application.reportFailure "HTTP Error: #{xhr.status}",
          -> loadFile(url, callback)
    else
      try
        if progress?
          progress xhr.responseText.length / parseInt(xhr.getResponseHeader('Content-length'))
      catch e
        # nothing
  xhr.send null
  xhr

Application =
  dataRequest: null
  unrequited: null
  inputBoxes: 
    pco2: {label:"CO<sub>2</sub> (ppm)";value:400;explanation:'Atmospheric CO<sub>2</sub> concentration in ppm'}
    ch4: {label:"CH<sub>4</sub> (ppm)"; value:1.7;explanation:'Atmospheric CH<sub>4</sub> concentration in ppm'}
    trop_o3: {label:"Trop. Ozone (ppb)"; value:28;explanation:'Tropospheric ozone concentration in ppb'}
    strat_o3: {label:"Strat. Ozone scale"; value:1 ;explanation:'Multiply the standard model stratospheric ozone concentration by this fraction'}
    Toffset: {label:"Ground T offset, C"; value:0;explanation:'Offset the ground and atmospheric column temperature by this amount, Centigrade'}
    h2orat:  {label:"Water Vapor Scale"; value:1;explanation:'Multiply the standard model tropospheric water vapor concentration by this fraction'}
    altitude: {label:"Altitude (km)"; value:70; explanation:'At what altitude to place the virtual sensor for the model calculation'}

  selectors:
    h2otscaled:
      choices: ['Water Vapor Pressure', 'Relative Humidity' ]
      values: [ 0, 1 ]
      selected: 0
      label: 'Holding Fixed'
      explanation: 'When the temperature is offset from the standard model, the water vapor can either be held at constant pressure or it can follow the saturation to hold a fixed relative humidity'
    model: 
      choices: ['Tropical Atmosphere', 'Midlatitude Summer', 'Midlatitude Winter', 'Subarctic Summer', 'Subarctic Winter', '1976 U.S. Standard Atmosphere' ]
      values: [ 1, 2, 3, 4, 5, 6 ]
      selected: 1
    icld:
      choices: ['No Clouds or Rain',
        'Cumulus Cloud Base .66km Top 2.7km',
        'Altostratus Cloud Base 2.4km Top 3.0km',
        'Stratus Cloud Base .33km Top 1.0km', 
        'Stratus/Strato CU Base .66km Top 2.0km',
        'Nimbostratus Cloud base .16km Top .66km',
        '2 mm/hr Drizzle (modeled with Stratus Cloud)',
        '5.0mm/hr Light Rain (modeled with Nimbostratus)',
        '12.5mm/hr Moderate Rain (modeled with Nimbostratus)',
        '25.0mm/hr Heavy Rain (modeled with Cumulus)',
        '75.0mm/hr Extreme Rain (modeled with Cumulus)',
        'Standard Cirrus Model',
        'Sub visual Cirrus Model',
        'NOAA Cirrus Model',
        '(LOWTRAN 6 Model)']
      values: [0,1,2,3,4,5,6,7,8,9,10,18,19,20]
      selected: 0
    i_obs:
      choices: ['Looking up', 'Looking down']
      values: ["0", "180"]
      selected: "180"
    spectrumPlotData:
      choices: [ "Wavenumber", "Wavelength (linear)", "Wavelength (log)"  ]
      values: [  "Wavenumber", "Wavelength (linear)", "Wavelength (log)" ]
      googleYLabels: [ ['Wavenumber (1/cm)','Intensity (W/m2 cm-1)'], [ 'Wavelength (microns)', 'Intensity (W/m2 micron)' ],[ 'Wavelength (microns)','Intensity (W/m2 log(micron) )' ] ]
      selected: 'Wavenumber'
    profilePlotData:
      choices: [ "Pressure (mbar)","T (K)","Water Vapor (mbar)","Ozone (ppm)","CO2 (ppm)","CH4 (ppm)" ]
      values: [ "Pressure (mbar)","T (K)","Water Vapor (mbar)","Ozone (ppm)","CO2 (ppm)","CH4 (ppm)" ]
      googleYLabels: [ "Pressure (mbar)","T (K)","Water Vapor (mbar)","Ozone (ppm)","CO2 (ppm)","CH4 (ppm)" ]

      selected: "T (K)"

  pushButtons: 
    toBackground:
      value: 'Save This Run to Background' 
    rawOutput:
      value: 'Show Raw Model Output'
      
  modelInputFields: [ "pco2", "ch4","trop_o3","strat_o3","Toffset","h2otscaled","h2orat","model","icld","altitude","i_obs","i_save" ]
  modelInputValues: new Object
  profileHeaders: []
  profileOutput: []
  spectrumHeaders: []
  spectrumOutput: []
  backgroundSpectrumOutput: null
  backgroundProfileOutput: null
  spectrumChart: null
  profileChart: null

  setupInputs: ->
    console.log "starting setupInput"
    for inputName, inputObject of Application.inputBoxes

      inputElement = document.getElementById( inputName + "-input")
      inputElement.innerHTML = '<input name="' + inputName + '" value="' + inputObject.value + '">'
      inputElement.addEventListener 'change',  ((evt) ->
        inputName = evt.target.name
        Application.inputBoxes[ inputName ]?.value = evt.target.value

        if inputName is "Toffset"
          h2otscaledElement = document.getElementById( "hidden" )
          if evt.target.value is 0
            h2otscaledElement.style.display = "none"
          else
            h2otscaledElement.style.display = "table-row"

#        Application.setupInputs()
        console.log "calling runModel from input change", Application.dataRequest
        Application.runModel()
      ), false

      inputElement = document.getElementById( inputName + "-expl" )
      inputElement.innerHTML = inputObject.label
      inputElement?.onmouseover = (evt) =>
        inputName = evt.target.id
        inputName = inputName.substr(0, inputName.length - 5)
        explanation = Application.inputBoxes[ inputName ]?.explanation
        commentBoxElement = document.getElementById( 'commentBox' )
        Application.outputCommentHTML = commentBoxElement.innerHTML 
        commentBoxElement.innerHTML = explanation
      inputElement?.onmouseout = (evt) =>
        commentBoxElement = document.getElementById( 'commentBox' )
        commentBoxElement.innerHTML = Application.outputCommentHTML

    for selectorName, selectorObject of Application.selectors
      selectorElement = document.getElementById( selectorName + '-expl' )
      selectorElement?.innerHTML = selectorObject.label

      selectorElement?.onmouseover = (evt) =>
        inputName = evt.target.id
        inputName = inputName.substr(0, inputName.length - 5)
        explanation = Application.selectors[ inputName ]?.explanation
        commentBoxElement = document.getElementById( 'commentBox' )
        Application.outputCommentHTML = commentBoxElement.innerHTML 
        commentBoxElement.innerHTML = explanation
      selectorElement?.onmouseout = (evt) =>
        commentBoxElement = document.getElementById( 'commentBox' )
        commentBoxElement.innerHTML = Application.outputCommentHTML

      selectorElement = document.getElementById( selectorName )

      selectorHTML = []
      selectorHTML.push('<select name="' + selectorName + '" class="textButton">')
      for choice, indexChoices in selectorObject.choices
        value = selectorObject.values[ indexChoices ]
        selectorHTML.push('<option value="' + value + '"')
        if value is selectorObject.selected then selectorHTML.push(' selected')
        selectorHTML.push('>' + choice )
      selectorHTML.push('</select>')
      selectorElement?.innerHTML = selectorHTML.join('')
      
      selectorElement?.addEventListener 'change', ((evt) ->
        selectorName = evt.currentTarget.id  # eg select-i_obs
        Application.selectors[ selectorName ].selected  = evt.target.value
        if selectorName is "spectrumPlotData" or selectorName is "profilePlotData"
          console.log "calling plotData from selector change"
          Application.plotData()
        else
          console.log "calling runModel from selector change"
          Application.runModel()
        ), false

    toBackgroundElement = document.getElementById( "toBackground" )
    toBackgroundElement.innerHTML = '<button class="textButton">Save This Run to Background</button>'
    toBackgroundElement.onclick = =>
      toBackgroundElement = document.getElementById( "toBackground" )
      if Application.backgroundSpectrumOutput?
        console.log "deleting background"
        Application.backgroundSpectrumOutput = null
        Application.backgroundProfileOutput = null
        Application.backgroundIOut = null
        toBackgroundElement.innerHTML = '<button class="textButton">Save This Run to Background</button>'
        console.log "calling plotData from background change"
        Application.plotData()
      else
        console.log "saving background"
        Application.backgroundSpectrumOutput = Application.spectrumOutput
        Application.backgroundProfileOutput = Application.profileOutput
        Application.backgroundIOut = Application.iOut
        toBackgroundElement = document.getElementById( "toBackground" )
        toBackgroundElement.innerHTML = '<button class="textButton">Delete Background Model Run</button>'

    rawButtonElement = document.getElementById( "rawModel" )
    rawButtonElement.innerHTML = '<button class="textButton">Show Raw Model Output</button>'
    rawButtonElement.onclick = =>
      rawWindow = window.open("","Raw Model Output")
      rawWindow.document.open("text/plain")
#      console.log "raw", Application.rawModelOutput[0..1].join("\n\r")
      rawWindow.document.write ( "<pre>" )
      rawWindow.document.write ( Application.rawModelOutput.join("\n") )
      rawWindow.document.write ( "</pre>" )
      rawWindow.document.close()

  runModel: ->
    console.log "starting runModel", @dataRequest

    @unrequited = null
    loadURL = []
    Application.spectrumOutput = []
    Application.profileOutput = []

    loadURL.push( '../../cgi-bin/modtran/modtran.cgi?' )
    for parameterName, parameterObject of @inputBoxes
      if @modelInputFields.indexOf( parameterName ) >= 0
        @modelInputValues[ parameterName ] = @inputBoxes[ parameterName ].value
    for selectorName, selectorObject of @selectors
      if @modelInputFields.indexOf( selectorName ) > 0
        @modelInputValues[ selectorName ] = selectorObject.selected
    @modelInputValues[ "i_save" ] = 0
    for inputField in @modelInputFields
      inputValue = @modelInputValues[ inputField ]
      loadURL.push( inputField + "=" + inputValue + "&" )
    loadURL = loadURL.join('')
    loadURL = loadURL.substr( 0, loadURL.length-1 )

    if loadURL is '/cgi-bin/modtran.cgi?pco2=400&ch4=1.7&trop_o3=28&strat_o3=1&Toffset=0&h2otscaled=0&h2orat=1&model=1&icld=0&altitude=70&i_obs=180&i_save=0'
      loadURL = 'modtran.base.out'

    console.log "loadURL", loadURL

#    loadURL = 'modtran.out'

    if @dataRequest
      console.log "bailing", @dataRequest
      @unrequited = true
      return

    @dataRequest = loadFile loadURL, (lines) =>
      Application.rawModelOutput = lines

#     parse into array
      rawWords = []
      for line, index in lines
        line = line.replace(/^\ +/, "") 
        line = line.replace(/\ +/g, ",")
        split = line.split(",")
        rawWords.push split

#     spectrum data
      Application.spectrumHeaders = [ "Wavenumber (cm<sup>-1</sup>)", "Wavelength (micron)", "Intensity (per wavenumber)", "Intensity (per micron)" ]
      freq = false
      count = 0
      for line, index in lines
        if rawWords[ index ][ 1 ] is "FREQ" then freq = true
        if freq 
          logify = 1
          if rawWords[ index ][ 0 ] > 99# and rawWords[ index ][ 0 ] < 2000 # filters the blank and header lines
            count += 1

            spectrumRow = []
            spectrumRow.push rawWords[ index ][ 0 ] #wavenumber
            spectrumRow.push rawWords[ index ][ 1 ] #wavelength

            if index < rawWords.length-1
              logify = ( Math.log(rawWords[index+1][1]) - Math.log(rawWords[index][1]) ) / ( rawWords[index+1][1]-rawWords[index][1] )


            if Application.planckSpectra[ count-1 ]?
              for planckIndex in [7..11]
                Application.planckSpectra[ count-1 ].push Application.planckSpectra[ count-1 ][ planckIndex ] * logify
            

            if rawWords[ index ].length is 12
              spectrumRow.push rawWords[ index ][ 8 ] * 3.14e4
              spectrumRow.push rawWords[ index ][ 9 ] * 3.14e4
              spectrumRow.push rawWords[ index ][ 9 ] * 3.14e4 * logify
            else
              spectrumRow.push rawWords[ index ][ 2 ] * 3.14e4
              spectrumRow.push rawWords[ index ][ 3 ] * 3.14e4
              spectrumRow.push rawWords[ index ][ 3 ] * 3.14e4 * logify
            if spectrumRow.length > 0
              Application.spectrumOutput.push spectrumRow.map(parseFloat)

#     chemical profiles
      Application.profileHeaders = [ "Altitude (km)", "Pressure (mbar)","T (K)","Water Vapor (mbar)",
        "Ozone (ppm)", "CO2 (ppm)", "CH4 (ppm)" ]

      gotOne = 0
      for line, index in lines

        if rawWords[ index ][ 0 ].substr(1,rawWords[ index ][ 0 ].length) is "INTEGRATED"
          if rawWords[ index ][ 1 ] is "RADIANCE"
            Application.iOut = rawWords[ index ][ 3 ] * 3.14e4
        
        if rawWords[ index ][ 0 ] is "BOUNDARY"
          if rawWords[ index ][ 1 ] is "TEMPERATURE"
            Application.groundT = rawWords[ index ][ 3 ]

        if rawWords[ index ][ 0 ] is "ATMOSPHERIC"
          fuckJavascript = rawWords[index][1]
          if fuckJavascript is "PROFILES"
            gotOne += 1

        profileRow = []
        if gotOne is 3 and rawWords[ index ][ 1 ] < 100 and rawWords[ index ].length is 15
          profileRow[1..Application.profileHeaders.length] = 0
          pressure = rawWords[ index ][ 2 ]
          h2oVap = rawWords[ index ][ 4 ] / 1e5 * pressure
          profileRow[ Application.profileHeaders.indexOf("Altitude (km)") ] = rawWords[ index ][ 1 ]
          profileRow[ Application.profileHeaders.indexOf("Pressure (mbar)") ] = pressure
          profileRow[ Application.profileHeaders.indexOf( "T (K)") ] = rawWords[ index ][ 3 ]
          profileRow[ Application.profileHeaders.indexOf( "Water Vapor (mbar)" ) ] = h2oVap
          profileRow[ Application.profileHeaders.indexOf( "Ozone (ppm)" ) ] = rawWords[ index ][ 5 ] * 1e4 / ( pressure - h2oVap )
          profileRow[ Application.profileHeaders.indexOf( "CO2 (ppm)" ) ] = rawWords[ index ][ 6 ] * 1e4 / ( pressure - h2oVap )
          profileRow[ Application.profileHeaders.indexOf( "CH4 (ppm)" ) ] = rawWords[ index ][ 8 ] * 1e4 / ( pressure - h2oVap )
          Application.profileOutput.push profileRow

      @dataRequest = null

      if @unrequited? 
        console.log "rerunning runModel", @unrequited
        Application.runModel()

      console.log "calling plotData from runModel", @dataRequest
    
      Application.plotData()

  plotData: ->
    console.log "starting plotData", @selectors.i_obs

    commentBox = document.getElementById( "commentBox" )
    commentHTML = []
    commentHTML.push('<table class="padded-table">')
    commentHTML.push('<tr><td>')
    if @selectors.i_obs.selected is "180"
      commentHTML.push('Upward')
    else
      commentHTML.push('Downward')
    commentHTML.push(' IR Heat Flux</td>')
    commentHTML.push('<td><b>' + Math.round(Application.iOut*100)/100 + '</b></td>')
    commentHTML.push('<td>W/m<sup>2</sup></td></tr>')

    if Application.backgroundIOut?
      commentHTML.push('<tr><td>IR Heat Loss (Background)</td>')
      commentHTML.push('<td><b>' + Math.round(Application.backgroundIOut*100)/100 + '</b></td>')
      commentHTML.push('<td>W/m<sup>2</sup></td></tr>')

    commentHTML.push('<tr><td>Ground Temperature</td>')
    commentHTML.push('<td><b>' + Math.round(Application.groundT*100)/100 + '</td>')
    commentHTML.push('<td> K</td></tr></table>')
    commentBox.innerHTML = commentHTML.join("")

    #spectrum plot

    graphDataTable = new google.visualization.DataTable

    choiceNumber = Application.selectors.spectrumPlotData.choices.indexOf( Application.selectors.spectrumPlotData.selected )

    if Application.selectors.spectrumPlotData.selected is "Wavenumber"
      modelSpectrumXColumn = 0
      modelSpectrumIColumn = 2
      planckSpectraColumn = 2
    if Application.selectors.spectrumPlotData.selected is "Wavelength (linear)"
      modelSpectrumXColumn = 1
      modelSpectrumIColumn = 3
      planckSpectraColumn = 7
    if Application.selectors.spectrumPlotData.selected is "Wavelength (log)"
      modelSpectrumXColumn = 1
      modelSpectrumIColumn = 4
      planckSpectraColumn = 12

    graphDataTable.addColumn 'number', Application.selectors.spectrumPlotData.googleYLabels[ choiceNumber ][ 0 ]  # wavenumber
    graphDataTable.addColumn 'number', 'Model'

    if Application.backgroundSpectrumOutput? 
      graphDataTable.addColumn 'number', 'Model Alt'

    for planckIndex in [0..4]
      graphDataTable.addColumn 'number', Application.planckHeader[ planckIndex + planckSpectraColumn ]
        
    for modeldataArray, rowNumber in Application.spectrumOutput

      rowForGoogle = []

      rowForGoogle.push Application.spectrumOutput[ rowNumber ][ modelSpectrumXColumn ] # wavenumber
      rowForGoogle.push Application.spectrumOutput[ rowNumber ][ modelSpectrumIColumn ] # intensity

      if @backgroundSpectrumOutput?
        rowForGoogle.push Application.backgroundSpectrumOutput[ rowNumber ][ modelSpectrumIColumn ]

      for planckIndex in [0..4]
        rowForGoogle.push Application.planckSpectra[ rowNumber ]?[ planckSpectraColumn + planckIndex ]

      graphDataTable.addRow rowForGoogle

#      if rowNumber > Application.spectrumOutput.length-5 then console.log "rowForGoogle" + rowNumber, rowForGoogle

    options = new Object
    options.hAxis = { title: Application.selectors.spectrumPlotData.googleYLabels[ choiceNumber ][ 0 ] }
    options.vAxis = { title: Application.selectors.spectrumPlotData.googleYLabels[ choiceNumber ][ 1 ] }
    options.backgroundColor = { fill: 'transparent' }
    options.tooltip = { trigger: "none" }

    if Application.selectors.spectrumPlotData.selected is "Wavelength (linear)"
      options.hAxis.viewWindowMode = 'explicit'
      options.hAxis.viewWindow = {min:5;max:30;}
    if Application.selectors.spectrumPlotData.selected is "Wavelength (log)"
      options.hAxis.logScale = true
      options.hAxis.viewWindowMode = 'explicit'
      options.hAxis.viewWindow = {min:5;max:50;}

    graphElement = document.getElementById('spectrum')

    Application.spectrumChart ||= new google.visualization.ComboChart( graphElement )

    Application.spectrumChart.draw graphDataTable, options

 # profile plot

    profileDataTable = new google.visualization.DataTable

    choiceNumber = Application.selectors.profilePlotData.choices.indexOf( Application.selectors.profilePlotData.selected )
    columnNumber = Application.profileHeaders.indexOf( Application.selectors.profilePlotData.selected )

    profileDataTable.addColumn 'number', Application.selectors.spectrumPlotData.selected
    profileDataTable.addColumn 'number', 'Altitude (km)'

    for rowArray, index in Application.profileOutput 
       rowForGoogle = []
       rowForGoogle.push rowArray[ columnNumber ]
       rowForGoogle.push rowArray[ 0 ]
       profileDataTable.addRow rowForGoogle.map(parseFloat)

    options = new Object
    options.hAxis = { title: Application.profileHeaders[ columnNumber ] }
    options.vAxis = { title: Application.profileHeaders[ 0 ] }
    if Application.selectors.i_obs.selected is 180 and Application.inputBoxes.altitude.value > 1
      options.vAxis.viewWindowMode = 'explicit'
      options.vAxis.viewWindow = {min:0;max:Application.inputBoxes.altitude.value}
    options.backgroundColor = {fill: 'transparent' }
    options.legend = {position: "none"}

    graphElement = document.getElementById('profile')

    Application.profileChart ||= new google.visualization.ComboChart( graphElement )

    Application.profileChart.draw profileDataTable, options


google.load('visualization', '1.0', {'packages':['corechart']})

google.setOnLoadCallback ->
  loadURL = "planck.spectra.csv"
  loadFile loadURL, (lines) =>
    Application.planckHeader = lines[0].split(',')
    Application.planckSpectra = []
    for line, index in lines[1...]
      valueArray = line.split(',').map(parseFloat)
      if valueArray.length > 1
        Application.planckSpectra.push valueArray
    console.log "calling setupInputs from google callback"
    Application.setupInputs()
    console.log "calling runModel from google callback"
    Application.runModel()




