import os
import time
from datetime import datetime, timedelta
import dateutil

from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User

# from template_utils.markup import formatter
import csv

from directory.models import Point, Place, Town

from utils.moon_phase import moon_phase


class StandardMetadata(models.Model):
    """
    A basic (abstract) model for metadata.
    
	Included in each model file to maintain application separation.
	
	Subclass new models from 'StandardMetadata' instead of 'models.Model'.
    """

    created = models.DateTimeField(default=datetime.now, editable=False)
    updated = models.DateTimeField(default=datetime.now, editable=False)

    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
        self.updated = datetime.now()
        super(StandardMetadata, self).save(*args, **kwargs)


class WeatherAlert(models.Model):
    """ WeatherAlert model.

        Based on the NOAA weather alert feed. """

    zone_id = models.CharField(_("NOAA zone id"), max_length=6)
    zone = models.CharField(_("NOAA zone"), max_length=255)
    updated = models.DateTimeField(_("updated"))
    summary = models.TextField(_("summary"), blank=True, null=True)
    effective = models.DateTimeField(_("effective time"))
    expires = models.DateTimeField(_("expiration time"))
    status = models.CharField(_("status"), max_length=30, blank=True, null=True)
    msg_type = models.CharField(_("message type"), max_length=30, blank=True, null=True)
    urgency = models.CharField(_("urgency"), max_length=30, blank=True, null=True)
    severity = models.CharField(_("severity"), max_length=30, blank=True, null=True)
    certainty = models.CharField(_("certainty"), max_length=30, blank=True, null=True)
    area = models.TextField(_("affected area"), blank=True, null=True)

    class Meta:
        verbose_name = _("weather alert")
        verbose_name_plural = _("weather alerts")
        ordering = (
            "effective",
            "expires",
        )
        get_latest_by = "effective"

    def __unicode__(self):
        return u"Weather alert for %s until %s" % (self.zone, self.expires)


class WeatherConditions(models.Model):
    """ WeatherConditions model."""

    town = models.ForeignKey(Town)
    observation_time = models.DateTimeField(_("observation time"))
    conditions = models.CharField(_("conditions"), max_length=140)
    temperature = models.FloatField(_("temperature (F)"))
    wind_dir = models.CharField(_("wind direction"), max_length=5)
    wind_speed = models.FloatField(_("wind speed (MPH)"))
    pressure = models.CharField(_("pressure (in)"), max_length=30)
    humidity = models.CharField(_("humitidy"), max_length=4)

    class Meta:
        verbose_name = _("weather condition")
        verbose_name_plural = _("weather conditions")
        ordering = ("observation_time",)
        get_latest_by = "observation_time"

    def __unicode__(self):
        return u"%s %s" % (self.town, self.observation_time)


class WeatherForecast(models.Model):
    town = models.ForeignKey(Town)
    observation_time = models.DateTimeField(_("observation time (RFC822)"))
    period = models.CharField(_("period"), max_length=15)
    conditions = models.CharField(_("conditions"), max_length=255)
    high_temperature_f = models.IntegerField(
        _("high temperature (F)"), max_length=3, blank=True, null=True
    )
    low_temperature_f = models.IntegerField(
        _("low temperature (F)"), max_length=3, blank=True, null=True
    )
    wind_conditions = models.CharField(
        _("wind conditions"), max_length=100, blank=True, null=True
    )

    class Meta:
        verbose_name = _("weather condition")
        verbose_name_plural = _("weather conditions")
        ordering = ("observation_time",)
        get_latest_by = "observation_time"

    def __unicode__(self):
        return u"%s %s" % (self.town, self.observation_time)


HEMISPHERES = (
    ("N", _("North")),
    ("S", _("South")),
)

MOON_PHASES = (
    (0, _("New")),
    (1, _("1st Q.")),
    (2, _("Full")),
    (3, _("3rd Q.")),
)


class AstroChart(models.Model):
    year = models.IntegerField(_("year"), max_length=4)
    dst_start = models.DateField(_("daylight savings start"))
    dst_end = models.DateField(_("daylight savings end"))
    hemisphere = models.CharField(max_length=1, choices=HEMISPHERES, default="N")

    class Meta:
        verbose_name = _("astronomical chart")
        verbose_name_plural = _("astronomical charts ")
        ordering = ("-year",)
        get_latest_by = "year"

    def __unicode__(self):
        return u"%s Astronomical Chart for the %s Hemisphere" % (
            self.year,
            self.hemisphere,
        )


