##
 #  @filename   :   main.cpp
 #  @brief      :   7.5inch e-paper display demo
 #  @author     :   Yehui from Waveshare
 #
 #  Copyright (C) Waveshare     July 28 2017
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
 # of this software and associated documnetation files (the "Software"), to deal
 # in the Software without restriction, including without limitation the rights
 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 # copies of the Software, and to permit persons to  whom the Software is
 # furished to do so, subject to the following conditions:
 #
 # The above copyright notice and this permission notice shall be included in
 # all copies or substantial portions of the Software.
 #
 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 # FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 # LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 # THE SOFTWARE.
 ##

from datetime import datetime, timedelta
import io
import locale
import logging
import math
import os.path
import time

locale.setlocale(locale.LC_ALL, 'fr_FR.UTF-8')

NEXT_STOP_Y = 255

from PIL import Image, ImageDraw, ImageFont

class WidgetPlacement:

    def __init__(self, module, *args, size, position, **kwargs):
        self.module = module
        self.args = args
        self.size = size
        self.position = position
        self.kwargs = kwargs

def byteToStr(v):
    return chr((v & 0xF) + 97) + chr(((v >> 4) & 0xF) + 97)

def wordToStr(v):
    return byteToStr(v & 0xFF) + byteToStr((v >> 8) & 0xFF)

def main(only_on_coming_evt=False, ignore_module=[], force_coming_event=True, expand_alerts=False, send_to=None, **config_args):
    image = Image.new('1', (480, 800), 255)
    #image = Image.new('L', (480, 800), 'white')
    draw = ImageDraw.Draw(image)

    shape = []

    import modules
    config = modules.Config(**config_args)

    # alerts
    alerts = []

    # Weather

    # Current Weather
    from modules.weather import WeatherJumboCurrentModule
    shape.append(WidgetPlacement(WeatherJumboCurrentModule, size=(480,150), position=(0,0)))

    # rule
    shape.append(WidgetPlacement(modules.RuleModule, size=(450, 1), position=(30, 142)))

    # pluie
    from modules.weather import WeatherRainModule
    shape.append(WidgetPlacement(WeatherRainModule, size=(480-int(480/1.6), 94), position=(0, 155)))

    # moon phase
    from modules.weather import WeatherMoonPhaseModule
    shape.append(WidgetPlacement(WeatherMoonPhaseModule, size=(65, 65), position=(0, 113)))

    # ical
    from modules.ical import IcalModule
    shape.append(WidgetPlacement(IcalModule, config, size=(480-int(480/1.6), 255), position=(0, 250)))

    occuped_space = 0
    if "IcalModule" not in ignore_module or force_coming_event:
        ical = IcalModule(config)
        evt_coming = force_coming_event or ical.non_local_event_coming(config) or ical.local_event_ending(config)
        if evt_coming:
            from modules.ratp import RATPNextStopModule
            nstops, nalerts = RATPNextStopModule().draw_module(config, ["M14/hopital+bicetre/A", "RB/cite+universitaire"], int(480/1.6), 275)
            image.paste(nstops, (480-int(480/1.6), NEXT_STOP_Y))
            occuped_space = nstops.height
            alerts += nalerts
        elif only_on_coming_evt:
            # stop here in this case
            return

    if occuped_space < 250:
        # weekly weather
        from modules.weather import WeeklyWeatherModule
        shape.append(WidgetPlacement(WeeklyWeatherModule, size=(int(480/1.6), 265), position=(480-int(480/1.6), NEXT_STOP_Y + occuped_space + (5 if occuped_space else 0))))

    # RATP weather
    major_lines = ["M7", "M14", "RB", "TT3A"]
    weekday = datetime.now().weekday()
    if weekday == 0 or weekday == 2 or weekday == 4:
        major_lines.append("M6")
    from modules.ratp import RATPWeatherModule
    shape.append(WidgetPlacement(RATPWeatherModule, size=(int(480/1.6), 94), position=(480-int(480/1.6), 155)))
    alerts.append({"module": RATPWeatherModule, "args_module": [major_lines], "args_func": [config]})

    # Toolbar
    from modules.weather import WeatherToolbarModule
    shape.append(WidgetPlacement(WeatherToolbarModule, size=(480, 50), position=(0, 530)))

    from modules.sncf import SNCFWeatherModule
    alerts.append({"module": SNCFWeatherModule, "args_func": [config, "normandie"]})
    from modules.weather import WeatherAlerts
    alerts.append(WeatherAlerts)

    # sunrise/set
    from modules.weather import WeatherSunModule
    shape.append(WidgetPlacement(WeatherSunModule, size=(int(480/2), 20), position=(0, 780), start_align=5))

    # Draw shapes
    last_height = 0
    last_y = 0
    for s in shape:
        if s.module.__name__ in ignore_module:
            logging.info("Skip module " + s.module.__name__ + ", as requested by arguments")
            continue

        try:
            x,y = s.position
            if y < 0:
                y = last_height + last_y
            width, height = s.size
            if height < 0:
                height = 480 - last_height - last_y

            img = s.module(*s.args).draw_module(config, width, height, **s.kwargs)
            if img.mode == "RGBA":
                image.paste(img, (x,y), img)
            else:
                image.paste(img, (x,y))
        except BaseException as e:
            logging.exception(e)
            alerts.append({
                "title": "Impossible de dessiner " + s.module.__name__,
                "description": type(e).__name__ + ": " + (e.message if hasattr(e, 'message') else str(e)),
                "icon": "weather/wi-earthquake.png",
            })


    from modules import AlertsModule
    mod, more_alerts = AlertsModule(config, alerts, ignore_module).draw_module(config, 480, min(317, 640 - NEXT_STOP_Y - occuped_space))
    if NEXT_STOP_Y + occuped_space + mod.height > 580 or mod.height > 260:
        image.paste(mod, (0, 640-mod.height), mod)
    elif mod.height < 100:
        image.paste(mod, (0, 542-mod.height), mod)
    else:
        image.paste(mod, (0, 580-mod.height), mod)

    # températures
    if "WeatherTemperatureModule" not in ignore_module:
        from modules.weather import WeatherTemperatureModule
        if NEXT_STOP_Y + occuped_space + mod.height > 580 or mod.height > 260:
            image.paste(WeatherTemperatureModule().draw_module(config, 480, 140), (0, 640))
        else:
            image.paste(WeatherTemperatureModule().draw_module(config, 480, 200), (0, 580))

    if expand_alerts and more_alerts > 0:
        mod, more_alerts = AlertsModule(config, alerts, ignore_module).draw_module(config, 480, 785 - NEXT_STOP_Y - occuped_space)
        image.paste(mod, (0, NEXT_STOP_Y + occuped_space), mod)


    fnt = ImageFont.truetype(config.fnt_R_path, 11)
    draw.text(
        (475, 798),
        "Dernière génération le " + datetime.now().strftime("%c"),
        fill="black", anchor="rb", font=fnt
    )

    logging.info("image generated")
    if send_to is not None:
        import urllib.request

        def do_req(url):
            with urllib.request.urlopen(url, b""):
                pass

        do_req(send_to + "EPDw_")
        pixels = image.load()
        width, height = image.size
        buf = [0xFF] * (int(width/8) * height)
        for y in range(height):
            for x in range(width):
                if pixels[x, y] == 0:
                    buf[int((y + (width - x - 1) * height) / 8)] &= ~(0x80 >> (y % 8))
        for i in range(math.ceil(len(buf) / 500)):
            data = ''.join([byteToStr(b) for b in buf[500*i:500*(i+1)]])
            size = wordToStr(len(data))
            do_req(send_to + data + size + "LOAD_")
        do_req(send_to + "SHOW_")

    else:
        try:
            import epd7in5

            epd = epd7in5.EPD()
            epd.init()
            logging.info("initialized")

            epd.display(epd.getbuffer(image))

            logging.info("time to sleep")
            epd.sleep()

        except:
            image.save("screen.png")

