# Copyright (c) 2014, Vienna University of Technology (TU Wien), Department
# of Geodesy and Geoinformation (GEO).
# All rights reserved.

# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of the Vienna University of Technology - Department of
#   Geodesy and Geoinformation nor the names of its contributors may be used to
#   endorse or promote products derived from this software without specific
#   prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL VIENNA UNIVERSITY OF TECHNOLOGY,
# DEPARTMENT OF GEODESY AND GEOINFORMATION BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# Author: Thomas Mistelbauer Thomas.Mistelbauer@geo.tuwien.ac.at
# Creation date: 2014-05-26

from cStringIO import StringIO
import os
from flask import Flask, render_template, jsonify, make_response
from flask.ext.cors import CORS
import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from poets.timedate.dateindex import get_dtindex
from poets.web.overlays import bounds
import pytesmo.time_series as ts


def curpath():
    """
    Gets the current path of the module.

    Returns
    -------
    pth : str
        Path of the module.
    """
    pth, _ = os.path.split(os.path.abspath(__file__))
    return pth


def to_dygraph_format(self):
    """
    Transforms pandas DataFrame to Dygraphs compatible format.

    Returns
    -------
    labels : list of str
        Labels of the Dygraphs array.
    values : list
        Values of the Dygraphs array.
    """

    labels = ['date']
    labels.extend(self.columns.values.tolist())
    data_values = np.hsplit(self.values, self.columns.values.size)
    data_index = self.index.values.astype('M8[s]').tolist()
    data_index = [x.strftime("%Y/%m/%d %H:%M:%S") for x in data_index]
    data_index = np.reshape(data_index, (len(data_index), 1))
    data_values.insert(0, data_index)
    data_values = np.column_stack(data_values)
    values = data_values.tolist()

    return labels, values

pd.DataFrame.to_dygraph_format = to_dygraph_format

dest = os.path.join(curpath(), 'static', 'temp')

app = Flask(__name__, static_folder='static', static_url_path='/static',
            template_folder="templates")
app.config['CORS_HEADERS'] = 'Content-Type'
cors = CORS(app, resources={r"/*": {"origins": "*"}})


def start(poet, host='127.0.0.1', port=5000, debug=False):
    """
    Starts application and sets global variables.

    Parameters
    ----------
    poet : Poet()
        Instance of Poet class.
    host : str, optional
        Host that is used by the app, defaults to 127.0.0.1.
    port : int, optional
        Port where app runs on, defaults to 50000.
    debug : bool, optional
        Starts app in debug mode if set True, defaults to False.
    """

    global regions
    global sources
    global variables
    global dates
    global vmin, vmax, cmap
    global shapefile
    global nan_value
    global host_gl
    global port_gl
    global tmp_res

    regions = poet.regions
    sources = poet.sources
    variables = poet.get_variables()
    shapefile = poet.shapefile
    nan_value = poet.nan_value
    tmp_res = poet.temporal_resolution
    host_gl = host
    port_gl = port

    if debug:
        app.run(debug=True, use_debugger=True, use_reloader=True, host=host,
                port=port)
    else:
        app.run(host=host, port=port)


@app.route('/', methods=['GET', 'POST'])
@app.route('/<reg>&<var>', methods=['GET', 'POST'])
def index(**kwargs):
    """
    Renders main page of the web application. Generates image arguments needed
    for OpenLayers overlay if parameters `reg` and `var` are set, renders
    entry page if not set.
    """

    global enddate
    global dates
    global ndate
    global idxdates

    if len(kwargs) > 0:

        if 'reg' in kwargs:
            region = kwargs['reg']
        if 'var' in kwargs:
            variable = kwargs['var']

        for src in sources.keys():
            if src in variable:
                source = sources[src]

        ndate = source._check_current_date()
        begindate = ndate[region][variable][0]
        enddate = ndate[region][variable][1]

        if begindate is None and enddate is None:

            error = 'No data available for this dataset.'

            return render_template('index.html',
                                   regions=regions,
                                   sources=sources.keys(),
                                   variables=variables,
                                   error=error)

        d = get_dtindex(tmp_res, begindate, enddate)
        dates = d.to_pydatetime()
        idxdates = len(dates) - 1

        fdates = []

        for i, d in enumerate(dates.tolist()):
            dat = {'id': i, 'date': d.strftime('%Y-%m-%d')}
            fdates.append(dat)

        lon_min, lon_max, lat_min, lat_max, c_lat, c_lon, zoom = \
            bounds(region, shapefile)

        return render_template('app.html',
                               max=idxdates,
                               coord=[c_lon, c_lat],
                               zoom=zoom,
                               ex1=(lon_max, lat_min),
                               ex2=(lon_min, lat_max),
                               region=region,
                               source=source.name,
                               variable=variable,
                               regions=regions,
                               variables=variables,
                               dates=fdates,
                               host=host_gl,
                               port=port_gl)
    else:
        return render_template('index.html',
                               regions=regions,
                               sources=sources.keys(),
                               variables=variables)