class Day(models.Model):
    date = models.DateField(_("date"))
    chart = models.ForeignKey(AstroChart)
    sunrise = models.TimeField(_("sunrise"), null=True, blank=True)
    sunset = models.TimeField(_("sunset"), null=True, blank=True)
    moonrise = models.TimeField(_("moonrise"), null=True, blank=True)
    moonset = models.TimeField(_("moonset"), null=True, blank=True)
    moonphase = models.IntegerField(
        _("moon phase"), choices=MOON_PHASES, null=True, blank=True
    )
    moonphase_time = models.TimeField(_("moon phase time"), null=True, blank=True)
    moonphase_fraction = models.FloatField(
        _("moon phase fraction"), null=True, blank=True
    )
    civil_twilight_am = models.TimeField(
        _("morning civil twilight"), null=True, blank=True
    )
    civil_twilight_pm = models.TimeField(
        _("evening civil twilight"), null=True, blank=True
    )
    nautical_twilight_am = models.TimeField(
        _("morning nautical twilight"), null=True, blank=True
    )
    nautical_twilight_pm = models.TimeField(
        _("evening nautical twilight"), null=True, blank=True
    )
    daily_avg_temp = models.DecimalField(
        _("daily average temperature"),
        null=True,
        blank=True,
        decimal_places=2,
        max_digits=5,
    )
    daily_high_temp = models.DecimalField(
        _("daily high temperature"),
        null=True,
        blank=True,
        decimal_places=2,
        max_digits=5,
    )
    daily_low_temp = models.DecimalField(
        _("daily low temperature"),
        null=True,
        blank=True,
        decimal_places=2,
        max_digits=5,
    )
    monthly_avg_temp = models.DecimalField(
        _("monthly average temperature"),
        null=True,
        blank=True,
        decimal_places=2,
        max_digits=5,
    )
    yearly_avg_temp = models.DecimalField(
        _("yearly average temperature"),
        null=True,
        blank=True,
        decimal_places=2,
        max_digits=5,
    )

    class Meta:
        verbose_name = _("day")
        verbose_name_plural = _("days")
        ordering = ("date",)
        get_latest_by = "date"

    def __unicode__(self):
        return u"%s" % (self.date)


TIDE_STATES = (
    ("H", _("High")),
    ("L", _("Low")),
)


class TideChart(StandardMetadata):
    place = models.ForeignKey(Place)
    year = models.CharField(max_length=4)
    mean_range = models.FloatField()
    spring_range = models.FloatField()
    mean_tide_level = models.FloatField()
    public = models.BooleanField(_("public"), default=False)
    reference = models.ForeignKey(
        "self", blank=True, null=True, related_name="ref_chart"
    )
    high_tide_offset = models.FloatField(_("high tide offset"), blank=True, null=True)
    low_tide_offest = models.FloatField(_("low tide offset"), blank=True, null=True)
    high_time_offset = models.IntegerField(
        _("high time offset"), blank=True, null=True, max_length=2
    )
    low_time_offset = models.IntegerField(
        _("low time offset"), blank=True, null=True, max_length=2
    )

    class Meta:
        verbose_name = _("tide chart")
        verbose_name_plural = _("tide charts")
        ordering = ("place",)

    def __unicode__(self):
        return u"%s %s %s" % (self.place.title, "Tide Chart", self.year)

    def get_absolute_url(self):
        args = self.place.slug + "-" + self.year
        return reverse("al-tide-chart-detail", args=args)

    def save(self, *args, **kwargs):
        super(TideChart, self).save(*args, **kwargs)
        if self.reference:
            if not TideDay.objects.filter(tide_chart=self):
                # There are no days for this chart and it's referenced from another one
                # Let's generate the days
                self.generate_tidedays(self.reference.tideday_set.all())

    def generate_tidedays(self, tidedays):
        fill_tide = Tide.objects.create(time="12:00", level=0.0, state="L")
        overflow = False
        for ref_day in tidedays:
            new_day = TideDay.objects.create(day=ref_day.day, tide_chart=self)

            if overflow:  # We overflowed from the previous day, so offset tides
                new_day.tide_1 = self.generate_tide(fill_tide)
                new_day.tide_2 = self.generate_tide(ref_day.tide_1)
                new_day.tide_3 = self.generate_tide(ref_day.tide_2)
                new_day.tide_4 = self.generate_tide(ref_day.tide_3)
                overflow = False  # Reset the overflow flag
            else:
                new_day.tide_1 = self.generate_tide(ref_day.tide_1)
                new_day.tide_2 = self.generate_tide(ref_day.tide_2)
                new_day.tide_3 = self.generate_tide(ref_day.tide_3)
                try:  # Does ref_day.tide_4 exist?
                    if ref_day.tide_4.time.hour == 23:
                        if ref_day.tide_4.state == "H":
                            if (
                                ref_day.tide_4.time.minute + self.high_time_offset
                            ) > 59:
                                # Adding the offest would overflow the day, so save
                                # the tide to add to the start of the next day.
                                fill_tide = ref_day.tide_4
                                overflow = True
                            else:
                                # Otherwise just save the high tide
                                new_day.tide_4 = self.generate_tide(ref_day.tide_4)
                        else:
                            # Do the same as the overflow scenario above, but for low tides
                            if (ref_day.tide_4.time.minute + self.low_time_offset) > 59:
                                fill_tide = ref_day.tide_4
                                overflow = True
                            else:
                                new_day.tide_4 = self.generate_tide(ref_day.tide_4)
                    else:  # Tide 4 is not even at 11 and will not trigger an overflow
                        new_day.tide_4 = self.generate_tide(ref_day.tide_4)
                except:
                    pass

            new_day.save()
        return True

    def generate_tide(self, ref_tide):
        if ref_tide.state == "H":
            d = datetime(
                1900, 1, 1, ref_tide.time.hour, ref_tide.time.minute
            ) + timedelta(minutes=self.high_time_offset)
            new_time = d.strftime("%H:%M")
            new_level = ref_tide.level * self.high_tide_offset
            new_state = "H"
        else:
            d = datetime(
                1908, 1, 1, ref_tide.time.hour, ref_tide.time.minute
            ) + timedelta(minutes=self.low_time_offset)
            new_time = d.strftime("%H:%M")
            new_level = ref_tide.level * self.low_tide_offest
            new_state = "L"
        return Tide.objects.create(time=(new_time), level=(new_level), state=new_state)


