text: """
  Tide Predictor
  
  Generates tide predictions using the pytides module.
  
  Convert University of Hawaii research quality datasets into tidal
  harmonic constituents for making predictions on any location that data
  exists for.
  
  Datasets and background info available from:
  ftp://ftp.soest.hawaii.edu/uhslc/rqds/
  """
  from __future__ import print_function
  import datetime
  from tidepredict.tide import Tide
  import numpy as np
  #import matplotlib.pyplot as plt
  import pandas as pd
  import io
  import zipfile
  import sys
  from ftplib import FTP
  import calendar
  import pytz
  from jinja2 import Environment, FileSystemLoader
  from tidepredict import ftp_helpers
  from tidepredict import constants
  import json
  from tidepredict import constituent
  import dateutil
  import pathlib
  
  def get_data_url(ocean = "pacific"):
      """returns the data file url for the uhslc server for a specific
      ocean area.
  
      accepted inputs are one of the following locations:
      pacific
      indian
      atlantic
      """
      #do string.lower to make it match for any case (user friendly)
      if ocean.lower() == "pacific":
          ftpurl = "uhslc/rqds/pacific"
      elif ocean.lower() == "atlantic":
          ftpurl = "uhslc/rqds/atlantic"
      elif ocean.lower() == "indian":
          ftpurl = "uhslc/rqds/indian"
      else:
          raise Exception("Ocean must be one of: Indian, Pacific, or Atlantic")
          sys.exit(1)
      return ftpurl  
  
  def predict_plain(tide, station_dict, format, timeobj):
      """
      Generates tide predictions similar to Xtide's plain mode.
      startdate for prediction (Python datetime)
      enddate:  enddate for prediction
      tide: the tide model to use
      """
      
      if format == "t":
          extrema = "Tide forecast for %s, %s\n" %(station_dict['name'],
                                          station_dict['country'])
          extrema += "Latitude:%5.2f Longitude:%5.2f\n" %(station_dict['lat'],
                                          station_dict['lon'])                                                                
      else:
          extrema = ""
  
      extremaUTC = tide.extrema(timeobj.st_utc, timeobj.en_utc)
      #print(extremaUTC)
      
      for e in extremaUTC:
          #print(e)
          #get localised time
          time = timeobj.localise(e[0])
          height = e[1]
          if format == "c":
              #csv so append station name
              extrema += station_dict['name'] + ","
              extrema += time.strftime("%Y-%m-%d,%H%M")
          else:
              extrema += time.strftime("%Y-%m-%d %H%M")
          
          if format == "c":
              extrema += ",%s," %timeobj.tz
          else:
              extrema += " %s" %timeobj.tz
  
          extrema += "%5.2f" %height   
  
          if format == "c":
              extrema += ","
          
          if e[2] == "L":
              extrema += " Low Tide"
          else:
              extrema += " High Tide"
          extrema += "\n"    
      
      return extrema
  
  
  def reconstruct_tide_model(station_dict, loc_code):
      """
      Method to reconstruct the tide model from the dictionary of all stations
      input: tm_file, which is the open json file
      """
      try:
          constits = [constituent.__getattribute__("_"+cstr) 
                  for cstr in station_dict[loc_code]['cons']]
      except KeyError:
          return None
  
      model = np.zeros(len(station_dict[loc_code]['cons']), dtype = Tide.dtype)
      assert len(constits) == len(station_dict[loc_code]['amps']) \
              == len(station_dict[loc_code]['phase']), \
             "model file arrays must be equal length"
      model['constituent'] = constits
      model['amplitude'] = station_dict[loc_code]['amps']
      model['phase'] = station_dict[loc_code]['phase']
      tide = Tide(model = model, radians = False)
      return tide
  
  def process_unhw_data(ftpurl, years = [15,16], loc_code = "h551a"):
      """Processes university of Hawaii data into a Python dictionary.
  
      Inputs:
      years: years of data (default most recent two years) as a list of 
             year ints
      location: unhw code for the location to retrieve. Default: Lyttelton, NZ
  
      Outputs:
      Dictionary in the form of {DateTime:TideLevel}
      timestamps are hourly.
  
      """
      datalist = []  
      #print(zipdat)
      #print(filedata)
      sio = ftp_helpers.get_byte_stream(constants.FTP_BASE,
                                        "%s/hourly/%s.zip"%(ftpurl,loc_code))
      for year in years:
          #try to open the downloaded zip archive
          try:
              zarchive = zipfile.ZipFile(sio)
          except zipfile.BadZipfile:
              print("Could not open zip archive")
              sys.exit()
  
          #print(zarchive)
          if year > int(str(datetime.datetime.today().year)[:-2]):
              #assume pre 2000 so h prefix required.
              datfile = "h%s%i.dat"%(loc_code[1:],year)
          else:    
              datfile = "i%s%i.dat"%(loc_code[1:],year)
          print("Opening data file:%s"%datfile)
          try:
              fdat = zarchive.open(datfile, "r")
          except RuntimeError:
              print('RuntimeError')
              sys.exit()
  
          #so we have the file data now read it line by line
          for i, line in enumerate(fdat):
              #ignore header info on the first row
              #todo should try and check file validity here.
              if i == 0:
                continue
              #print(line.decode("UTF-8"))
              linedc = line.decode("UTF-8")
              #tide height data starts at column 22 (py index = 21)
              tideheights = linedc[21:].split()
              #date data starts at column 12(11) ends at 20 
              dateraw = linedc[11:20]
              dateactual = ( dateraw[:4] + "-" + dateraw[4:6] + "-" + dateraw[6:8]
                          )
                  
              for j,item in enumerate(tideheights):
                  #check for bad data 
                  if int(item) == 9999 or int(item) == -9999:
                      continue
                  datakey = dateactual
                  if dateraw[8] == "1":
                      datakey += " %02d" %(j) + ":00:00"
                  else:
                      datakey += " %02d" %(j + 12) + ":00:00"
                  data = float(item)/1000
                  datalist.append([datakey, data])
          fdat.close()
      return datalist
  
  def output_to_file(datalist):
      """Outputs the processed datadict to a textfile
      
          Mainly used for debugging purposes
      """
      f2 = open("unhw_processed.dat", "w")
      for item in datalist:
         print("%s %s" %(item[0], item[1]), file = f2)
      f2.close()
  
  def fit_model(datalist):
      """Fits harmonic model to tides using pytides
      """
  
      t = []
      heights = []
      for dt, height in datalist:
          #print(line.split()[:2])
          t.append(datetime.datetime.strptime(dt, "%Y-%m-%d %H:%M:%S"))
          heights.append(float(height))
  
      ##Fit the tidal data to the harmonic model using Pytides
      print("Fitting harmonic model")
      #print(heights[:5])
      #print(t[:5])
      my_tide = Tide.decompose(np.array(heights), np.array(t))
      return my_tide
  
  def output_html(my_tide, month, year):
      """ Dumps a html page of a calendar month of tide predictions
          todo: generalise
      """
      ##Prepare our variables for the template
      location = "Lyttelton, NZ"
      tzname = "Pacific/Auckland"
      tz = datetime.timezone(tzname)
      utc = datetime.timezone('UTC')
      datum = "MLLW"
      units = "metres"
      rows = []
      print("Running tide prediction")
      for day in range(1,calendar.monthrange(year,month)[1] + 1):
          start = tz.localize(datetime.datetime(year, month, day))
          end = start + datetime.timedelta(days=1)
          startUTC = utc.normalize(start.astimezone(utc))
          endUTC = utc.normalize(end.astimezone(utc))
          extremaUTC = my_tide.extrema(startUTC, endUTC)
          date = {'date': day, 'day': calendar.day_abbr[start.weekday()]}
          extrema = []
          for e in extremaUTC:
              e = list(e)
              #print(e)
              time = tz.normalize(e[0].astimezone(tz))
              ##Round the time to the nearest minute
              time = time + datetime.timedelta(minutes=time.second > 30)
              height = e[1]
              extrema.append({'time': time.strftime('%H:%M'), 'height': "{0:.2f}".format(height)})
          #This is just for nicer formatting of days with only three tides
          for _ in range(4 - len(extrema)):
              extrema.append({'time': '', 'height': ''})
          rows.append([date, extrema])
  
      ##Render our template
      #print(" ".join(rows))
      #get the current working directory - this is useful on windows machines
      mydir = pathlib.Path.home()
      print(mydir)
      env = Environment(loader=FileSystemLoader(mydir),trim_blocks=True)
      template = env.get_template('template.html')
      with open(mydir / "output.html", "w") as fh:
          print(template.render(
          location = location,
          tzname = tzname,
          datum = datum,
          units = units,
          year = year,
          month = calendar.month_name[month],
          data = rows
          ), file = fh)    
  
  if __name__ == "__main__":
      pass
      #plot_data(datadict)
      #output_html(my_tides, 9, 2019)
  