@app.route('/_ts/<reg>&<src>&<var>&<loc>', methods=['GET', 'OPTIONS'])
@app.route('/_ts/<reg>&<src>&<var>&<loc>&<anom>', methods=['GET', 'OPTIONS'])
def get_ts(**kwargs):
    """
    Gets time series for selected location, gets anomaly of time series if
    `anom` parameter is passed.

    Returns
    -------
    jsonified str
        Time series (anomaly) in Dygraphs compatible json format.
    """

    anomaly = False

    if 'reg' in kwargs:
        region = kwargs['reg']
    if 'src' in kwargs:
        source = sources[kwargs['src']]
    if 'var' in kwargs:
        variable = kwargs['var']
    if 'loc' in kwargs:
        loc = kwargs['loc']
    if 'anom' in kwargs:
        anomaly = True

    loc = loc.split(',')
    lonlat = (float(loc[0]), float(loc[1]))

    df = source.read_ts(lonlat, region, variable, shapefile)

    if anomaly:
        df = ts.anomaly.calc_anomaly(df, window_size=100)
        columns = []
        for cols in df.columns:
            columns.append(cols + '_anomaly')
        df.columns = columns

    labels, values = df.to_dygraph_format()
    data = {'labels': labels, 'data': values}

    return jsonify(data)


@app.route('/_tsdown/<reg>&<src>&<var>&<loc>')
@app.route('/_tsdown/<reg>&<src>&<var>&<loc>&<anom>')
def download_ts(**kwargs):
    """
    Initiates download time series (anomaly) in comma separated values format.

    Returns
    -------
    jsonified str
        Time series (anomaly) in Dygraphs compatible json format.
    """

    anomaly = False

    if 'reg' in kwargs:
        region = kwargs['reg']
    if 'src' in kwargs:
        source = sources[kwargs['src']]
    if 'var' in kwargs:
        variable = kwargs['var']
    if 'loc' in kwargs:
        loc = kwargs['loc']
    if 'anom' in kwargs:
        anomaly = True

    loc = loc.split(',')
    lonlat = (float(loc[0]), float(loc[1]))

    filename = region + '_' + variable + '_' + loc[0][:6] + '_' + loc[1][:6]

    df = source.read_ts(lonlat, region, variable, shapefile)

    if anomaly:
        df = ts.anomaly.calc_anomaly(df)
        columns = []
        for cols in df.columns:
            columns.append(cols + '_anomaly')
        df.columns = columns

        filename += '_anomaly'

    output = StringIO()

    df.to_csv(output)
    csv = output.getvalue()

    response = make_response(csv)
    response.headers["Content-Disposition"] = ("attachment; filename=" +
                                               filename + ".csv")

    return response


@app.route('/_rimg/<reg>&<src>&<var>&<idx>', methods=['GET', 'POST'])
def request_image(**kwargs):
    """
    Creates image for OpenLayers overlay.

    Returns
    -------
    StringIO
        Image in StringIO.
    """

    global vmin
    global vmax
    global metadata
    global cmap

    if 'reg' in kwargs:
        region = kwargs['reg']
    if 'src' in kwargs:
        source = sources[kwargs['src']]
    if 'var' in kwargs:
        variable = kwargs['var']
    if 'idx' in kwargs:
        idx = kwargs['idx']

    pidx = (dates[int(idx)])

    img, _, _, metadata = source.read_img(pidx, region, variable)

    if source.unit is not None:
        if metadata is not None and 'unit' not in metadata:
            metadata['unit'] = source.unit
        elif metadata is None:
            metadata = {}
            metadata['unit'] = source.unit

    if source.valid_range is not None:
        vmin = source.valid_range[0]
        vmax = source.valid_range[1]
    else:
        vmin = np.nanmin(img)
        vmax = np.nanmax(img)

    cmap = source.colorbar

    # Resale the image
    n = 10
    img = np.kron(img, np.ones((n, n)))
    img[img == nan_value] = np.NAN

    buf = StringIO()
    plt.imsave(buf, img, vmin=vmin, vmax=vmax, cmap=cmap)

    image = buf.getvalue()

    response = make_response(image)
    response.headers["Content-Type"] = ("image/png; filename=data.png")

    return response


@app.route('/_rlegend/<reg>&<var>', methods=['GET', 'POST'])
@app.route('/_rlegend/<reg>&<var>&<idx>', methods=['GET', 'POST'])
def request_legend(**kwargs):
    """
    Creates Legend for OpenLayers overlay.

    Returns
    -------
    StringIO
        Legend in StringIO.
    """

    global vmin
    global vmax
    global metadata
    global cmap

    fig = plt.figure(figsize=(4, 0.7))
    ax1 = fig.add_axes([0.05, 0.7, 0.9, 0.10])
    norm = mpl.colors.Normalize(vmin=vmin, vmax=vmax)
    cb1 = mpl.colorbar.ColorbarBase(ax1, cmap=cmap, norm=norm,
                                    orientation='horizontal')
    plt.xticks(fontsize=9)

    if metadata:
        units = ['units', 'unit', 'UNITS', 'UNIT']
        for unit in units:
            if unit in metadata:
                cb1.set_label(metadata[unit], fontsize=10)

    buf = StringIO()

    plt.savefig(buf)
    plt.close()

    image = buf.getvalue()

    response = make_response(image)
    response.headers["Content-Type"] = ("image/png; filename=legend.png")

    return response


@app.route('/about')
def about():
    """
    Creates the `about` page.
    """
    return render_template('about.html')