class Tide(models.Model):
    time = models.TimeField(_("time"))
    level = models.FloatField(_("level"), blank=True, null=True)
    state = models.CharField(max_length=1, choices=TIDE_STATES, blank=True)

    def __unicode__(self):
        return u"%s" % (self.time)

    def get_absolute_url(self):
        args = self.state + "-at-" + self.time
        return reverse("al-tide-detail", args=args)


class TideDay(models.Model):
    day = models.ForeignKey(Day)
    tide_chart = models.ForeignKey(TideChart)
    tide_1 = models.ForeignKey(Tide, related_name="tide_1", blank=True, null=True)
    tide_2 = models.ForeignKey(Tide, related_name="tide_2", blank=True, null=True)
    tide_3 = models.ForeignKey(Tide, related_name="tide_3", blank=True, null=True)
    tide_4 = models.ForeignKey(Tide, related_name="tide_4", blank=True, null=True)

    class Meta:
        verbose_name = _("tide day")
        verbose_name_plural = _("tide days")
        ordering = ("day",)
        get_latest_by = "day"

    def __unicode__(self):
        return u"%s" % (self.day)


class AstroChartUpload(models.Model):
    data_file = models.FileField(
        _("data file (.csv)"),
        upload_to="temp",
        help_text=_(
            "A comma separeted list of tide data for your particular location."
        ),
    )
    chart = models.ForeignKey(
        AstroChart, help_text=_("Select a chart to add the astronomical data to.")
    )
    dst = models.BooleanField(_("correct for DST"), default=False)

    class Meta:
        verbose_name = _("astronomical chart upload")
        verbose_name_plural = _("astronomical chart uploads")

    def save(self, *args, **kwargs):
        super(AstroChartUpload, self).save(*args, **kwargs)
        self.process_datafile()
        super(AstroChartUpload, self).delete()

    def process_datafile(self):
        try:
            data = csv.reader(open(self.data_file.path))
        except:
            raise Exception("There is a problem with your csv file.")
        for row in data:
            print row[0]
            day = Day.objects.create(date=row[0], chart=self.chart)
            if self.dst:
                if (
                    self.chart.dst_start
                    < datetime.strptime(day.date, "%Y-%m-%d").date()
                    < self.chart.dst_end
                ):
                    delta = timedelta(hours=1)
                else:
                    delta = timedelta(hours=0)
            else:
                delta = timedelta(hours=0)
            # B/c python datetime blows:
            # Take string in csv file, convert to time, convert to datetime, add delta,
            #   convert to timetuple and convert to string for insertion to db.
            day.sunrise = time.strftime(
                "%H:%M",
                (datetime(*time.strptime(row[1], "%H%M")[0:5]) + delta).timetuple(),
            )
            day.sunset = time.strftime(
                "%H:%M",
                (datetime(*time.strptime(row[2], "%H%M")[0:5]) + delta).timetuple(),
            )
            if row[3]:
                day.moonrise = time.strftime(
                    "%H:%M",
                    (datetime(*time.strptime(row[3], "%H%M")[0:5]) + delta).timetuple(),
                )
            if row[4]:
                day.moonset = time.strftime(
                    "%H:%M",
                    (datetime(*time.strptime(row[4], "%H%M")[0:5]) + delta).timetuple(),
                )
            day.civil_twilight_am = time.strftime(
                "%H:%M",
                (datetime(*time.strptime(row[5], "%H%M")[0:5]) + delta).timetuple(),
            )
            day.civil_twilight_pm = time.strftime(
                "%H:%M",
                (datetime(*time.strptime(row[6], "%H%M")[0:5]) + delta).timetuple(),
            )
            day.nautical_twilight_am = time.strftime(
                "%H:%M",
                (datetime(*time.strptime(row[7], "%H%M")[0:5]) + delta).timetuple(),
            )
            day.nautical_twilight_pm = time.strftime(
                "%H:%M",
                (datetime(*time.strptime(row[8], "%H%M")[0:5]) + delta).timetuple(),
            )
            if row[9]:
                day.moonphase = row[9]
                day.moonphase_time = time.strftime(
                    "%H:%M",
                    (
                        datetime(*time.strptime(row[10], "%H%M")[0:5]) + delta
                    ).timetuple(),
                )
            day.save()


