# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# Copyright (c) 2012, Peter Levi <peterlevi@peterlevi.com>
# This program is free software: you can redistribute it and/or modify it 
# under the terms of the GNU General Public License version 3, as published 
# by the Free Software Foundation.
# 
# This program is distributed in the hope that it will be useful, but 
# WITHOUT ANY WARRANTY; without even the implied warranties of 
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR 
# PURPOSE.  See the GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along 
# with this program.  If not, see <http://www.gnu.org/licenses/>.
### END LICENSE

import os
import string
import re
import logging
from variety.Smart import Smart

from variety.Util import Util

logger = logging.getLogger('variety')

class Downloader(object):
    def __init__(self, parent, source_type, name, location, is_refresher=False):
        self.parent = parent
        self.source_type = source_type
        self.name = name
        self.location = location
        self.is_refresher = is_refresher

    def update_download_folder(self):
        filename = self.convert_to_filename(self.location)
        l = len(self.parent.real_download_folder)
        if len(filename) + l > 160:
            filename = filename[:(150 - l)] + Util.md5(filename)[:10]
        self.target_folder = os.path.join(self.parent.real_download_folder, filename)

    def convert_to_filename(self, url):
        url = re.sub(r"http://", "", url)
        url = re.sub(r"https://", "", url)
        valid_chars = "_%s%s" % (string.ascii_letters, string.digits)
        return ''.join(c if c in valid_chars else '_' for c in url)

    def get_local_filename(self, url):
        return os.path.join(self.target_folder, Util.get_local_name(url))

    def is_in_downloaded(self, url):
        return os.path.exists(self.get_local_filename(url))

    def is_in_favorites(self, url):
        return self.parent and os.path.exists(os.path.join(self.parent.options.favorites_folder, Util.get_local_name(url)))

    def save_locally(self, origin_url, image_url,
                     source_type=None, source_location=None, source_name=None,
                     force_download=False, extra_metadata={}, local_filename=None):
        if not source_type:
            source_type = self.source_type
        if not source_name:
            source_name = self.name
        if not source_location:
            source_location = self.location

        if not force_download and self.parent and origin_url in self.parent.banned:
            logger.info(lambda: "URL " + origin_url + " is banned, skip downloading")
            return None

        try:
            os.makedirs(self.target_folder)
        except Exception:
            pass

        if origin_url.startswith('//'):
            origin_url = 'https:' + origin_url

        if image_url.startswith('//'):
            image_url = origin_url.split('//')[0] + image_url

        if not local_filename:
            local_filename = self.get_local_filename(image_url)
        logger.info(lambda: "Origin URL: " + origin_url)
        logger.info(lambda: "Image URL: " + image_url)
        logger.info(lambda: "Local name: " + local_filename)

        if not force_download and os.path.exists(local_filename):
            logger.info(lambda: "File already exists, skip downloading")
            return None

        if self.parent and self.parent.options.safe_mode:
            sfw_rating = Smart.get_sfw_rating(origin_url)
            if sfw_rating is not None and sfw_rating < 100:
                logger.info(lambda: "Skipping non-safe download %s. Is the source %s:%s "
                                    "suitable for Safe mode?" % (origin_url, source_type, self.location))
                return None

        if self.parent and self.parent.options.safe_mode and 'keywords' in extra_metadata:
            blacklisted = set(k.lower() for k in extra_metadata['keywords']) & Smart.get_safe_mode_keyword_blacklist()
            if len(blacklisted) > 0:
                logger.info(lambda: "Skipping non-safe download %s due to blacklisted keywords (%s). "
                                    "Is the source %s:%s suitable for Safe mode?" %
                                    (origin_url, str(blacklisted), source_type, self.location))
                return None

        try:
            r = Util.request(image_url, stream=True)
            with open(local_filename, 'wb') as f:
                Util.request_write_to(r, f)
        except Exception, e:
            logger.info(lambda: "Download failed from image URL: %s (source location: %s) " % (image_url, self.location))
            raise e

        if not Util.is_image(local_filename, check_contents=True):
            logger.info(lambda: "Downloaded data was not an image, image URL might be outdated")
            os.unlink(local_filename)
            return None

        metadata = {
            "sourceType": source_type,
            "sourceName": source_name,
            "sourceLocation": source_location,
            "sourceURL": origin_url,
            "imageURL": image_url
        }
        metadata.update(extra_metadata)
        Util.write_metadata(local_filename, metadata)

        logger.info(lambda: "Download complete")
        return local_filename

    def parse_server_options(self, key, default_min_download_interval, default_min_fill_queue_interval):
        min_download_interval = default_min_download_interval
        min_fill_queue_interval = default_min_fill_queue_interval

        try:
            logger.info(lambda: "%s: parsing serverside options" % self.name)
            options = self.parent.server_options[key]
            logger.info(lambda: "%s serverside options: %s" % (self.name, str(options)))
        except Exception:
            logger.exception(lambda: "Could not parse %s serverside options, using defaults %d, %d" % (
                self.name, min_download_interval, min_fill_queue_interval))
            return min_download_interval, min_fill_queue_interval

        try:
            min_download_interval = int(options["min_download_interval"])
        except Exception:
            logger.exception(lambda: "Bad or missing min_download_interval")

        try:
            min_fill_queue_interval = int(options["min_fill_queue_interval"])
        except Exception:
            logger.exception(lambda: "Bad or missing min_fill_queue_interval")

        return min_download_interval, min_fill_queue_interval

