#!/usr/bin/env bash
#######################################################################################
#
# weatherman - display weather information from the command line
#
# (c) Copyright (c) 2013, Rob Hunter. All rights reserved.
# (c) Copyright (c) 2013, darkhorse.nu. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. 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.
#
# THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR OR CONTRIBUTORS 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.
#
#######################################################################################
VERSION="1.2.2"

type -P curl &>/dev/null || { echo "Could not find curl in $PATH" >&2; exit 1; }

usage() {
    EXE=$(basename $0)
    cat << EOF
Usage:
 $EXE [options] LOCATION 

Examples:
    $EXE "Tokyo, Japan"
    $EXE -e 77001

Options:
 -m          use metric units (Celcius, millibars, millimeters)
 -e          use English units (Fahrenheit, inches)
 -x          show extended forecast
 -F FORMAT   print output using specified format
 -S          set unit and location setting as defaults
 -h          display this help and exit
 -v          output version information and exit

EOF
    exit
}


UNITS=1
if [[ -r "$HOME/.weathermanrc" ]]; then
    . $HOME/.weathermanrc
fi

while getopts "emxF:Shv" OPTION
do
    case $OPTION in
        e)
            UNITS=0
            ;;
        m)  UNITS=1
            ;;
        x)
            FC_EXTENDED=1
            ;;
        F)
            FORMAT=$OPTARG
            ;;
        S)
            SAVE_OPTIONS=1
            ;;
        h)
            usage
            ;;
        v)  echo "weatherman $VERSION" 
            exit
            ;; 
        ?)
            usage
            ;;
    esac
done
[[ $UNITS = "1" ]] && TEMP_UNIT=C || TEMP_UNIT=F
[[ $FORMAT && $FC_EXTENDED ]] && echo "WARNING: -F option isn't usable with extended forecast - ignoring"

shift $(( $OPTIND -1 ))
while test $# -gt 0; do
    LOC="$1"
shift
done

if [[ -z $LOC ]]; then
    usage
fi

if [[ $(echo $LOC | awk '{ if ($1 ~ /^[0-9][0-9][0-9][0-9][0-9]$/) print "1"; }') ]];
then
    PROVIDER_URI="?zip=$LOC"
    UNITS_STRING="%26units=$UNITS"
else
    LOCSTRING=$(echo $LOC | awk -F'[,/]' '{print $2"/"$1}')
    PROVIDER_URI=$(echo $LOCSTRING-weather.html | sed 's/ /%20/g')
    UNITS_STRING="?units=$UNITS"
fi

# Don't display banner in format mode
[[ ! $FORMAT ]] && echo -n "Gathering data..."

