# -*- coding: utf-8 -*-

###############################################################################
#   Copyright(c) 2008-2013 pyLoad Team
#   http://www.pyload.org
#
#   This file is part of pyLoad.
#   pyLoad is free software: you can redistribute it and/or modify
#   it under the terms of the GNU Affero General Public License as
#   published by the Free Software Foundation, either version 3 of the
#   License, or (at your option) any later version.
#
#   Subjected to the terms and conditions in LICENSE
#
#   @author: RaNaN
###############################################################################

import sys
from time import time, sleep
from random import randint

from pyload.utils import decode
from pyload.utils.fs import exists, makedirs, join, remove

# TODO
#       more attributes if needed
#       get rid of catpcha & container plugins ?! (move to crypter & internals)
#       adapt old plugins as needed

class Fail(Exception):
    """ raised when failed """

class Retry(Exception):
    """ raised when start again from beginning """

class Abort(Exception):
    """ raised when aborted """

class Base(object):
    """
    The Base plugin class with all shared methods and every possible attribute for plugin definition.
    """
    #: Version as string or number
    __version__ = "0.1"
    # Type of the plugin, will be inherited and should not be set!
    __type__ = ""
    #: Regexp pattern which will be matched for download/crypter plugins
    __pattern__ = r""
    #: Internal addon plugin which is always loaded
    __internal__ = False
    #: When True this addon can be enabled by every user
    __user_context__ = False
    #: Config definition: list of  (name, type, label, default_value) or
    #: (name, label, desc, Input(...))
    __config__ = tuple()
    #: Short description, one liner
    __description__ = ""
    #: More detailed text
    __explanation__ = """"""
    #: List of needed modules
    __dependencies__ = tuple()
    #: Used to assign a category for addon plugins
    __category__ = ""
    #: Tags to categorize the plugin, see documentation for further info
    __tags__ = tuple()
    #: Base64 encoded .png icon, should be 32x32, please don't use sizes above ~2KB, for bigger icons use url.
    __icon__ = ""
    #: Alternative, link to png icon
    __icon_url__ = ""
    #: Domain name of the service
    __domain__ = ""
    #: Url with general information/support/discussion
    __url__ = ""
    #: Url to term of content, user is accepting these when using the plugin
    __toc_url__ = ""
    #: Url to service (to buy premium) for accounts
    __ref_url__ = ""

    __author__ = tuple()
    __author_mail__ = tuple()


    def __init__(self, core, owner=None):
        self.__name__ = self.__class__.__name__

        #: Core instance
        self.core = core
        #: logging instance
        self.log = core.log
        #: core config
        self.config = core.config
        #: :class:`EventManager`
        self.evm = core.eventManager
        #: :class:`InteractionManager`
        self.im = core.interactionManager
        if owner is not None:
            #: :class:`Api`, user api when user is set
            self.api = self.core.api.withUserContext(owner)
            if not self.api:
                raise Exception("Plugin running with invalid user")

            #: :class:`User`, user related to this plugin
            self.owner = self.api.user
        else:
            self.api = self.core.api
            self.owner = None

        #: last interaction task
        self.task = None

        #: js engine, see `JsEngine`
        self.js = self.core.js

    def __getitem__(self, item):
        """ Retrieves meta data attribute """
        return getattr(self, "__%s__" % item)

    def logInfo(self, *args, **kwargs):
        """ Print args to log at specific level

        :param args: Arbitrary object which should be logged
        :param kwargs: sep=(how to separate arguments), default = " | "
        """
        self._log("info", *args, **kwargs)

    def logWarning(self, *args, **kwargs):
        self._log("warning", *args, **kwargs)

    def logError(self, *args, **kwargs):
        self._log("error", *args, **kwargs)

    def logDebug(self, *args, **kwargs):
        self._log("debug", *args, **kwargs)

    def _log(self, level, *args, **kwargs):
        if "sep" in kwargs:
            sep = "%s" % kwargs["sep"]
        else:
            sep = " | "

        strings = []
        for obj in args:
            if type(obj) == unicode:
                strings.append(obj)
            elif type(obj) == str:
                strings.append(decode(obj))
            else:
                strings.append(str(obj))

        getattr(self.log, level)("%s: %s" % (self.__name__, sep.join(strings)))

    def getName(self):
        """ Name of the plugin class """
        return self.__name__

    @property
    def pattern(self):
        """  Gives the compiled pattern of the plugin """
        return self.core.pluginManager.getPlugin(self.__type__, self.__name__).re

    def setConfig(self, option, value):
        """ Set config value for current plugin """
        self.core.config.set(self.__name__, option, value)

    def getConf(self, option):
        """ see `getConfig` """
        return self.getConfig(option)

    def getConfig(self, option):
        """ Returns config value for current plugin """
        return self.core.config.get(self.__name__, option)

    def setStorage(self, key, value):
        """ Saves a value persistently to the database """
        self.core.db.setStorage(self.__name__, key, value)

    def store(self, key, value):
        """ same as `setStorage` """
        self.core.db.setStorage(self.__name__, key, value)

    def getStorage(self, key=None, default=None):
        """ Retrieves saved value or dict of all saved entries if key is None """
        if key is not None:
            return self.core.db.getStorage(self.__name__, key) or default
        return self.core.db.getStorage(self.__name__, key)

    def retrieve(self, *args, **kwargs):
        """ same as `getStorage` """
        return self.getStorage(*args, **kwargs)

    def delStorage(self, key):
        """ Delete entry in db """
        self.core.db.delStorage(self.__name__, key)

    def shell(self):
        """ open ipython shell """
        if self.core.debug:
            from IPython import embed
            #noinspection PyUnresolvedReferences
            sys.stdout = sys._stdout
            embed()

    def abort(self):
        """ Check if plugin is in an abort state, is overwritten by subtypes"""
        return False

    def checkAbort(self):
        """  Will be overwritten to determine if control flow should be aborted """
        if self.abort(): raise Abort()

    def load(self, url, get={}, post={}, ref=True, cookies=True, just_header=False, decode=False):
        """Load content at url and returns it

        :param url: url as string
        :param get: GET as dict
        :param post: POST as dict, list or string
        :param ref: Set HTTP_REFERER header
        :param cookies: use saved cookies
        :param just_header: if True only the header will be retrieved and returned as dict
        :param decode: Whether to decode the output according to http header, should be True in most cases
        :return: Loaded content
        """
        if not hasattr(self, "req"): raise Exception("Plugin type does not have Request attribute.")
        self.checkAbort()

        res = self.req.load(url, get, post, ref, cookies, just_header, decode=decode)

        if self.core.debug:
            from inspect import currentframe

            frame = currentframe()
            if not exists(join("tmp", self.__name__)):
                makedirs(join("tmp", self.__name__))

            f = open(
                join("tmp", self.__name__, "%s_line%s.dump.html" % (frame.f_back.f_code.co_name, frame.f_back.f_lineno))
                , "wb")
            del frame # delete the frame or it wont be cleaned

            try:
                tmp = res.encode("utf8")
            except:
                tmp = res

            f.write(tmp)
            f.close()

        if just_header:
            #parse header
            header = {"code": self.req.code}
            for line in res.splitlines():
                line = line.strip()
                if not line or ":" not in line: continue

                key, none, value = line.partition(":")
                key = key.lower().strip()
                value = value.strip()

                if key in header:
                    if type(header[key]) == list:
                        header[key].append(value)
                    else:
                        header[key] = [header[key], value]
                else:
                    header[key] = value
            res = header

        return res

    def invalidTask(self):
        if self.task:
            self.task.invalid()

    def invalidCaptcha(self):
        self.logDebug("Deprecated method .invalidCaptcha, use .invalidTask")
        self.invalidTask()

    def correctTask(self):
        if self.task:
            self.task.correct()

    def correctCaptcha(self):
        self.logDebug("Deprecated method .correctCaptcha, use .correctTask")
        self.correctTask()

    def decryptCaptcha(self, url, get={}, post={}, cookies=False, forceUser=False, imgtype='jpg',
                       result_type='textual'):
        """ Loads a captcha and decrypts it with ocr, plugin, user input

        :param url: url of captcha image
        :param get: get part for request
        :param post: post part for request
        :param cookies: True if cookies should be enabled
        :param forceUser: if True, ocr is not used
        :param imgtype: Type of the Image
        :param result_type: 'textual' if text is written on the captcha\
        or 'positional' for captcha where the user have to click\
        on a specific region on the captcha

        :return: result of decrypting
        """

        img = self.load(url, get=get, post=post, cookies=cookies)

        id = ("%.2f" % time())[-6:].replace(".", "")
        temp_file = open(join("tmp", "tmpCaptcha_%s_%s.%s" % (self.__name__, id, imgtype)), "wb")
        temp_file.write(img)
        temp_file.close()

        name = "%sOCR" % self.__name__
        has_plugin = name in self.core.pluginManager.getPlugins("internal")

        if self.core.captcha:
            OCR = self.core.pluginManager.loadClass("internal", name)
        else:
            OCR = None

        if OCR and not forceUser:
            sleep(randint(3000, 5000) / 1000.0)
            self.checkAbort()

            ocr = OCR()
            result = ocr.get_captcha(temp_file.name)
        else:
            task = self.im.createCaptchaTask(img, imgtype, temp_file.name, self.__name__, result_type)
            self.task = task

            while task.isWaiting():
                if self.abort():
                    self.im.removeTask(task)
                    raise Abort()
                sleep(1)

            #TODO task handling
            self.im.removeTask(task)

            if task.error and has_plugin: #ignore default error message since the user could use OCR
                self.fail(_("Pil and tesseract not installed and no Client connected for captcha decrypting"))
            elif task.error:
                self.fail(task.error)
            elif not task.result:
                self.fail(_("No captcha result obtained in appropriate time."))

            result = task.result
            self.log.debug("Received captcha result: %s" % str(result))

        if not self.core.debug:
            try:
                remove(temp_file.name)
            except:
                pass

        return result

    def fail(self, reason):
        """ fail and give reason """
        raise Fail(reason)