if __name__ == '__main__':
    import argparse

    parser = argparse.ArgumentParser(description='E-ink status generator.')

    parser.add_argument('--ignore-module', '-I', nargs="*", default=[],
                    help='Ignore the given modules')
    parser.add_argument('--expand-alerts', '-A', action='store_const', const=True,
                    help='If needed, expand alerts on graphic')
    parser.add_argument('--force-coming-evt', '-E', action='store_const', const=True,
                    help='Consider an event coming, whatever calendar says')
    parser.add_argument('--only-on-coming-evt', '-O', action='store_const', const=True,
                    help='Refresh screen only if there is upcoming event')
    parser.add_argument('--cache-timeout', '-C', type=int, default=90,
                    help='How many minutes to keep the infos in cache')
    parser.add_argument('--max-cache-timeout', type=int, default=120,
                    help='Maximum time to serve the infos in cache in case of issue')
    parser.add_argument('--send-to', help='Send the request to a HTTP server')

    args = parser.parse_args()

    main(
        args.only_on_coming_evt or (datetime.now().minute % 10 == 0 and not datetime.now().minute % 30 == 0),
        args.ignore_module,
        args.force_coming_evt,
        args.expand_alerts,
        cache_timeout=args.cache_timeout,
        max_cache_timeout=args.max_cache_timeout,
        send_to=args.send_to,
    )
