import os
import requests
import sys
import time
from fractions import Fraction as frac
from iterfzf import iterfzf
import ueberzug.lib.v0 as ueberzug
from PIL import ImageFile

HOME = os.path.expanduser("~")
CONFIG_PATH = os.path.expanduser("~/.config/walley/config.py")


class Walley:
    """Walley.
    Main program class
    """

    def __init__(self,
                 subreddit=None,
                 limit=None,
                 resolution="FHD",
                 directory="~"):
        """__init__.
        Class init.
        Args are overwritten by config. Intended for testing/debugging
        Args:
            subreddit:  str(subreddit)
            limit:      int(max downloads)
            resolution: str("FHD")
            directory:  str("~/Pictures/")
        """
        self.res_collection = {
            'FHD': (1920, 1080),
            '2K': (2560, 1440),
            'WQHD': (3440, 1440),
            '4K': (3440, 2160),
        }
        self.ext = ('.jpg', '.png', '.jepg')
        self.sub = None  # sub as string
        self.dir = os.path.expanduser(directory)
        self.lim = None  # int
        self.res = self.res_collection[f'{resolution}']  # tuple type
        self.url = None
        self.nsfw = False
        self.candidates = []  # potential dls
        self.imgout = []  # validatet candidates ready for dl
        self.data = None
        self.resolution = None
        self.check_res = None
        self.ratio = None
        self.check_ratio = None

    def eval_entries(self):
        """eval_entries.
        Makes main request and retrieves json,
        checks HTTP code,
        subreddit validity and max_limit compliance
        """
        resp = requests.get(self.url, headers={'User-agent': 'wppGetter'})
        if resp.status_code != 200:
            return False
        if resp.json()['data']['dist'] == 0:
            return False
        if self.lim > 200:
            return False  # maybe change the max_limit in the future
        self.data = resp.json()
        return True

    def get_candidates(self):
        """get_candidates.
        Appends all potential downloads to self.candidates array.
        Can filter nsfw content if set in config
        Must be run after self.eval_entries()!
        """
        for entry in self.data['data']['children']:
            # filter out nsfw if filter is on
            if not self.nsfw:
                if entry['data']['over_18']:
                    continue
            self.candidates.append(entry['data']['url'])
        return self.candidates

    def check_criteria(self, data):
        """check_resolution.
        Checks if image resolution meets the specified minimum
        Args:
            data: image data
        """
        # is checking enabled ?
        if self.check_ratio or self.check_res:
            parser = ImageFile.Parser()
            parser.feed(data)
            (x, y) = (parser.image.size[0], parser.image.size[1])
            # check resolution and aspect ratio

            # both
            if self.check_ratio and self.check_res:
                if (x, y) >= self.res and (x / y == self.ratio):
                    return True
            # only ratio
            elif self.check_ratio and not self.check_res:
                if (x / y == self.ratio):
                    return True
            # only resolution
            elif not self.check_ratio and self.check_res:
                if (x, y) >= self.res:
                    return True

        # no checking, always download
        else:
            return True

    def check_host(self, entry):
        """check_host.
        Checks if image is hosted on either reddit or imgur
        Args:
            entry: url
        """
        if entry.lower().startswith('http://i.redd.it/') or entry.lower(
        ).startswith('http://i.imgur.com/') or entry.lower().startswith(
                'https://i.redd.it/') or entry.lower().startswith(
                    'https://i.imgur.com/'):
            return True

    def eval_candidate(self, entry):
        """eval_candidate.
        Main filtering function. Checks sequentially for:
            -file extension
            -hosting service
            -resolution
        Args:
            entry: image URL
        """
        # raw image data
        data = requests.get(entry,
                            headers={
                                'User-agent': 'wppGetter'
                            },
                            stream=True).content
        # check file extension of image
        if not entry.lower().endswith(self.ext):
            return False
        try:
            # check image host and criteria specified in config
            if self.check_host(entry) and self.check_criteria(data):
                return True
            else:
                return False
        except Exception:
            print("Error while validating resolution"
                  " and/or aspect ratio for ", entry,
                  "continuing...")

    def download(self, URL):
        """download.
        Downloads and saves an image file
        Args:
            URL: image file URL
        """
        try:
            with open(f"{self.dir}/{os.path.basename(URL)}", "wb") as f:
                f.write(
                    requests.get(URL,
                                 headers={
                                     'User-agent': 'wppGetter'
                                 },
                                 stream=True).content)
            return True
        except Exception:
            raise Exception(
                "An error while downloading"
                " and/or saving the picture occurred"
            )

    def redl_protection(self, URL):
        """redl_protection.
        Finds potential duplicate downloads
        by comparing remote with local files
        Args:
            URL: image URL
        """
        img = os.path.join(self.dir, os.path.basename(URL))
        return os.path.isfile(img)

    def dir_checkup(self):
        """dir_checkup.
        Checks if download directory exitsts
        """
        return os.path.isdir(f"{self.dir}")

    def startup_procedure(self):
        """startup_procedure.
        checks for user config (and creates it if not present)
        and loads config into class vars
        """

        # check for config
        if not os.path.exists(f"{HOME}/.config/walley/config.py"):
            Logger(
                "No config found,"
                f" creating template config in {HOME}/.config/walley",
                'RED').log()

            # check for config dir
            if not os.path.exists(f"{HOME}/.config/walley"):
                # create config dir
                os.makedirs(f"{HOME}/.config/walley")

            # create config file
            open(f"{HOME}/.config/walley/config.py", "w").close()
            # write sample config
            with open(f"{HOME}/.config/walley/config.py",
                      'w', encoding='utf-8') as f:
                f.write("SUBREDDIT = \"wallpapers\"\n"
                        "LIMIT = 10\n"
                        "CHECK_RESOLUTION = True\n"
                        "RESOLUTION = \"FHD\"\n"
                        "CHECK_RATIO = True\n"
                        "RATIO = \"16/9\"\n"
                        "DIRECTORY = \"~/Pictures/test\"\n"
                        "ALLOW_NSFW = False\n")

        # load config into class vars
        try:
            # add config to python path and import
            sys.path.append(os.path.dirname(os.path.expanduser(CONFIG_PATH)))
            import config
            # import config vars
            self.sub = config.SUBREDDIT
            self.dir = os.path.expanduser(config.DIRECTORY)
            self.lim = config.LIMIT
            self.resolution = config.RESOLUTION
            self.check_res = config.CHECK_RESOLUTION
            self.check_ratio = config.CHECK_RATIO
            self.res = self.res_collection[
                f'{self.resolution}']  # tuple type
            # frac needed to preserve fraction in terminal output
            self.ratio = frac(config.RATIO)
            self.nsfw = config.ALLOW_NSFW
            self.url = (f"https://www.reddit.com/r/{self.sub}"
                        f"/hot/.json?t=all&limit={self.lim}")

            # Logging
            Logger("Config found\nProceding with following settings:",
                   "GREEN").log()

            # Log config settings
            if self.check_ratio or self.check_res:
                # check resolution and aspect ratio

                # both
                if self.check_ratio and self.check_res:
                    Logger(
                        f"\nSubreddit: r/{self.sub}\nDirectory:"
                        f" {self.dir}\nResolution:"
                        f" {self.resolution}\nAspect Ratio:"
                        f" {self.ratio}\nLimit: {self.lim}\n"
                    ).log()
                # only ratio
                elif self.check_ratio and not self.check_res:
                    Logger(
                        f"\nSubreddit: r/{self.sub}\nDirectory:"
                        f" {self.dir}\nResolution:"
                        f" None\nAspect Ratio:"
                        f" {self.ratio}\nLimit: {self.lim}\n"
                    ).log()
                # only resolution
                elif not self.check_ratio and self.check_res:
                    Logger(
                        f"\nSubreddit: r/{self.sub}\nDirectory:"
                        f" {self.dir}\nResolution:"
                        f" {self.resolution}\nAspect Ratio:"
                        f" None\nLimit: {self.lim}\n"
                    ).log()
            # no checking, always download
            else:
                Logger(
                    f"\nSubreddit: r/{self.sub}\nDirectory:"
                    f" {self.dir}\nResolution:"
                    f" None\nAspect Ratio:"
                    f" None\nLimit: {self.lim}\n"
                ).log()
            # check for res and ratio checking
            if self.nsfw:
                Logger("Info: NSFW CONTET FILTERING IS OFF", "RED").log()
            else:
                Logger("Info: NSFW content filtering is on", "GREEN").log()
            return True

        except Exception:
            raise Exception("Error while reading config, check entries.")

    def get_preview_atrr(self):
        prev_images = []
        for entry in self.data['data']['children']:
            # filter out nsfw if filter is on
            if not self.nsfw:
                if entry['data']['over_18']:
                    continue
                name = entry['data']['title']
                thumb = entry['data']['thumbnail']
                url = entry['data']['url']
                prev_images.append(Image(name, thumb, url))
        return prev_images