class TideChartUpload(models.Model):
    data_file = models.FileField(
        _("data file (.csv)"),
        upload_to="temp",
        help_text=_(
            "A comma separeted list of tide data for your particular location."
        ),
    )
    chart = models.ForeignKey(
        TideChart, help_text=_("Select a chart to add the tide data to.")
    )

    class Meta:
        verbose_name = _("tide chartupload")
        verbose_name_plural = _("tide chart uploads")

    def save(self, *args, **kwargs):
        super(TideChartUpload, self).save(*args, **kwargs)
        self.process_datafile()
        super(TideChartUpload, self).delete()

    def process_datafile(self):
        try:
            tides = BeautifulStoneSoup(open(self.data_file.path))
        except:
            raise Exception(
                "There is a problem with your XML file or you do not have BeautifulSoup installed."
            )
        try:
            days = Day.objects.filter(date__year=chart.year)
            for day in days:
                """
                 XML for tide data from tidesandcurrents.noaa.gov:
                   <item>
                    <date>YYYY-MM-DD</date> 
                    <time>HH:MM A/P</time> 
                    <level_std>11.2 ft</level>
                    <level_mtr>2.1 m</leve>
                    <state>H/L</state>
                  </item>
                    
                 """

                days_tides = tides.findAll(text=day.date.replace("/", "-"))
                tideday = TideDay.objects.create(day=day, tide_chart=self.chart)
                tide_1 = days_tides[0].parent.parent
                tide_2 = days_tides[1].parent.parent
                tide_3 = days_tides[2].parent.parent
                try:
                    tide_4 = days_tides[3].parent.parent
                except:
                    pass
                tideday.tide_1 = Tide.objects.create(
                    time=tide_1.time.contents[0],
                    level=tide_1.predictions_in_ft.contents[0],
                    state=tide_1.highlow.conetnts[0],
                )
                tideday.tide_2 = Tide.objects.create(
                    time=tide_2.time.contents[0],
                    level=tide_2.predictions_in_ft.contents[0],
                    state=tide_2.highlow.conetnts[0],
                )
                tideday.tide_3 = Tide.objects.create(
                    time=tide_3.time.contents[0],
                    level=tide_3.predictions_in_ft.contents[0],
                    state=tide_3.highlow.conetnts[0],
                )
                if tide_4:
                    tideday.tide_4 = Tide.objects.create(
                        time=tide_4.time.contents[0],
                        level=tide_4.predictions_in_ft.contents[0],
                        state=tide_4.highlow.conetnts[0],
                    )
                tideday.save()
        except:
            raise Exception(
                "Tide data must be loaded after astronomical data for any given date."
            )