PROVIDER_URL=$(curl --connect-timeout 10 --max-time 10 -sL -w "%{url_effective}\\n" http://weather.weatherbug.com/$PROVIDER_URI -o /dev/null)
[[ $? > 0 ]] && { echo -en "\rConnection to the data provider failed.\n"; exit 1; };

if [[ -z $FC_EXTENDED ]]; then

    IFSOLD=$IFS
    IFS="
"
    for line in $(curl -f --connect-timeout 10 --max-time 10 -s -L $PROVIDER_URL$UNITS_STRING); do
 
        [[ $line =~ "alerttype"       ]] && { ALERT=$(echo $line | sed -n '/\.*/s/.*alerttype">\(.*\)<\/a>.*/\1/p'); continue; };
        [[ $line =~ "tRecentLocation" ]] && { LOCATION=$(echo $line | awk -F"'" '{if ($8 == "") print $6", "$2; else print $6", "$4" "$8; }'); continue; };
        [[ $line =~ "divObsTime"      ]] && { OBSTIME=$(echo $line | sed -n '/\.*/s/.*>\(.*\)<\/div.*/\1/p'); continue; };
        [[ $line =~ "divTemp"         ]] && { TEMP_NOW=$(echo $line | sed -n '/\.*/s/.*>\(.*\)&deg.*/\1/p' | awk '{ rounded = sprintf("%.0f", $1); print rounded }'); continue; };
        [[ $line =~ "loc-windspd"     ]] && { WINDSPEED=$(echo $line | sed -n '/\.*/s/.*Speed">\(.*\)<\/span><span.*/\1/p'); continue; };
        [[ $line =~ "divWindVaneDir"  ]] && { WINDDIR=$(echo $line | sed -n '/\.*/s/.*loc-windvane-arrw-\(.*\)"><\/div.*/\1/p' | tr 'a-z' 'A-Z'); continue; };
        [[ $line =~ "divHi"           ]] && { TEMP_HIGH=$(echo $line | sed -n '/\.*/s/.*>\(.*\)&deg.*/\1/p'); continue; };
        [[ $line =~ "divLo"           ]] && { TEMP_LOW=$(echo $line | sed -n '/\.*/s/.*>\(.*\)&deg.*/\1/p'); continue; };
        [[ $line =~ "divFeelsLike"    ]] && { TEMP_INDEX=$(echo $line | sed -n '/\.*/s/.*>\(.*\)&deg.*/\1/p'); continue; };
        [[ $line =~ "divHumidity"     ]] && { HUMIDITY=$(echo $line | sed -n '/\.*/s/.*>\(.*\)%<\/span.*/\1/p'); continue; };
        [[ $line =~ "divRain"         ]] && { RAIN=$(echo $line | sed -n '/\.*/s/.*>\(.*\)<\/span.*/\1/p' | sed 's/&quot;/"/' | \
                                            awk '{ if ($1 == "0\"") print "0.00\""; \
                                                   else if ($1 == "N/A\"") print "N/A"; \
                                                   else print $1; }'); continue; };
        [[ $line =~ "divGust"         ]] && { GUST=$(echo $line | sed -n '/\.*/s/.*>\(.*\)<\/span.*/\1/p'); continue; };
        [[ $line =~ "divDewPoint"     ]] && { DEWPOINT=$(echo $line | sed -n '/\.*/s/.*>\(.*\)&deg.*/\1/p'); continue; };
        [[ $line =~ "divAvgWind"      ]] && { WINDAVG=$(echo $line | sed -n '/\.*/s/.*>\(.*\)<\/span.*/\1/p' | awk '{print $2" "$1}'); continue; };
        [[ $line =~ "divPressure"     ]] && { PRESSURE=$(echo $line | sed -n '/\.*/s/.*>\(.*\)<\/span.*/\1/p' | sed 's/&quot;/"/'); continue; };
        [[ $line =~ "Rain/Month:"     ]] && { RAINAVG=$(echo $line | sed -n '/\.*/s/.*>\(.*\)<\/span.*/\1/p' | sed 's/&quot;/"/' | \
                                            awk '{ if ($1 == "0\"") print "0.00\""; \
                                                   else if ($1 == "N/A\"") print "N/A"; \
                                                   else print $1; }'); continue; };
        [[ $line =~ "Sunrise:"        ]] && { SUNRISE=$(echo $line | sed -n '/\.*/s/.*>\(.*\)<\/span.*/\1/p'); continue; };
        [[ $line =~ "Sunset:"         ]] && { SUNSET=$(echo $line | sed -n '/\.*/s/.*>\(.*\)<\/span.*/\1/p'); continue; };
        [[ $line =~ "divStationName"  ]] && { STATION=$(echo $line | sed -n '/\.*/s/.*>\(.*\)<\/span.*/\1/p' | sed 's/\s$//'); continue; };
        [[ $line =~ "mphase"          ]] && { MOONPHASE=$(echo $line | sed -n '/\.*/s/.*mphase\(.*\).png".*/\1/p' | \
                                            awk '{ if ($1 == "01") print "New"; \
                                                   else if ($1 ~ /^0[2-6]$/) print "Waxing Crescent"; \
                                                   else if ($1 == "07") print "First Quarter"; \
                                                   else if ($1 ~ /^0[8-9]$/ || $1 ~ /1[0-3]/) print "Waxing Gibbous"; \
                                                   else if ($1 == "14") print "Full"; \
                                                   else if ($1 ~ /^1[4-9]$/ || $1 == "20") print "Waning Gibbous"; \
                                                   else if ($1 == "21") print "Last Quarter"; \
                                                   else if ($1 ~ /^2[2-6]$/) print "Waning Crescent"; \
                                                   else print "--"; }'); continue; };

        [[ $line =~ "loc-forecast"   ]] && BEGIN_FC=1
        [[ $line =~ "loc-changestat" ]] && break;

        if [[ $BEGIN_FC ]]; then
            [[ $line =~ "<h3>" ]] && FC_TIME+=( $(echo $line | sed -n '/\.*/s/.*<h3>\(.*\)<\/h3>.*/\1/p') )
            [[ $line =~ "<img" ]] && FC_DETAIL+=( $(echo $line | sed -n '/\.*/s/.* alt="\(.*\)" border.*/\1/p') )
        fi

    done
    IFS=$IFSOLD

    # Assume we didn't get any data from the provider if OBSTIME is empty and exit
    if [[ -z $OBSTIME ]]; then
        echo -e "\rNo data.         "
        exit 1
    fi

    if [[ $FORMAT ]]; then

        # Sequence character mappings
        declare -A seq
        seq[%u]=$TEMP_UNIT
        seq[%l]=$LOCATION
        seq[%n]=$STATION
        seq[%o]=$OBSTIME
        seq[%a]=$ALERT
        seq[%c]=$TEMP_NOW
        seq[%T]=$TEMP_HIGH
        seq[%t]=$TEMP_LOW
        seq[%i]=$TEMP_INDEX
        seq[%h]=$HUMIDITY
        seq[%d]=$DEWPOINT
        seq[%w]=$WINDSPEED
        seq[%W]=$WINDDIR
        seq[%g]=$WINDAVG
        seq[%p]=$PRESSURE
        seq[%r]=$RAIN
        seq[%R]=$RAINAVG
        seq[%s]=$SUNRISE
        seq[%S]=$SUNSET
        seq[%m]=$MOONPHASE

        for i in "${!seq[@]}"; do
            FORMAT=$(echo -n $FORMAT | sed s/$i/"${seq[$i]}"/g)
        done
  
        for i in $(echo $FORMAT | grep -o '%*%.*'); do

            # Handle literal %
            if [[ $i =~ ^%%.*$ ]]; then
                LTRL_END=$(echo $i | sed -n '/\.*/s/^..\(.*\)/\1/p')
            
                if [[ $LTRL_END =~ ^% ]]; then
                    echo "$LTRL_END is not a valid format sequence."
                    exit 1
                else
                    FORMAT=$(echo -n $FORMAT | sed s/%%$LTRL_END/%$LTRL_END/)
                fi
            else
                echo "$i is not a valid format sequence."
                exit 1
            fi

        done

        echo -e $FORMAT

    else
        [[ $UNITS = "1" ]] && PADOFFSET=2 || PADOFFSET=0
        echo -en "\r"
        echo "Current Conditions for $LOCATION"
        echo "Reported by $STATION @ $OBSTIME"
        echo ""
        [[ $ALERT ]] && echo -e "ALERT: $ALERT\n"
        echo "Temp: $TEMP_NOW $TEMP_UNIT"
        echo "${FC_TIME[0]}: ${FC_DETAIL[0]}"
        echo "${FC_TIME[1]}: ${FC_DETAIL[1]}"
        echo ""
        echo -n "High: $TEMP_HIGH $TEMP_UNIT";     PADLEN=$((11 - ${#TEMP_HIGH})); for((n=1;n<=$PADLEN;n++)); do echo -n " "; done
        echo -n "Wind Speed: $WINDDIR $WINDSPEED"; PADLEN=$((7 - (${#WINDSPEED} + ${#WINDDIR}) + $PADOFFSET)); for((n=1;n<=$PADLEN;n++)); do echo -n " "; done
        echo "Rain: $RAIN"
        echo -n "Low: $TEMP_LOW $TEMP_UNIT"; PADLEN=$((12 - ${#TEMP_LOW})); for((n=1;n<=$PADLEN;n++)); do echo -n " "; done
        echo -n "Gust: $GUST";               PADLEN=$((14 - ${#GUST} + $PADOFFSET)); for((n=1;n<=$PADLEN;n++)); do echo -n " "; done
        echo "Rain/Month: $RAINAVG"

        echo -n "Feels Like: $TEMP_INDEX"
        [[ ! $TEMP_INDEX = "N/A" ]] && echo -n " $TEMP_UNIT" || echo -n "  "
        PADLEN=$((5 - ${#TEMP_INDEX})); for((n=1;n<=$PADLEN;n++)); do echo -n " "; done

        echo -n "Avg Wind: $WINDAVG"; PADLEN=$((10 - ${#WINDAVG} + $PADOFFSET)); for((n=1;n<=$PADLEN;n++)); do echo -n " "; done
        echo "Sunrise: $SUNRISE"

        echo -n "Humidity: $HUMIDITY";
        [[ ! $HUMIDITY = "N/A" ]] && echo -n "%" || echo -n " "
        PADLEN=$((8 - ${#HUMIDITY})); for((n=1;n<=$PADLEN;n++)); do echo -n " "; done

        echo -n "Pressure: $PRESSURE"; PADLEN=$((10 - ${#PRESSURE} + $PADOFFSET)); for((n=1;n<=$PADLEN;n++)); do echo -n " "; done
        echo "Sunset: $SUNSET"

        echo -n "Dew Point: $DEWPOINT"
        [[ ! $DEWPOINT = "--" ]] && echo -n " $TEMP_UNIT" || echo -n "  ";
        PADLEN=$((26 - ${#DEWPOINT} + $PADOFFSET)); for((n=1;n<=$PADLEN;n++)); do echo -n " "; done

        echo "Moonphase: $MOONPHASE"
        echo ""
        echo "Data provided by WeatherBug.com"
    fi

fi

# Extended forecast
if [[ $FC_EXTENDED ]]; then

    PROVIDER_URL=$(echo -n $PROVIDER_URL | sed 's/.html.*$/\/local-forecast\/7-day-forecast.html/' && echo "?units=$UNITS")

    # Insert null at array index 0
    FC_X_DAY=("")
    FC_X_TEMP=("")
    FC_X_DETAIL=("")

    OLDIFS=$IFS
    IFS="
"
    for line in $(curl -f --connect-timeout 10 --max-time 10 -s -L $PROVIDER_URL); do
    
        [[ $line =~ "tRecentLocation"     ]] && { LOCATION=$(echo $line | awk -F"'" '{if ($8 == "") print $6", "$2; else print $6", "$4" "$8; }'); continue; };
        [[ $line =~ "\"box-7-day-fcast\"" ]] && BEGIN_PARSE=1
        [[ $line =~ "\"boxmore\""         ]] && break

        if [[ $BEGIN_PARSE ]]; then

            if [[ $line =~ "<h3>" ]]; then
                FC_X_DAY+=( $(echo $line | tr -d '\r' | sed -n '/\.*/s/.*<h3>\(.*\)<\/h3>/\1/p') )
            fi

            [[ $line =~ "&deg;" ]] && FC_X_TEMP+=( $(echo $line | tr -d '\r' | sed -n '/\.*/s/.*\">\(.*\)&deg;<\/span>.*/\1/p') )
            [[ $line =~ "<p>"   ]] && FC_X_DETAIL+=( $(echo $line | tr -d '\r' | sed -n '/\.*/s/.*<p>\(.*\)<\/p>.*/\1/p') )

        fi

    done
    IFS=$OLDIFS

    # Assume we didn't get any data from the provider if index 1 is empty and exit
    if [[ -z ${FC_X_DAY[1]} ]]; then
        echo -e "\rNo data.         "
        exit 1
    fi

    # "Tonight" is sometimes at array index 7 instead of 1 depending on the time of day.
    # In this case, the data is moved into index 0 which is normally empty. 
    # If index 0 has data, the display loop rearranges the output in the correct order.
    if [[ ${FC_X_DAY[7]} = "Tonight" && ! ${FC_X_DAY[1]} = "Today" ]]; then
        FC_X_DAY[0]=${FC_X_DAY[7]}
        FC_X_TEMP[0]=${FC_X_TEMP[7]}
        FC_X_DETAIL[0]=${FC_X_DETAIL[7]}

        FC_X_FLIPPED=1
    fi

    ARRAYLEN=$((${#FC_X_DAY[@]} / 2))

    # Calculate max buffer length to be used for alignment
    PADLENMAX="0"
    for ((i=0; i<=$ARRAYLEN; i++)); do
        [[ ${#FC_X_DETAIL[$i]} -gt $PADLENMAX ]] && PADLENMAX=${#FC_X_DETAIL[$i]}
    done
    PADLENMAX=$(($PADLENMAX + 3))

    echo -en "\r"
    echo "Extended Forecast for $LOCATION"
    echo ""
    for ((i=0; i<=$ARRAYLEN; i++)); do

        # Skip iteration if index 0 is empty
        [[ $i = "0" && ${FC_X_DAY[0]} = "" ]] && continue

        # Exit loop if order is flipped on last iteration
        [[ $i = "7" && $FC_X_FLIPPED ]] && break

        echo -n "${FC_X_DAY[$i]^^}"
        PADLEN=$((11 - (${#FC_X_DAY[$i]} + ${#FC_X_TEMP[$i]}))); for((n=1;n<=$PADLEN;n++)); do echo -n " "; done
        echo -n "${FC_X_TEMP[$i]} $TEMP_UNIT  ${FC_X_DETAIL[$i]}"

        if [[ ${FC_X_TEMP[$i+$ARRAYLEN]} && $i > 0 ]]; then
            PADLEN=$(( $PADLENMAX - ${#FC_X_DETAIL[$i]} + (3 - ${#FC_X_TEMP[$i+$ARRAYLEN]}) )); for((n=1;n<=$PADLEN;n++)); do echo -n " "; done
            echo -n "${FC_X_TEMP[$i+$ARRAYLEN]} $TEMP_UNIT  ${FC_X_DETAIL[$i+$ARRAYLEN]}"
        fi

        echo ""
    done

    echo -e "\nData provided by WeatherBug.com"

fi

if [[ $SAVE_OPTIONS ]]; then

    cat > ~/.weathermanrc <<EOF
LOC="$LOC"
UNITS=$UNITS
EOF

    echo -e "\nSaved settings to ~/.weathermanrc"
fi

exit 0