class Image(object):

    def __init__(self, name, thumb_url, url):
        self.name = name
        self.thumb = thumb_url
        self.url = url

    def download_preview(self):
        """download.
        Downloads and saves an image file
        """
        try:
            with open(f"/tmp/{self.name}", "wb") as f:
                f.write(
                    requests.get(self.thumb,
                                 headers={
                                     'User-agent': 'wppGetter'
                                 },
                                 stream=True).content)
            return True
        except Exception:
            raise Exception(
                "An error while downloading"
                " and/or saving the picture occurred"
            )

    def download_to_dir(self):
        Walley().download(self.url)

    @ueberzug.Canvas()
    def draw_self(self, canvas):
        self.download_preview()
        demo = canvas.create_placement('demo', x=10, y=0)
        demo.path = f"/tmp/{self.name}"
        print(demo.path)
        demo.visibility = ueberzug.Visibility.VISIBLE
        time.sleep(2)


class Logger:
    """Logger.
    Contains logging functions with color highlighting for linux terminals
    """

    def __init__(self, message, color='RESET'):
        """__init__.
        Class init
        Args:
            message: message to print
            color: color of print, defaults to white if unset
        """
        self.color_collection = {
            'RED': '\033[1;31m',
            'GREEN': '\033[1;32m',
            'ORANGE': '\033[1;33m',
            'RESET': '\033[0m',
        }
        self.color = self.color_collection[f"{color}"]
        self.message = message

    def log(self, line=False):
        """log.
        Main logging function
        Args:
            line: prints a separator line if set to True or 1
        """
        if line:
            print("--------------------------------")
            print(self.color + self.message + self.color_collection['RESET'])
        else:
            print(self.color + self.message + self.color_collection['RESET'])


if __name__ == '__main__':
    argv = sys.argv
    argc = len(argv)
    print(argc, argv)
    Wall = Walley()

    # load class vars from config
    Wall.startup_procedure()

    Logger("Verifying download directory", "ORANGE").log()

    # check for download dir
    if not Wall.dir_checkup():
        Logger(
            "Invalid directory specified."
            " Check if directory specified in config exists",
            "RED").log(1)
        sys.exit()

    Logger("Checking connection and subreddit", "ORANGE").log()

    # get json, check http code and subreddit
    if Wall.eval_entries():
        Logger("Check successfull", "GREEN").log()
        Logger("Getting images links", "ORANGE").log(1)

    if argc == 1:
        # make download array
        candidates = Wall.get_candidates()

        Logger("Evaluating images", "ORANGE").log()

        # filter through download array
        for candidate in candidates:
            if not Wall.redl_protection(candidate):
                if Wall.eval_candidate(candidate):
                    Logger(f"Downloading Image {candidate}", "GREEN").log()
                    # download filtered file
                    Wall.download(str(candidate))
                else:
                    Logger("Image did not match criteria. Skipping ...",
                           "ORANGE").log()
            else:
                Logger("Image was already downloaded before. Skipping ...",
                       "ORANGE").log()

        Logger("Completed all downloads. Exiting...", "GREEN").log(1)
        sys.exit()
    else:
        if argv[1] == '-p':
            print("preview mode")
            names = []
            images = Wall.get_preview_atrr()
            for image in images:
                names.append(image.name)

            # images[2].draw_self()

            json_str = ("echo \"{\\\"action\\\": \\\"add\\\", \\\"identifier\\\": \\\"peppo\\\", "
                        "\\\"x\\\": 0, \\\"y\\\": 0, "
                        "\\\"path\\\": \\\"/home/lukas/Pictures/gnu.png\\\"}\"")
            
            # command_str = f"{json_str} | ueberzug layer --parser json "
            command_str = ("ueberzug layer --parser bash 0< <(\n"
                           "declare -Ap add_command=([action]=\"add\" [identifier]=\"example0\" [x]=\"0\" [y]=\"0\" [path]=\"/home/lukas/Pictures/lpic.png\")\n"
                           "sleep 2)")
            print(json_str)
            print(command_str)
            """
            iterfzf(names, preview="echo \"{}\" > /tmp/walleyfifo")
            """
            iterfzf(names, preview=command_str)

        """
        with ueberzug.Canvas() as c:
            paths = ['/home/lukas/Pictures/test/7kzka0pu2el61.jpg', '/home/lukas/Pictures/test/3zgjdp2unll61.jpg']
            demo = c.create_placement('demo', x=0, y=0, scaler=ueberzug.ScalerOption.COVER.value)
            demo.path = paths[0]
            demo.visibility = ueberzug.Visibility.VISIBLE

            for i in range(30):
                with c.lazy_drawing:
                    demo.x = i * 3
                    demo.y = i * 3
                    demo.path = paths[i % 2]
                time.sleep(1)

            time.sleep(2)
        """
