# -*- coding: utf-8 -*-
"""
Contains generic utillity functions used in various different parts of
Drogulus.
"""
from .constants import K

def distance(key_one, key_two):
    """
    Calculate the XOR result between two string representations of hex values.
    Returned as an int.
    """
    val_key_one = int(key_one, 16)
    val_key_two = int(key_two, 16)
    return val_key_one ^ val_key_two

def sort_peer_nodes(peer_nodes, target_key):
    """
    Given a list of peer nodes, efficiently sorts it so that the peers closest
    to the target key are at the head. If the list is longer than K then only
    the K closest contacts will be returned.
    """
    def node_key(node, target_key=target_key):
        """
        Returns the node's distance to the target key.
        """
        return distance(node.network_id, target_key)

    peer_nodes.sort(key=node_key)
    return peer_nodes[:K]


def imc (peso,altura):
    valor = peso / altura **2
    if valor <18:
        return "Delgadez"
    elif valor <25:
        return "Normal"
    elif valor <29:
        return "Sobrepeso"
    else:
        return "Obesidad"

valor_imc = imc (58,1.55)
print (valor_imc)


#!/usr/bin/env python
#
# Public Domain 2014-2018 MongoDB, Inc.
# Public Domain 2008-2014 WiredTiger, Inc.
#
# This is free and unencumbered software released into the public domain.
#
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
#
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS 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.
#
# WiredTigerTestCase
#   parent class for all test cases
#

# If unittest2 is available, use it in preference to (the old) unittest
try:
    import unittest2 as unittest
except ImportError:
    import unittest

from contextlib import contextmanager
import glob, os, re, shutil, sys, time, traceback
import wiredtiger, wtscenario

def shortenWithEllipsis(s, maxlen):
    if len(s) > maxlen:
        s = s[0:maxlen-3] + '...'
    return s

class CapturedFd(object):
    """
    CapturedFd encapsulates a file descriptor (e.g. 1 or 2) that is diverted
    to a file.  We use this to capture and check the C stdout/stderr.
    Meanwhile we reset Python's sys.stdout, sys.stderr, using duped copies
    of the original 1, 2 fds.  The end result is that Python's sys.stdout
    sys.stderr behave normally (e.g. go to the tty), while the C stdout/stderr
    ends up in a file that we can verify.
    """
    def __init__(self, filename, desc):
        self.filename = filename
        self.desc = desc
        self.expectpos = 0
        self.file = None

    def readFileFrom(self, filename, pos, maxchars):
        """
        Read a file starting at a given position,
        returning the beginning of its contents
        """
        with open(filename, 'r') as f:
            f.seek(pos)
            return shortenWithEllipsis(f.read(maxchars+1), maxchars)

    def capture(self):
        """
        Start capturing the file descriptor.
        Note that the original targetFd is closed, we expect
        that the caller has duped it and passed the dup to us
        in the constructor.
        """
        self.file = open(self.filename, 'w')
        return self.file

    def release(self):
        """
        Stop capturing.
        """
        self.file.close()
        self.file = None

    def check(self, testcase):
        """
        Check to see that there is no unexpected output in the captured output
        file.  If there is, raise it as a test failure.
        This is generally called after 'release' is called.
        """
        if self.file != None:
            self.file.flush()
        filesize = os.path.getsize(self.filename)
        if filesize > self.expectpos:
            contents = self.readFileFrom(self.filename, self.expectpos, 10000)
            WiredTigerTestCase.prout('ERROR: ' + self.filename +
                                     ' unexpected ' + self.desc +
                                     ', contains:\n"' + contents + '"')
            testcase.fail('unexpected ' + self.desc + ', contains: "' +
                      contents + '"')
        self.expectpos = filesize

    def checkAdditional(self, testcase, expect):
        """
        Check to see that an additional string has been added to the
        output file.  If it has not, raise it as a test failure.
        In any case, reset the expected pos to account for the new output.
        """
        if self.file != None:
            self.file.flush()
        gotstr = self.readFileFrom(self.filename, self.expectpos, 1000)
        testcase.assertEqual(gotstr, expect, 'in ' + self.desc +
                             ', expected "' + expect + '", but got "' +
                             gotstr + '"')
        self.expectpos = os.path.getsize(self.filename)

    def checkAdditionalPattern(self, testcase, pat):
        """
        Check to see that an additional string has been added to the
        output file.  If it has not, raise it as a test failure.
        In any case, reset the expected pos to account for the new output.
        """
        if self.file != None:
            self.file.flush()
        gotstr = self.readFileFrom(self.filename, self.expectpos, 1000)
        if re.search(pat, gotstr) == None:
            testcase.fail('in ' + self.desc +
                          ', expected pattern "' + pat + '", but got "' +
                          gotstr + '"')
        self.expectpos = os.path.getsize(self.filename)

class TestSuiteConnection(object):
    def __init__(self, conn, connlist):
        connlist.append(conn)
        self._conn = conn
        self._connlist = connlist

    def close(self, config=''):
        self._connlist.remove(self._conn)
        return self._conn.close(config)

    # Proxy everything except what we explicitly define to the
    # wrapped connection
    def __getattr__(self, attr):
        if attr in self.__dict__:
            return getattr(self, attr)
        else:
            return getattr(self._conn, attr)

# Just like a list of strings, but with a convenience function
class ExtensionList(list):
    skipIfMissing = False
    def extension(self, dirname, name, extarg=None):
        if name != None and name != 'none':
            ext = '' if extarg == None else '=' + extarg
            self.append(dirname + '/' + name + ext)

class WiredTigerTestCase(unittest.TestCase):
    _globalSetup = False
    _printOnceSeen = {}

    # conn_config can be overridden to add to basic connection configuration.
    # Can be a string or a callable function or lambda expression.
    conn_config = ''

    # session_config can be overridden to add to basic session configuration.
    # Can be a string or a callable function or lambda expression.
    session_config = ''

    # conn_extensions can be overridden to add a list of extensions to load.
    # Each entry is a string (directory and extension name) and optional config.
    # Example:
    #    conn_extensions = ('extractors/csv_extractor',
    #                       'test/fail_fs={allow_writes=100}')
    conn_extensions = ()

    @staticmethod
    def globalSetup(preserveFiles = False, useTimestamp = False,
                    gdbSub = False, lldbSub = False, verbose = 1, builddir = None, dirarg = None,
                    longtest = False):
        WiredTigerTestCase._preserveFiles = preserveFiles
        d = 'WT_TEST' if dirarg == None else dirarg
        if useTimestamp:
            d += '.' + time.strftime('%Y%m%d-%H%M%S', time.localtime())
        shutil.rmtree(d, ignore_errors=True)
        os.makedirs(d)
        wtscenario.set_long_run(longtest)
        WiredTigerTestCase._parentTestdir = d
        WiredTigerTestCase._builddir = builddir
        WiredTigerTestCase._origcwd = os.getcwd()
        WiredTigerTestCase._resultfile = open(os.path.join(d, 'results.txt'), "w", 0)  # unbuffered
        WiredTigerTestCase._gdbSubprocess = gdbSub
        WiredTigerTestCase._lldbSubprocess = lldbSub
        WiredTigerTestCase._longtest = longtest
        WiredTigerTestCase._verbose = verbose
        WiredTigerTestCase._dupout = os.dup(sys.stdout.fileno())
        WiredTigerTestCase._stdout = sys.stdout
        WiredTigerTestCase._stderr = sys.stderr
        WiredTigerTestCase._concurrent = False
        WiredTigerTestCase._globalSetup = True
        WiredTigerTestCase._ttyDescriptor = None

    def fdSetUp(self):
        self.captureout = CapturedFd('stdout.txt', 'standard output')
        self.captureerr = CapturedFd('stderr.txt', 'error output')
        sys.stdout = self.captureout.capture()
        sys.stderr = self.captureerr.capture()

    def fdTearDown(self):
        # restore stderr/stdout
        self.captureout.release()
        self.captureerr.release()
        sys.stdout = WiredTigerTestCase._stdout
        sys.stderr = WiredTigerTestCase._stderr

    def __init__(self, *args, **kwargs):
        if hasattr(self, 'scenarios'):
            assert(len(self.scenarios) == len(dict(self.scenarios)))
        unittest.TestCase.__init__(self, *args, **kwargs)
        if not self._globalSetup:
            WiredTigerTestCase.globalSetup()

    def __str__(self):
        # when running with scenarios, if the number_scenarios() method
        # is used, then each scenario is given a number, which can
        # help distinguish tests.
        scen = ''
        if hasattr(self, 'scenario_number') and hasattr(self, 'scenario_name'):
            scen = ' -s ' + str(self.scenario_number) + \
                   ' (' + self.scenario_name + ')'
        return self.simpleName() + scen

    def shortDesc(self):
        ret_str = ''
        if hasattr(self, 'scenario_number'):
            ret_str = ' -s ' + str(self.scenario_number)
        return self.simpleName() + ret_str

    def simpleName(self):
        return "%s.%s.%s" %  (self.__module__,
                              self.className(), self._testMethodName)

    # Return the wiredtiger_open extension argument for
    # any needed shared library.
    def extensionsConfig(self):
        exts = self.conn_extensions
        if hasattr(exts, '__call__'):
            exts = ExtensionList()
            self.conn_extensions(exts)
        result = ''
        extfiles = {}
        skipIfMissing = False
        if hasattr(exts, 'skip_if_missing'):
            skipIfMissing = exts.skip_if_missing
        for ext in exts:
            extconf = ''
            if '=' in ext:
                splits = ext.split('=', 1)
                ext = splits[0]
                extconf = '=' + splits[1]
            splits = ext.split('/')
            if len(splits) != 2:
                raise Exception(self.shortid() +
                    ": " + ext +
                    ": extension is not named <dir>/<name>")
            libname = splits[1]
            dirname = splits[0]
            pat = os.path.join(WiredTigerTestCase._builddir, 'ext',
                dirname, libname, '.libs', 'libwiredtiger_*.so')
            filenames = glob.glob(pat)
            if len(filenames) == 0:
                if skipIfMissing:
                    self.skipTest('extension "' + ext + '" not built')
                    continue
                else:
                    raise Exception(self.shortid() +
                        ": " + ext +
                        ": no extensions library found matching: " + pat)
            elif len(filenames) > 1:
                raise Exception(self.shortid() +
                    ": " + ext +
                    ": multiple extensions libraries found matching: " + pat)
            complete = '"' + filenames[0] + '"' + extconf
            if ext in extfiles:
                if extfiles[ext] != complete:
                    raise Exception(self.shortid() +
                        ": non-matching extension arguments in " +
                        str(exts))
            else:
                extfiles[ext] = complete
        if len(extfiles) != 0:
            result = ',extensions=[' + ','.join(extfiles.values()) + ']'
        return result

    # Can be overridden, but first consider setting self.conn_config
    # or self.conn_extensions
    def setUpConnectionOpen(self, home):
        self.home = home
        config = self.conn_config
        if hasattr(config, '__call__'):
            config = self.conn_config()
        config += self.extensionsConfig()
        # In case the open starts additional threads, flush first to
        # avoid confusion.
        sys.stdout.flush()
        conn_param = 'create,error_prefix="%s",%s' % (self.shortid(), config)
        try:
            conn = self.wiredtiger_open(home, conn_param)
        except wiredtiger.WiredTigerError as e:
            print "Failed wiredtiger_open: dir '%s', config '%s'" % \
                (home, conn_param)
            raise e
        self.pr(`conn`)
        return conn

    # Replacement for wiredtiger.wiredtiger_open that returns
    # a proxied connection that knows to close it itself at the
    # end of the run, unless it was already closed.
    def wiredtiger_open(self, home=None, config=''):
        conn = wiredtiger.wiredtiger_open(home, config)
        return TestSuiteConnection(conn, self._connections)

    # Can be overridden, but first consider setting self.session_config
    def setUpSessionOpen(self, conn):
        config = self.session_config
        if hasattr(config, '__call__'):
            config = self.session_config()
        return conn.open_session(config)

    # Can be overridden
    def close_conn(self, config=''):
        """
        Close the connection if already open.
        """
        if self.conn != None:
            self.conn.close(config)
            self.conn = None

    def open_conn(self, directory=".", config=None):
        """
        Open the connection if already closed.
        """
        if self.conn == None:
            if config != None:
                self._old_config = self.conn_config
                self.conn_config = config
            self.conn = self.setUpConnectionOpen(directory)
            if config != None:
                self.conn_config = self._old_config
            self.session = self.setUpSessionOpen(self.conn)

    def reopen_conn(self, directory=".", config=None):
        """
        Reopen the connection.
        """
        self.close_conn()
        self.open_conn(directory, config)

    def setUp(self):
        if not hasattr(self.__class__, 'wt_ntests'):
            self.__class__.wt_ntests = 0
        if WiredTigerTestCase._concurrent:
            self.testsubdir = self.shortid() + '.' + str(self.__class__.wt_ntests)
        else:
            self.testsubdir = self.className() + '.' + str(self.__class__.wt_ntests)
        self.testdir = os.path.join(WiredTigerTestCase._parentTestdir, self.testsubdir)
        self.__class__.wt_ntests += 1
        self.starttime = time.time()
        if WiredTigerTestCase._verbose > 2:
            self.prhead('started in ' + self.testdir, True)
        # tearDown needs connections list, set it here in case the open fails.
        self._connections = []
        self.origcwd = os.getcwd()
        shutil.rmtree(self.testdir, ignore_errors=True)
        if os.path.exists(self.testdir):
            raise Exception(self.testdir + ": cannot remove directory")
        os.makedirs(self.testdir)
        os.chdir(self.testdir)
        with open('testname.txt', 'w+') as namefile:
            namefile.write(str(self) + '\n')
        self.fdSetUp()
        # tearDown needs a conn field, set it here in case the open fails.
        self.conn = None
        try:
            self.conn = self.setUpConnectionOpen(".")
            self.session = self.setUpSessionOpen(self.conn)
        except:
            self.tearDown()
            raise

    def tearDown(self):
        excinfo = sys.exc_info()
        passed = (excinfo == (None, None, None))
        if passed:
            skipped = False
        else:
            skipped = (excinfo[0] == unittest.SkipTest)
        self.pr('finishing')

        # Close all connections that weren't explicitly closed.
        # Connections left open (as a result of a test failure)
        # can result in cascading errors.  We also make sure
        # self.conn is on the list of active connections.
        if not self.conn in self._connections:
            self._connections.append(self.conn)
        for conn in self._connections:
            try:
                conn.close()
            except:
                pass
        self._connections = []

        try:
            self.fdTearDown()
            # Only check for unexpected output if the test passed
            if passed:
                self.captureout.check(self)
                self.captureerr.check(self)
        finally:
            # always get back to original directory
            os.chdir(self.origcwd)

        # Make sure no read-only files or directories were left behind
        os.chmod(self.testdir, 0777)
        for root, dirs, files in os.walk(self.testdir):
            for d in dirs:
                os.chmod(os.path.join(root, d), 0777)
            for f in files:
                os.chmod(os.path.join(root, f), 0666)

        # Clean up unless there's a failure
        if (passed or skipped) and not WiredTigerTestCase._preserveFiles:
            shutil.rmtree(self.testdir, ignore_errors=True)
        else:
            self.pr('preserving directory ' + self.testdir)

        elapsed = time.time() - self.starttime
        if elapsed > 0.001 and WiredTigerTestCase._verbose >= 2:
            print "%s: %.2f seconds" % (str(self), elapsed)
        if not passed and not skipped:
            print "ERROR in " + str(self)
            self.pr('FAIL')
            self.prexception(excinfo)
            self.pr('preserving directory ' + self.testdir)
        if WiredTigerTestCase._verbose > 2:
            self.prhead('TEST COMPLETED')

    def backup(self, backup_dir, session=None):
        if session is None:
            session = self.session
        shutil.rmtree(backup_dir, ignore_errors=True)
        os.mkdir(backup_dir)
        bkp_cursor = session.open_cursor('backup:', None, None)
        while True:
            ret = bkp_cursor.next()
            if ret != 0:
                break
            shutil.copy(bkp_cursor.get_key(), backup_dir)
        self.assertEqual(ret, wiredtiger.WT_NOTFOUND)
        bkp_cursor.close()

    @contextmanager
    def expectedStdout(self, expect):
        self.captureout.check(self)
        yield
        self.captureout.checkAdditional(self, expect)

    @contextmanager
    def expectedStderr(self, expect):
        self.captureerr.check(self)
        yield
        self.captureerr.checkAdditional(self, expect)

    @contextmanager
    def expectedStdoutPattern(self, pat):
        self.captureout.check(self)
        yield
        self.captureout.checkAdditionalPattern(self, pat)

    @contextmanager
    def expectedStderrPattern(self, pat):
        self.captureerr.check(self)
        yield
        self.captureerr.checkAdditionalPattern(self, pat)

    def assertRaisesWithMessage(self, exceptionType, expr, message):
        """
        Like TestCase.assertRaises(), but also checks to see
        that a message is printed on stderr.  If message starts
        and ends with a slash, it is considered a pattern that
        must appear in stderr (it need not encompass the entire
        error output).  Otherwise, the message must match verbatim,
        including any trailing newlines.
        """
        if len(message) > 2 and message[0] == '/' and message[-1] == '/':
            with self.expectedStderrPattern(message[1:-1]):
                self.assertRaises(exceptionType, expr)
        else:
            with self.expectedStderr(message):
                self.assertRaises(exceptionType, expr)

    def assertRaisesException(self, exceptionType, expr,
        exceptionString=None, optional=False):
        """
        Like TestCase.assertRaises(), with some additional options.
        If the exceptionString argument is used, the exception's string
        must match it. If optional is set, then no assertion occurs
        if the exception doesn't occur.
        Returns true if the assertion is raised.
        """
        raised = False
        try:
            expr()
        except BaseException, err:
            if not isinstance(err, exceptionType):
                self.fail('Exception of incorrect type raised, got type: ' + \
                    str(type(err)))
            if exceptionString != None and exceptionString != str(err):
                self.fail('Exception with incorrect string raised, got: "' + \
                    str(err) + '"')
            raised = True
        if not raised and not optional:
            self.fail('no assertion raised')
        return raised

    def raisesBusy(self, expr):
        """
        Execute the expression, returning true if a 'Resource busy'
        exception is raised, returning false if no exception is raised.
        Any other exception raises a test suite failure.
        """
        return self.assertRaisesException(wiredtiger.WiredTigerError, \
            expr, exceptionString='Resource busy', optional=True)

    def assertTimestampsEqual(self, ts1, ts2):
        """
        TestCase.assertEqual() for timestamps
        """
        self.assertEqual(int(ts1, 16), int(ts2, 16))

    def exceptionToStderr(self, expr):
        """
        Used by assertRaisesHavingMessage to convert an expression
        that throws an error to an expression that throws the
        same error but also has the exception string on stderr.
        """
        try:
            expr()
        except BaseException, err:
            sys.stderr.write('Exception: ' + str(err))
            raise

    def assertRaisesHavingMessage(self, exceptionType, expr, message):
        """
        Like TestCase.assertRaises(), but also checks to see
        that the assert exception, when string-ified, includes a message.
        If message starts and ends with a slash, it is considered a pattern that
        must appear (it need not encompass the entire message).
        Otherwise, the message must match verbatim.
        """
        self.assertRaisesWithMessage(
            exceptionType, lambda: self.exceptionToStderr(expr), message)

    @staticmethod
    def printOnce(msg):
        # There's a race condition with multiple threads,
        # but we won't worry about it.  We err on the side
        # of printing the message too many times.
        if not msg in WiredTigerTestCase._printOnceSeen:
            WiredTigerTestCase._printOnceSeen[msg] = msg
            WiredTigerTestCase.prout(msg)

    def KNOWN_FAILURE(self, name):
        myname = self.simpleName()
        msg = '**** ' + myname + ' HAS A KNOWN FAILURE: ' + name + ' ****'
        self.printOnce(msg)
        self.skipTest('KNOWN FAILURE: ' + name)

    def KNOWN_LIMITATION(self, name):
        myname = self.simpleName()
        msg = '**** ' + myname + ' HAS A KNOWN LIMITATION: ' + name + ' ****'
        self.printOnce(msg)

    @staticmethod
    def printVerbose(level, message):
        if level <= WiredTigerTestCase._verbose:
            WiredTigerTestCase.prout(message)

    def verbose(self, level, message):
        WiredTigerTestCase.printVerbose(level, message)

    def prout(self, s):
        WiredTigerTestCase.prout(s)

    @staticmethod
    def prout(s):
        os.write(WiredTigerTestCase._dupout, s + '\n')

    def pr(self, s):
        """
        print a progress line for testing
        """
        msg = '    ' + self.shortid() + ': ' + s
        WiredTigerTestCase._resultfile.write(msg + '\n')

    def prhead(self, s, *beginning):
        """
        print a header line for testing, something important
        """
        msg = ''
        if len(beginning) > 0:
            msg += '\n'
        msg += '  ' + self.shortid() + ': ' + s
        self.prout(msg)
        WiredTigerTestCase._resultfile.write(msg + '\n')

    def prexception(self, excinfo):
        WiredTigerTestCase._resultfile.write('\n')
        traceback.print_exception(excinfo[0], excinfo[1], excinfo[2], None, WiredTigerTestCase._resultfile)
        WiredTigerTestCase._resultfile.write('\n')

    # print directly to tty, useful for debugging
    def tty(self, message):
        WiredTigerTestCase.tty(message)

    @staticmethod
    def tty(message):
        if WiredTigerTestCase._ttyDescriptor == None:
            WiredTigerTestCase._ttyDescriptor = open('/dev/tty', 'w')
        WiredTigerTestCase._ttyDescriptor.write(message + '\n')

    def ttyVerbose(self, level, message):
        WiredTigerTestCase.ttyVerbose(level, message)

    @staticmethod
    def ttyVerbose(level, message):
        if level <= WiredTigerTestCase._verbose:
            WiredTigerTestCase.tty(message)

    def shortid(self):
        return self.id().replace("__main__.","")

    def className(self):
        return self.__class__.__name__

def longtest(description):
    """
    Used as a function decorator, for example, @wttest.longtest("description").
    The decorator indicates that this test function should only be included
    when running the test suite with the --long option.
    """
    def runit_decorator(func):
        return func
    if not WiredTigerTestCase._longtest:
        return unittest.skip(description + ' (enable with --long)')
    else:
        return runit_decorator

def islongtest():
    return WiredTigerTestCase._longtest

def runsuite(suite, parallel):
    suite_to_run = suite
    if parallel > 1:
        from concurrencytest import ConcurrentTestSuite, fork_for_tests
        if not WiredTigerTestCase._globalSetup:
            WiredTigerTestCase.globalSetup()
        WiredTigerTestCase._concurrent = True
        suite_to_run = ConcurrentTestSuite(suite, fork_for_tests(parallel))
    try:
        return unittest.TextTestRunner(
            verbosity=WiredTigerTestCase._verbose).run(suite_to_run)
    except BaseException as e:
        # This should not happen for regular test errors, unittest should catch everything
        print('ERROR: running test: ', e)
        raise e

def run(name='__main__'):
    result = runsuite(unittest.TestLoader().loadTestsFromName(name), False)
    sys.exit(0 if result.wasSuccessful() else 1)


# Copyright 2014 The Oppia Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Common utility functions."""

from __future__ import absolute_import  # pylint: disable=import-only-modules
from __future__ import unicode_literals  # pylint: disable=import-only-modules

import base64
import collections
import datetime
import hashlib
import imghdr
import json
import os
import random
import re
import string
import sys
import time
import unicodedata
import zlib

from constants import constants
import feconf
import python_utils

_YAML_PATH = os.path.join(os.getcwd(), '..', 'oppia_tools', 'pyyaml-5.1.2')
sys.path.insert(0, _YAML_PATH)

import yaml  # isort:skip  #pylint: disable=wrong-import-position

DATETIME_FORMAT = '%m/%d/%Y, %H:%M:%S:%f'
PNG_DATA_URL_PREFIX = 'data:image/png;base64,'
SECONDS_IN_HOUR = 60 * 60
SECONDS_IN_MINUTE = 60

class InvalidInputException(Exception):
    """Error class for invalid input."""

    pass

class ValidationError(Exception):
    """Error class for when a domain object fails validation."""

    pass

class ExplorationConversionError(Exception):
    """Error class for when an exploration fails to convert from a certain
    version to a certain version.
    """

    pass

def create_enum(*sequential, **names):
    """Creates a enumerated constant.

    Args:
        *sequential: *. Sequence List to generate the enumerations.
        **names: *. Names of the enumerration.

    Returns:
        dict. Dictionary containing the enumerated constants.
    """
    enums = dict(python_utils.ZIP(sequential, sequential), **names)
    return type(b'Enum', (), enums)

def get_file_contents(filepath, raw_bytes=False, mode='r'):
    """Gets the contents of a file, given a relative filepath from oppia/.

    Args:
        filepath: str. A full path to the file.
        raw_bytes: bool. Flag for the raw_bytes output.
        mode: str. File opening mode, default is in read mode.

    Returns:
        *. Either the raw_bytes stream if the flag is set or the
        decoded stream in utf-8 format.
    """
    if raw_bytes:
        mode = 'rb'
        encoding = None
    else:
        encoding = 'utf-8'

    with python_utils.open_file(filepath, mode, encoding=encoding) as f:
        return f.read()

def get_exploration_components_from_dir(dir_path):
    """Gets the (yaml, assets) from the contents of an exploration data dir.

    Args:
        dir_path: str. A full path to the exploration root directory.

    Returns:
        *. A 2-tuple, the first element of which is a yaml string, and the
        second element of which is a list of (filepath, content) 2-tuples.
        The filepath does not include the assets/ prefix.

    Raises:
        Exception. If the following condition doesn't hold: "There
            is exactly one file not in assets/, and this file has a
            .yaml suffix".
    """
    yaml_content = None
    assets_list = []

    dir_path_array = dir_path.split('/')
    while dir_path_array[-1] == '':
        dir_path_array = dir_path_array[:-1]
    dir_path_length = len(dir_path_array)

    for root, directories, files in os.walk(dir_path):
        for directory in directories:
            if root == dir_path and directory != 'assets':
                raise Exception(
                    'The only directory in %s should be assets/' % dir_path)

        for filename in files:
            filepath = os.path.join(root, filename)
            if root == dir_path:
                # These files are added automatically by Mac OS Xsystems.
                # We ignore them.
                if not filepath.endswith('.DS_Store'):
                    if yaml_content is not None:
                        raise Exception(
                            'More than one non-asset file specified '
                            'for %s' % dir_path)
                    elif not filepath.endswith('.yaml'):
                        raise Exception(
                            'Found invalid non-asset file %s. There '
                            'should only be a single non-asset file, '
                            'and it should have a .yaml suffix.' % filepath)
                    else:
                        yaml_content = get_file_contents(filepath)
            else:
                filepath_array = filepath.split('/')
                # The additional offset is to remove the 'assets/' prefix.
                filename = '/'.join(filepath_array[dir_path_length + 1:])
                assets_list.append((filename, get_file_contents(
                    filepath, raw_bytes=True)))

    if yaml_content is None:
        raise Exception('No yaml file specifed for %s' % dir_path)

    return yaml_content, assets_list

def get_comma_sep_string_from_list(items):
    """Turns a list of items into a comma-separated string.

    Args:
        items: list(str). List of the items.

    Returns:
        str. String containing the items in the list separated by commas.
    """

    if not items:
        return ''

    if len(items) == 1:
        return items[0]

    return '%s and %s' % (', '.join(items[:-1]), items[-1])

def to_ascii(input_string):
    """Change unicode characters in a string to ascii if possible.

    Args:
        input_string: str. String to convert.

    Returns:
        str. String containing the ascii representation of the input string.
    """
    return unicodedata.normalize(
        'NFKD', python_utils.UNICODE(input_string)).encode('ascii', 'ignore')

def dict_from_yaml(yaml_str):
    """Gets the dict representation of a YAML string.

    Args:
        yaml_str: str. Yaml string for conversion into dict.

    Returns:
        dict. Parsed dict representation of the yaml string.

    Raises:
        InavlidInputException. If the yaml string sent as the
            parameter is unable to get parsed, them this error gets
            raised.
    """
    try:
        retrieved_dict = yaml.safe_load(yaml_str)
        assert isinstance(retrieved_dict, dict)
        return retrieved_dict
    except (AssertionError, yaml.YAMLError) as e:
        raise InvalidInputException(e)

def recursively_remove_key(obj, key_to_remove):
    """Recursively removes keys from a list or dict.

    Args:
        obj: *. List or dict passed for which the keys has to
            be removed.
        key_to_remove: str. Key value that has to be removed.

    Returns:
        *. Dict or list with a particular key value removed.
    """
    if isinstance(obj, list):
        for item in obj:
            recursively_remove_key(item, key_to_remove)
    elif isinstance(obj, dict):
        if key_to_remove in obj:
            del obj[key_to_remove]
        for key, unused_value in obj.items():
            recursively_remove_key(obj[key], key_to_remove)

def get_random_int(upper_bound):
    """Returns a random integer in [0, upper_bound).

    Args:
        upper_bound: int. Upper limit for generation of random
            integer.

    Returns:
        int. Randomly generated integer less than the upper_bound.
    """
    assert upper_bound >= 0 and isinstance(upper_bound, int)

    generator = random.SystemRandom()
    return generator.randrange(0, stop=upper_bound)

def get_random_choice(alist):
    """Gets a random element from a list.

    Args:
        alist: list(*). Input to get a random choice.

    Returns:
        *. Random element choosen from the passed input list.
    """
    assert isinstance(alist, list) and len(alist) > 0

    index = get_random_int(len(alist))
    return alist[index]

def convert_png_data_url_to_binary(image_data_url):
    """Converts a PNG base64 data URL to a PNG binary data.

    Args:
        image_data_url: str. A string that is to be interpreted as a PNG
            data URL.

    Returns:
        str. Binary content of the PNG created from the data URL.

    Raises:
        Exception. The given string does not represent a PNG data URL.
    """
    if image_data_url.startswith(PNG_DATA_URL_PREFIX):
        return base64.b64decode(
            python_utils.urllib_unquote(
                image_data_url[len(PNG_DATA_URL_PREFIX):]))
    else:
        raise Exception('The given string does not represent a PNG data URL.')

def convert_png_binary_to_data_url(content):
    """Converts a PNG image string (represented by 'content') to a data URL.

    Args:
        content: str. PNG binary file content.

    Returns:
        str. Data URL created from the binary content of the PNG.

    Raises:
        Exception. The given binary string does not represent a PNG image.
    """
    if imghdr.what(None, h=content) == 'png':
        return '%s%s' % (
            PNG_DATA_URL_PREFIX,
            python_utils.url_quote(base64.b64encode(content))
        )
    else:
        raise Exception('The given string does not represent a PNG image.')

def convert_png_to_data_url(filepath):
    """Converts the png file at filepath to a data URL.

    Args:
        filepath: str. A full path to the file.

    Returns:
        str. Data url created from the filepath of the PNG.
    """
    file_contents = get_file_contents(filepath, raw_bytes=True, mode='rb')
    return convert_png_binary_to_data_url(file_contents)

def camelcase_to_hyphenated(camelcase_str):
    """Camelcase to hyhpenated conversion of the passed string.

    Args:
        camelcase_str: str. Camelcase string representation.

    Returns:
        str. Hypenated string representation of the camelcase string.
    """
    intermediate_str = re.sub('(.)([A-Z][a-z]+)', r'\1-\2', camelcase_str)
    return re.sub('([a-z0-9])([A-Z])', r'\1-\2', intermediate_str).lower()

def camelcase_to_snakecase(camelcase_str):
    """Camelcase to snake case conversion of the passed string.

    Args:
        camelcase_str: str. Camelcase string representation.

    Returns:
        str. Snakecase representation of the passed camelcase string.
    """
    intermediate_str = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', camelcase_str)
    return re.sub('([a-z0-9])([A-Z])', r'\1_\2', intermediate_str).lower()

def set_url_query_parameter(url, param_name, param_value):
    """Set or replace a query parameter, and return the modified URL.

    Args:
        url: str. URL string which contains the query parameter.
        param_name: str. Parameter name to be removed.
        param_value: str. Set the parameter value, if it exists.

    Returns:
        str. Formated URL that has query parameter set or replaced.

    Raises:
        Exception. If the query parameter sent is not of string type,
            them this exception is raised.
    """
    if not isinstance(param_name, python_utils.BASESTRING):
        raise Exception(
            'URL query parameter name must be a string, received %s'
            % param_name)

    scheme, netloc, path, query_string, fragment = python_utils.url_split(url)
    query_params = python_utils.parse_query_string(query_string)

    query_params[param_name] = [param_value]
    new_query_string = python_utils.url_encode(query_params, doseq=True)

    return python_utils.url_unsplit(
        (scheme, netloc, path, new_query_string, fragment))

class JSONEncoderForHTML(json.JSONEncoder):
    """Encodes JSON that is safe to embed in HTML."""

    def encode(self, o):
        chunks = self.iterencode(o, True)
        return ''.join(chunks) if self.ensure_ascii else u''.join(chunks)

    def iterencode(self, o, _one_shot=False):
        chunks = super(
            JSONEncoderForHTML, self).iterencode(o, _one_shot=_one_shot)
        for chunk in chunks:
            yield chunk.replace('&', '\\u0026').replace(
                '<', '\\u003c').replace('>', '\\u003e')

def convert_to_hash(input_string, max_length):
    """Convert a string to a SHA1 hash.

    Args:
        input_string: str. Input string for conversion to hash.
        max_length: int. Maximum Length of the generated hash.

    Returns:
        str. Hash Value generated from the input_String of the
        specified length.

    Raises:
        Exception. If the input string is not the instance of the str,
            them this exception is raised.
    """
    if not isinstance(input_string, python_utils.BASESTRING):
        raise Exception(
            'Expected string, received %s of type %s' %
            (input_string, type(input_string)))

    # Encodes strings using the character set [A-Za-z0-9].
    # Prefixing altchars with b' to ensure that all characters in encoded_string
    # remain encoded (otherwise encoded_string would be of type unicode).
    encoded_string = base64.b64encode(
        hashlib.sha1(python_utils.convert_to_bytes(input_string)).digest(),
        altchars=b'ab'
    ).replace('=', 'c')

    return encoded_string[:max_length]

def base64_from_int(value):
    """Converts the number into base64 representation.

    Args:
        value: int. Integer value for conversion into base64.

    Returns:
        *. Returns the base64 representation of the number passed.
    """
    return base64.b64encode(bytes([value]))

def get_time_in_millisecs(datetime_obj):
    """Returns time in milliseconds since the Epoch.

    Args:
        datetime_obj: datetime. An object of type datetime.datetime.

    Returns:
        float. The time in milliseconds since the Epoch.
    """
    msecs = time.mktime(datetime_obj.timetuple()) * 1000.0
    return msecs + python_utils.divide(datetime_obj.microsecond, 1000.0)

def convert_naive_datetime_to_string(datetime_obj):
    """Returns a human-readable string representing the naive datetime object.

    Args:
        datetime_obj: datetime. An object of type datetime.datetime. Must be a
            naive datetime object.

    Returns:
        str. The string representing the naive datetime object.
    """
    return datetime_obj.strftime(DATETIME_FORMAT)

def convert_string_to_naive_datetime_object(date_time_string):
    """Returns the naive datetime object equivalent of the date string.

    Args:
        date_time_string: str. The string format representing the datetime
            object in the format: Month/Day/Year,
            Hour:Minute:Second:MicroSecond.

    Returns:
        datetime. An object of type naive datetime.datetime corresponding to
        that string.
    """
    return datetime.datetime.strptime(date_time_string, DATETIME_FORMAT)

def get_current_time_in_millisecs():
    """Returns time in milliseconds since the Epoch.

    Returns:
        float. The time in milliseconds since the Epoch.
    """
    return get_time_in_millisecs(datetime.datetime.utcnow())

def get_human_readable_time_string(time_msec):
    """Given a time in milliseconds since the epoch, get a human-readable
    time string for the admin dashboard.

    Args:
        time_msec: float. Time in milliseconds since the Epoch.

    Returns:
        str. A string representing the time.
    """
    return time.strftime(
        '%B %d %H:%M:%S', time.gmtime(python_utils.divide(time_msec, 1000.0)))

def create_string_from_largest_unit_in_timedelta(timedelta_obj):
    """Given the timedelta object, find the largest nonzero time unit and
    return that value, along with the time unit, as a human readable string.
    The returned string is not localized.

    Args:
        timedelta_obj: datetime.timedelta. A datetime timedelta object. Datetime
            timedelta objects are created when you subtract two datetime
            objects.

    Returns:
        str. A human readable string representing the value of the largest
        nonzero time unit, along with the time units. If the largest time unit
        is seconds, 1 minute is returned. The value is represented as an integer
        in the string.

    Raises:
        Exception. If the provided timedelta is not positive.
    """
    total_seconds = timedelta_obj.total_seconds()
    if total_seconds <= 0:
        raise Exception(
            'Expected a positive timedelta, received: %s.' % total_seconds)
    elif timedelta_obj.days != 0:
        return '%s day%s' % (
            int(timedelta_obj.days), 's' if timedelta_obj.days > 1 else '')
    else:
        number_of_hours, remainder = divmod(total_seconds, SECONDS_IN_HOUR)
        number_of_minutes, _ = divmod(remainder, SECONDS_IN_MINUTE)
        if number_of_hours != 0:
            return '%s hour%s' % (
                int(number_of_hours), 's' if number_of_hours > 1 else '')
        elif number_of_minutes > 1:
            return '%s minutes' % int(number_of_minutes)
        # Round any seconds up to one minute.
        else:
            return '1 minute'

def are_datetimes_close(later_datetime, earlier_datetime):
    """Given two datetimes, determines whether they are separated by less than
    feconf.PROXIMAL_TIMEDELTA_SECS seconds.

    Args:
        later_datetime: datetime. The later datetime.
        earlier_datetime: datetime. The earlier datetime.

    Returns:
        bool. True if difference between two datetimes is less than
        feconf.PROXIMAL_TIMEDELTA_SECS seconds otherwise false.
    """
    difference_in_secs = (later_datetime - earlier_datetime).total_seconds()
    return difference_in_secs < feconf.PROXIMAL_TIMEDELTA_SECS

def generate_random_string(length):
    """Generates a random string of the specified length.

    Args:
        length: int. Length of the string to be generated.

    Returns:
        str. Random string of specified length.
    """
    return base64.urlsafe_b64encode(os.urandom(length))[:length]

def generate_new_session_id():
    """Generates a new session id.

    Returns:
        str. Random string of length 24.
    """
    return generate_random_string(24)

def vfs_construct_path(base_path, *path_components):
    """Mimics behavior of os.path.join on Posix machines.

    Args:
        base_path: str. The initial path upon which components would be added.
        *path_components: list(str). Components that would be added to the path.

    Returns:
        str. The path that is obtained after adding the components.
    """
    return os.path.join(base_path, *path_components)

def vfs_normpath(path):
    """Normalize path from posixpath.py, eliminating double slashes, etc.

    Args:
        path: str. Path that is to be normalized.

    Returns:
        str. Path if it is not null else a dot string.
    """
    return os.path.normpath(path)

def require_valid_name(name, name_type, allow_empty=False):
    """Generic name validation.

    Args:
        name: str. The name to validate.
        name_type: str. A human-readable string, like 'the exploration title' or
            'a state name'. This will be shown in error messages.
        allow_empty: bool. If True, empty strings are allowed.

    Raises:
        Exception. Name isn't a string.
        Exception. The length of the name_type isn't between
            1 and 50.
        Exception. Name starts or ends with whitespace.
        Exception. Adjacent whitespace in name_type isn't collapsed.
        Exception. Invalid character is present in name.
    """
    if not isinstance(name, python_utils.BASESTRING):
        raise ValidationError('%s must be a string.' % name)

    if allow_empty and name == '':
        return

    # This check is needed because state names are used in URLs and as ids
    # for statistics, so the name length should be bounded above.
    if len(name) > 50 or len(name) < 1:
        raise ValidationError(
            'The length of %s should be between 1 and 50 '
            'characters; received %s' % (name_type, name))

    if name[0] in string.whitespace or name[-1] in string.whitespace:
        raise ValidationError(
            'Names should not start or end with whitespace.')

    if re.search(r'\s\s+', name):
        raise ValidationError(
            'Adjacent whitespace in %s should be collapsed.' % name_type)

    for character in constants.INVALID_NAME_CHARS:
        if character in name:
            raise ValidationError(
                'Invalid character %s in %s: %s' %
                (character, name_type, name))

def require_valid_url_fragment(name, name_type, allowed_length):
    """Generic URL fragment validation.

    Args:
        name: str. The name to validate.
        name_type: str. A human-readable string, like 'topic url fragment'.
            This will be shown in error messages.
        allowed_length: int. Allowed length for the name.

    Raises:
        Exception. Name is not a string.
        Exception. Name is empty.
        Exception. The length of the name_type is not correct.
        Exception. Invalid character is present in the name.
    """
    if not isinstance(name, python_utils.BASESTRING):
        raise ValidationError(
            '%s field must be a string. Received %s.' % (name_type, name))

    if name == '':
        raise ValidationError(
            '%s field should not be empty.' % name_type)

    if len(name) > allowed_length:
        raise ValidationError(
            '%s field should not exceed %d characters, '
            'received %s.' % (name_type, allowed_length, name))

    if not re.match(constants.VALID_URL_FRAGMENT_REGEX, name):
        raise ValidationError(
            '%s field contains invalid characters. Only lowercase words'
            ' separated by hyphens are allowed. Received %s.' % (
                name_type, name))

def require_valid_thumbnail_filename(thumbnail_filename):
    """Generic thumbnail filename validation.

        Args:
            thumbnail_filename: str. The thumbnail filename to validate.
        """
    if thumbnail_filename is not None:
        if not isinstance(thumbnail_filename, python_utils.BASESTRING):
            raise ValidationError(
                'Expected thumbnail filename to be a string, received %s'
                % thumbnail_filename)
        if thumbnail_filename.rfind('.') == 0:
            raise ValidationError(
                'Thumbnail filename should not start with a dot.')
        if '/' in thumbnail_filename or '..' in thumbnail_filename:
            raise ValidationError(
                'Thumbnail filename should not include slashes or '
                'consecutive dot characters.')
        if '.' not in thumbnail_filename:
            raise ValidationError(
                'Thumbnail filename should include an extension.')

        dot_index = thumbnail_filename.rfind('.')
        extension = thumbnail_filename[dot_index + 1:].lower()
        if extension != 'svg':
            raise ValidationError(
                'Expected a filename ending in svg, received %s' %
                thumbnail_filename)

def require_valid_meta_tag_content(meta_tag_content):
    """Generic meta tag content validation.

        Args:
            meta_tag_content: str. The meta tag content to validate.
        """
    if not isinstance(meta_tag_content, python_utils.BASESTRING):
        raise ValidationError(
            'Expected meta tag content to be a string, received %s'
            % meta_tag_content)
    if len(meta_tag_content) > constants.MAX_CHARS_IN_META_TAG_CONTENT:
        raise ValidationError(
            'Meta tag content should not be longer than %s characters.'
            % constants.MAX_CHARS_IN_META_TAG_CONTENT)

def require_valid_page_title_fragment_for_web(page_title_fragment_for_web):
    """Generic page title fragment validation.

    Args:
        page_title_fragment_for_web: str. The page title fragment to validate.

    Raises:
        Exception. Page title fragment is not a string.
        Exception. Page title fragment is too lengthy.
    """
    max_chars_in_page_title_frag_for_web = (
        constants.MAX_CHARS_IN_PAGE_TITLE_FRAGMENT_FOR_WEB)
    if not isinstance(page_title_fragment_for_web, python_utils.BASESTRING):
        raise ValidationError(
            'Expected page title fragment to be a string, received %s'
            % page_title_fragment_for_web)
    if len(page_title_fragment_for_web) > max_chars_in_page_title_frag_for_web:
        raise ValidationError(
            'Page title fragment should not be longer than %s characters.'
            % constants.MAX_CHARS_IN_PAGE_TITLE_FRAGMENT_FOR_WEB)

def capitalize_string(input_string):
    """Converts the first character of a string to its uppercase equivalent (if
    it's a letter), and returns the result.

    Args:
        input_string: str. String to process (to capitalize).

    Returns:
        str. Capitalizes the string.
    """
    # This guards against empty strings.
    if input_string:
        return input_string[0].upper() + input_string[1:]
    else:
        return input_string

def get_hex_color_for_category(category):
    """Returns the category, it returns the color associated with the category,
    if the category is present in the app constants else given a default color.

    Args:
        category: str. Category to get color.

    Returns:
        str. Color assigned to that category.
    """
    return (
        constants.CATEGORIES_TO_COLORS[category]
        if category in constants.CATEGORIES_TO_COLORS
        else constants.DEFAULT_COLOR)

def get_thumbnail_icon_url_for_category(category):
    """Returns the category, it returns the associated thumbnail icon, if the
    category is present in the app constants else given a default thumbnail.

    Args:
        category: str. Category to get Thumbnail icon.

    Returns:
        str. Path to the Thumbnail Icon assigned to that category.
    """
    icon_name = (
        category if category in constants.CATEGORIES_TO_COLORS
        else constants.DEFAULT_THUMBNAIL_ICON)
    # Remove all spaces from the string.
    return '/subjects/%s.svg' % (icon_name.replace(' ', ''))

def is_supported_audio_language_code(language_code):
    """Checks if the given language code is a supported audio language code.

    Args:
        language_code: str. The language code.

    Returns:
        bool. Whether the language code is supported audio language code or not.
    """
    language_codes = [lc['id'] for lc in constants.SUPPORTED_AUDIO_LANGUAGES]
    return language_code in language_codes

def is_valid_language_code(language_code):
    """Checks if the given language code is a valid language code.

    Args:
        language_code: str. The language code.

    Returns:
        bool. Whether the language code is valid or not.
    """
    language_codes = [
        lc['code'] for lc in constants.SUPPORTED_CONTENT_LANGUAGES]
    return language_code in language_codes

def get_supported_audio_language_description(language_code):
    """Returns the language description for the given language code.

    Args:
        language_code: str. The language code for which the description is
            required.

    Returns:
        str. The language description for the given language code.

    Raises:
        Exception. If the given language code is unsupported.
    """
    for language in constants.SUPPORTED_AUDIO_LANGUAGES:
        if language['id'] == language_code:
            return language['description']
    raise Exception('Unsupported audio language code: %s' % language_code)

def is_user_id_valid(
        user_id, allow_system_user_id=False, allow_pseudonymous_id=False):
    """Verify that the user ID is in a correct format or that it belongs to
    a system user.

    Args:
        user_id: str. The user ID to be checked.
        allow_system_user_id: bool. Whether to allow system user ID.
        allow_pseudonymous_id: bool. Whether to allow pseudonymized ID.

    Returns:
        bool. True when the ID is in a correct format or if the ID belongs to
        a system user, False otherwise.
    """
    if allow_system_user_id and user_id in feconf.SYSTEM_USERS.keys():
        return True

    if allow_pseudonymous_id and is_pseudonymous_id(user_id):
        return True

    return bool(re.match(feconf.USER_ID_REGEX, user_id))

def is_pseudonymous_id(user_id):
    """Check that the ID is a pseudonymous one.

    Args:
        user_id: str. The ID to be checked.

    Returns:
        bool. Whether the ID represents a pseudonymous user.
    """
    return bool(re.match(feconf.PSEUDONYMOUS_ID_REGEX, user_id))

def unescape_encoded_uri_component(escaped_string):
    """Unescape a string that is encoded with encodeURIComponent.

    Args:
        escaped_string: str. String that is encoded with encodeURIComponent.

    Returns:
        str. Decoded string that was initially encoded with encodeURIComponent.
    """
    return python_utils.urllib_unquote(escaped_string).decode('utf-8')

def snake_case_to_camel_case(snake_str):
    """Converts a string in snake_case to camelCase.

    Args:
        snake_str: str. String that is in snake_case.

    Returns:
        str. Converted string that is in camelCase.
    """
    components = snake_str.split('_')
    # We capitalize the first letter of each component except the first one
    # with the 'title' method and join them together.
    return components[0] + ''.join(x.title() for x in components[1:])

def get_asset_dir_prefix():
    """Returns prefix for asset directory depending whether dev or prod.
    It is used as a prefix in urls for images, css and script files.

    Returns:
        str. Prefix '/build' if constants.DEV_MODE is false, otherwise
        null string.
    """
    asset_dir_prefix = ''
    if not constants.DEV_MODE:
        asset_dir_prefix = '/build'

    return asset_dir_prefix

def get_hashable_value(value):
    """This function returns a hashable version of the input JSON-like value.

    It converts the built-in sequences into their hashable counterparts
    {list: tuple, dict: (sorted tuple of pairs)}. Additionally, their
    elements are converted to hashable values through recursive calls. All
    other value types are assumed to already be hashable.

    Args:
        value: *. Some JSON-like object, that is, an object made-up of only:
            lists, dicts, strings, ints, bools, None. Types can be nested in
            each other.

    Returns:
        *. A new object that will always have the same hash for "equivalent"
        values.
    """
    if isinstance(value, list):
        return tuple(get_hashable_value(e) for e in value)
    elif isinstance(value, dict):
        return tuple(sorted(
            # Dict keys are already hashable, only values need converting.
            (k, get_hashable_value(v)) for k, v in value.items()))
    else:
        return value

def compress_to_zlib(data):
    """Compress the data to zlib format for efficient storage and communication.

    Args:
        data: str. Data to be compressed.

    Returns:
        str. Compressed data string.
    """
    return zlib.compress(data)

def decompress_from_zlib(data):
    """Decompress the zlib compressed data.

    Args:
        data: str. Data to be decompressed.

    Returns:
        str. Decompressed data string.
    """
    return zlib.decompress(data)

def compute_list_difference(list_a, list_b):
    """Returns the set difference of two lists.

    Args:
        list_a: list. The first list.
        list_b: list. The second list.

    Returns:
        list. List of the set difference of list_a - list_b.
    """
    return list(set(list_a) - set(list_b))

class OrderedCounter(collections.Counter, collections.OrderedDict):
    """Counter that remembers the order elements are first encountered."""

    pass


from .ai import *
from .condition import *
from .debug import *
from .diplomacy import *
from .effect import *
from .player import *
from .resource import *
from .tile import *
from .trigger import *
from .unit import *
from .units import *


"""Provides the 'OffshoreSubstationDesign` class."""

__author__ = "Jake Nunemaker"
__copyright__ = "Copyright 2020, National Renewable Energy Laboratory"
__maintainer__ = "Jake Nunemaker"
__email__ = "Jake.Nunemaker@nrel.gov"

import numpy as np

from ORBIT.phases.design import DesignPhase

class OffshoreSubstationDesign(DesignPhase):
    """Offshore Substation Design Class."""

    expected_config = {
        "site": {"depth": "m"},
        "plant": {"num_turbines": "int"},
        "turbine": {"turbine_rating": "MW"},
        "substation_design": {
            "mpt_cost_rate": "USD/MW (optional)",
            "topside_fab_cost_rate": "USD/t (optional)",
            "topside_design_cost": "USD (optional)",
            "shunt_cost_rate": "USD/MW (optional)",
            "switchgear_cost": "USD (optional)",
            "backup_gen_cost": "USD (optional)",
            "workspace_cost": "USD (optional)",
            "other_ancillary_cost": "USD (optional)",
            "topside_assembly_factor": "float (optional)",
            "oss_substructure_cost_rate": "USD/t (optional)",
            "oss_pile_cost_rate": "USD/t (optional)",
            "num_substations": "int (optional)",
        },
    }

    output_config = {
        "num_substations": "int",
        "offshore_substation_topside": "dict",
        "offshore_substation_substructure": "dict",
    }

    def __init__(self, config, **kwargs):
        """
        Creates an instance of OffshoreSubstationDesign.

        Parameters
        ----------
        config : dict
        """

        config = self.initialize_library(config, **kwargs)
        self.config = self.validate_config(config)
        self._outputs = {}

    def run(self):
        """Main run function."""

        self.calc_substructure_length()
        self.calc_substructure_deck_space()
        self.calc_topside_deck_space()

        self.calc_num_mpt_and_rating()
        self.calc_mpt_cost()
        self.calc_topside_mass_and_cost()
        self.calc_shunt_reactor_cost()
        self.calc_switchgear_cost()
        self.calc_ancillary_system_cost()
        self.calc_assembly_cost()
        self.calc_substructure_mass_and_cost()

        self._outputs["offshore_substation_substructure"] = {
            "type": "Monopile",  # Substation install only supports monopiles
            "deck_space": self.substructure_deck_space,
            "mass": self.substructure_mass,
            "length": self.substructure_length,
            "unit_cost": self.substructure_cost,
        }

        self._outputs["offshore_substation_topside"] = {
            "deck_space": self.topside_deck_space,
            "mass": self.topside_mass,
            "unit_cost": self.substation_cost,
        }

        self._outputs["num_substations"] = self.num_substations

    @property
    def substation_cost(self):
        """Returns total procuremet cost of the topside."""

        return (
            self.mpt_cost
            + self.topside_cost
            + self.shunt_reactor_cost
            + self.switchgear_costs
            + self.ancillary_system_costs
            + self.land_assembly_cost
        )

    @property
    def total_cost(self):
        """Returns total procurement cost of the substation(s)."""

        if not self._outputs:
            raise Exception("Has OffshoreSubstationDesign been ran yet?")

        return (
            self.substructure_cost + self.substation_cost
        ) * self.num_substations

    def calc_substructure_length(self):
        """
        Calculates substructure length as the site depth + 10m
        """

        self.substructure_length = self.config["site"]["depth"] + 10

    def calc_substructure_deck_space(self):
        """
        Calculates required deck space for the substation substructure.

        Coming soon!
        """

        self.substructure_deck_space = 1

    def calc_topside_deck_space(self):
        """
        Calculates required deck space for the substation topside.

        Coming soon!
        """

        self.topside_deck_space = 1

    def calc_num_mpt_and_rating(self):
        """
        Calculates the number of main power transformers (MPTs) and their rating.

        Parameters
        ----------
        num_turbines : int
        turbine_rating : float
        """

        _design = self.config.get("substation_design", {})

        num_turbines = self.config["plant"]["num_turbines"]
        turbine_rating = self.config["turbine"]["turbine_rating"]
        capacity = num_turbines * turbine_rating

        self.num_substations = _design.get(
            "num_substations", int(np.ceil(capacity / 500))
        )
        self.num_mpt = np.ceil(
            num_turbines * turbine_rating / (250 * self.num_substations)
        )
        self.mpt_rating = (
            round(
                (
                    (num_turbines * turbine_rating * 1.15)
                    / (self.num_mpt * self.num_substations)
                )
                / 10.0
            )
            * 10.0
        )

    def calc_mpt_cost(self):
        """
        Calculates the total cost for all MPTs.

        Parameters
        ----------
        mpt_cost_rate : float
        """

        _design = self.config.get("substation_design", {})
        mpt_cost_rate = _design.get("mpt_cost_rate", 12500)

        self.mpt_cost = self.mpt_rating * self.num_mpt * mpt_cost_rate

    def calc_topside_mass_and_cost(self):
        """
        Calculates the mass and cost of the substation topsides.

        Parameters
        ----------
        topside_fab_cost_rate : int | float
        topside_design_cost: int | float
        """

        _design = self.config.get("substation_design", {})
        topside_fab_cost_rate = _design.get("topside_fab_cost_rate", 14500)
        topside_design_cost = _design.get("topside_design_cost", 4.5e6)

        self.topside_mass = 3.85 * self.mpt_rating * self.num_mpt + 285
        self.topside_cost = (
            self.topside_mass * topside_fab_cost_rate + topside_design_cost
        )

    def calc_shunt_reactor_cost(self):
        """
        Calculates the cost of the shunt reactor.

        Parameters
        ----------
        shunt_cost_rate : int | float
        """

        _design = self.config.get("substation_design", {})
        shunt_cost_rate = _design.get("shunt_cost_rate", 35000)

        self.shunt_reactor_cost = (
            self.mpt_rating * self.num_mpt * shunt_cost_rate * 0.5
        )

    def calc_switchgear_cost(self):
        """
        Calculates the cost of the switchgear.

        Parameters
        ----------
        switchgear_cost : int | float
        """

        _design = self.config.get("substation_design", {})
        switchgear_cost = _design.get("switchgear_cost", 14.5e5)

        self.switchgear_costs = self.num_mpt * switchgear_cost

    def calc_ancillary_system_cost(self):
        """
        Calculates cost of ancillary systems.

        Parameters
        ----------
        backup_gen_cost : int | float
        workspace_cost : int | float
        other_ancillary_cost : int | float
        """

        _design = self.config.get("substation_design", {})
        backup_gen_cost = _design.get("backup_gen_cost", 1e6)
        workspace_cost = _design.get("workspace_cost", 2e6)
        other_ancillary_cost = _design.get("other_ancillary_cost", 3e6)

        self.ancillary_system_costs = (
            backup_gen_cost + workspace_cost + other_ancillary_cost
        )

    def calc_assembly_cost(self):
        """
        Calculates the cost of assembly on land.

        Parameters
        ----------
        topside_assembly_factor : int | float
        """

        _design = self.config.get("substation_design", {})
        topside_assembly_factor = _design.get("topside_assembly_factor", 0.075)
        self.land_assembly_cost = (
            self.switchgear_costs
            + self.shunt_reactor_cost
            + self.ancillary_system_costs
        ) * topside_assembly_factor

    def calc_substructure_mass_and_cost(self):
        """
        Calculates the mass and associated cost of the substation substructure.

        Parameters
        ----------
        oss_substructure_cost_rate : int | float
        oss_pile_cost_rate : int | float
        """

        _design = self.config.get("substation_design", {})
        oss_substructure_cost_rate = _design.get(
            "oss_substructure_cost_rate", 3000
        )
        oss_pile_cost_rate = _design.get("oss_pile_cost_rate", 0)

        substructure_mass = 0.4 * self.topside_mass
        substructure_pile_mass = 8 * substructure_mass ** 0.5574
        self.substructure_cost = (
            substructure_mass * oss_substructure_cost_rate
            + substructure_pile_mass * oss_pile_cost_rate
        )

        self.substructure_mass = substructure_mass + substructure_pile_mass

    @property
    def design_result(self):
        """
        Returns the results of self.run().
        """

        if not self._outputs:
            raise Exception("Has OffshoreSubstationDesign been ran yet?")

        return self._outputs

    @property
    def detailed_output(self):
        """Returns detailed phase information."""

        _outputs = {
            "num_substations": self.num_substations,
            "substation_mpt_rating": self.mpt_rating,
            "substation_topside_mass": self.topside_mass,
            "substation_topside_cost": self.topside_cost,
            "substation_substructure_mass": self.substructure_mass,
            "substation_substructure_cost": self.substructure_cost,
        }

        return _outputs


# -------------------------------------------------------------------------------------------------
#                                            Embroidery
# -------------------------------------------------------------------------------------------------
#
#       The program is to design simple ornament matrices for Christmas fair. It creates a matrix
# where 0 means an empty pixel, and positive integers mean different colors.
#
# -------------------------------------------------------------------------------------------------

NULL_CELL = ""  # null cell is a cell to fill with some color in the next step of create triangle function
ZERO_CELL = 0

# ---------------------------------------- main functions -----------------------------------------

def draw_rectangle(width, height, border_color=1, fill_color=1, border_width=1):
    '''
    Creats the rectangle matrix like this:

        1 1 1 1 1 1 1          1 1 1 1 1 1 1          1 1 1 1 1 1 1
        1 1 1 1 1 1 1          1 2 2 2 2 2 1          1 1 1 1 1 1 1
        1 1 1 1 1 1 1          1 2 2 2 2 2 1          1 1 2 2 2 1 1
        1 1 1 1 1 1 1          1 2 2 2 2 2 1          1 1 2 2 2 1 1
        1 1 1 1 1 1 1          1 2 2 2 2 2 1          1 1 1 1 1 1 1
        1 1 1 1 1 1 1          1 1 1 1 1 1 1          1 1 1 1 1 1 1
    '''
    matrix = []
    for row_no in range(height):
        row = []
        for col_no in range(width):
            fill_pattern = border_color if any([
                                            row_no + 1 <= border_width,       # first row
                                            height - row_no <= border_width,  # last row
                                            col_no + 1 <= border_width,       # first column
                                            height - col_no <= border_width   # last column
                                            ]) else fill_color
            row.append(fill_pattern)
        matrix.append(row)

    return matrix

def draw_triangle(height, border_color=1, fill_color=1):
    '''
    Creats the rectangle matrix like this:

        0 0 0 1 0 0 0          0 0 0 1 0 0 0
        0 0 1 1 1 0 0          0 0 1 2 1 0 0
        0 1 1 1 1 1 0          0 1 2 2 2 1 0
        1 1 1 1 1 1 1          1 1 1 1 1 1 1
    '''
    width = 2 * height - 1

    return create_triangle(height, width, fill_color, border_color)

def draw_christmas_tree(blocks, border_color=1, fill_color=1):
    '''
    Creats the rectangle matrix like this:

        0 0 0 0 0 1 0 0 0 0 0
        0 0 0 0 1 2 1 0 0 0 0
        0 0 0 1 2 2 2 1 0 0 0
        0 0 0 0 1 2 1 0 0 0 0
        0 0 0 1 2 2 2 1 0 0 0
        0 0 1 2 2 2 2 2 1 0 0
        0 0 0 1 2 2 2 1 0 0 0
        0 0 1 2 2 2 2 2 1 0 0
        0 1 2 2 2 2 2 2 2 1 0
        0 0 1 2 2 2 2 2 1 0 0
        0 1 2 2 2 2 2 2 2 1 0
        1 1 1 1 1 1 1 1 1 1 1
    '''
    height = 3
    width = 5 + (blocks - 1) * 2  # 5 - the base of the tree in the first block; 2 - distance between the bases of the trees of the next blocks
    base_width = 5

    matrix = []
    for block in range(blocks - 1):  # -1 -> without the last row
        matrix.extend(create_triangle(height, width, fill_color, border_color, False, base_width + block * 2))
    matrix.extend(create_triangle(height, width, fill_color, border_color, True, base_width + (block + 1) * 2))  # the last row with border

    return matrix

def draw_circle(radius, border_color=1, fill_color=1, half=True):
    '''
    Creats the circle matrix like this:

        0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0
        0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 2 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0
        0 0 0 0 0 0 0 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 0 0 0 0 0 0 0
        0 0 0 0 0 0 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 0 0 0 0 0 0
        0 0 0 0 0 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 0 0 0 0 0
        0 0 0 0 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 0 0 0 0
        0 0 0 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 0 0 0
        0 0 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 0 0
        0 0 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 0 0
        0 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 0
        0 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 0
        0 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 0
        1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1
        1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1
        1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1
    '''
    import math

    def get_distance_from_center(center_x, center_y, point_x, point_y):
        '''
        Based on Pythagoras' theorem, it calculates the length by the point from the center of the circle.
                     ____________________
            |AB| = \\/ (Xa-Xb)² + (Ya-Yb)²
        '''

        return round(math.sqrt(math.pow(abs(center_x - point_x), 2) + math.pow(abs(center_y - point_y), 2)), 0)

    def create_matrix(size_x, size_y):
        '''Sets matrix.'''
        matrix = []
        for row_no in range(size_x):
            row = []
            for col_no in range(size_y):
                row.append(NULL_CELL)
            matrix.append(row)
        return matrix

    def fill_empty(matrix, radius):
        '''Fills the matrix with empty cells located outside the circle.'''
        center_x, center_y = 0, 1
        circle_center = (radius - 1, radius - 1)

        for x in range(size_x):
            for y in range(size_y):
                distance = get_distance_from_center(circle_center[center_x], circle_center[center_y], x, y)
                if distance >= radius:
                    matrix[x][y] = ZERO_CELL
        return matrix

    def fill_border(matrix, border_color):
        '''Fills matrix with border cells.'''
        for x in range(size_x):
            for y in range(size_y):
                if matrix[x][y] == NULL_CELL:  # cell to fill
                    if x == 0 or x == size_x - 1 or y == 0 or y == size_y - 1:  # the first and the last row and column
                        matrix[x][y] = border_color
                    elif matrix[x][y - 1] == ZERO_CELL or matrix[x][y + 1] == ZERO_CELL or matrix[x - 1][y] == ZERO_CELL or matrix[x + 1][y]:  # checks whether it is adjacent to zero (before, after, above, under)
                        matrix[x][y] = border_color
        return matrix

    # ------- draw_circle main code -------
    size_x = radius - 1 if half is True else radius * 2 - 1
    size_y = radius * 2 - 1

    matrix = create_matrix(size_x, size_y)
    matrix = fill_empty(matrix, radius)
    matrix = fill_border(matrix, border_color)
    matrix = fill_normal(matrix, fill_color)

    return matrix

def embroider(matrix, color_scheme):
    '''Draws on screen (console) created the matrix with patterns.'''
    for row in matrix:
        for cell in row:
            print(color_scheme[cell], end=' ')
        print()
    print()

# -------------------------------------- internal functions ---------------------------------------

def create_triangle(matrix_height, matrix_width, fill_color, border_color, border_last_row=True, base_width=None):
    '''
    Returns matrix filled with triangle pattern. Default values: base_width = matrix_width

        0 0 0 1 0 0 0           0 0 0 1 0 0 0           0 0 0 0 0 1 0 0 0 0 0           0 0 1 2 2 2 2 2 1 0 0
        0 0 1 2 1 0 0           0 0 1 2 1 0 0           0 0 0 0 1 2 1 0 0 0 0           0 1 2 2 2 2 2 2 2 1 0
        0 1 2 2 2 1 0           0 1 2 2 2 1 0           0 0 0 1 1 1 1 1 0 0 0           1 2 2 2 2 2 2 2 2 2 1
        1 1 1 1 1 1 1           1 2 2 2 2 2 1

        height = 4              matrix_height = 4       matrix_height = None            matrix_height = None
        matrix_width = 7        matrix_width = 7        matrix_width = 11               matrix_width = 11
        base_width=None         base_width=None         base_width=5                    base_width=None
        border_last_row=True    border_last_row=False   border_last_row=True            border_last_row=False
    '''
    def fill_empty(matrix_height, matrix_width, base_width):
        '''Fills matrix with empty cells (zeros).'''
        matrix = []

        empty_cell = (matrix_width - base_width) / 2 + (matrix_height - 1)  # number of empty cells one side from the middle column
        for row_no in range(matrix_height):
            row = []
            for col_no in range(matrix_width):
                row.append(ZERO_CELL) if (col_no + 1 <= empty_cell or matrix_width - col_no <= empty_cell) else row.append(NULL_CELL)  # empty cells from left or right side

            matrix.append(row)
            empty_cell -= 1

        return matrix

    def fill_border(matrix, border_color, border_last_row):
        '''Fills matrix with border cells.'''
        for row_no in range(matrix_height):
            for col_no in range(matrix_width):
                if matrix[row_no][col_no] == NULL_CELL:  # cell to fill
                    if col_no == 0 or matrix[row_no][col_no - 1] == ZERO_CELL or col_no == matrix_width - 1 or matrix[row_no][col_no + 1] == ZERO_CELL:
                        matrix[row_no][col_no] = border_color

        if border_last_row:
            for col_no in range(matrix_width):  # fills the last row border cell
                if matrix[row_no][col_no] == NULL_CELL:  # cell to fill
                    matrix[matrix_height - 1][col_no] = border_color
        return matrix

    # ------- create_triangle main code -------
    if base_width is None:  # base_width default value
        base_width = matrix_width
    elif base_width < matrix_height:  # the minimum base fills the entire height
        base_width = (matrix_height * 2) - 1

    matrix = fill_empty(matrix_height, matrix_width, base_width)
    matrix = fill_border(matrix, border_color, border_last_row)
    matrix = fill_normal(matrix, fill_color)

    return matrix

def fill_normal(matrix, fill_color):
    '''Fills matrix with normaln filled cells.'''
    matrix_height, matrix_width = len(matrix), len(matrix[0])

    for row_no in range(matrix_height):
        for col_no in range(matrix_width):
            if matrix[row_no][col_no] == NULL_CELL:  # cell to fill
                matrix[row_no][col_no] = fill_color
    return matrix

# ------------------------------------------- main code -------------------------------------------

if __name__ == '__main__':
    color_scheme = {ZERO_CELL: '0', 1: '1', 2: '2'}

    print("Rectangle:")
    embroider(draw_rectangle(19, 19, 1, 2, 3), color_scheme)
    print("Triangle:")
    embroider(draw_triangle(10, border_color=1, fill_color=2), color_scheme)
    print("Christmas tree:")
    embroider(draw_christmas_tree(8, 1, 2), color_scheme)
    print("Circle:")
    embroider(draw_circle(15, 1, 2), color_scheme)


# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models

class Migration(SchemaMigration):

    def forwards(self, orm):
        # Adding field 'Banner.disabled'
        db.add_column(u'mx_banner', 'disabled',
                      self.gf('django.db.models.fields.BooleanField')(default=False),
                      keep_default=False)

    def backwards(self, orm):
        # Deleting field 'Banner.disabled'
        db.delete_column(u'mx_banner', 'disabled')

    models = {
        u'downtime.period': {
            'Meta': {'object_name': 'Period'},
            'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
            'end_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'start_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
        },
        u'mx.banner': {
            'Meta': {'object_name': 'Banner'},
            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
            'days': ('django.db.models.fields.PositiveIntegerField', [], {}),
            'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
            'message': ('django.db.models.fields.CharField', [], {'max_length': '140'}),
            'period': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['downtime.Period']"}),
            'title': ('django.db.models.fields.CharField', [], {'max_length': '25'})
        }
    }

    complete_apps = ['mx']


from __future__ import absolute_import

from mock import MagicMock, patch

from sentry.testutils.cases import RuleTestCase
from sentry.rules.actions.notify_event_service import NotifyEventServiceAction
from sentry.tasks.sentry_apps import notify_sentry_app

class NotifyEventServiceActionTest(RuleTestCase):
    rule_cls = NotifyEventServiceAction

    def test_applies_correctly_for_plugins(self):
        event = self.get_event()

        plugin = MagicMock()
        plugin.is_enabled.return_value = True
        plugin.should_notify.return_value = True

        rule = self.get_rule(data={"service": "mail"})

        with patch("sentry.plugins.plugins.get") as get_plugin:
            get_plugin.return_value = plugin

            results = list(rule.after(event=event, state=self.get_state()))

        assert len(results) is 1
        assert plugin.should_notify.call_count is 1
        assert results[0].callback is plugin.rule_notify

    def test_applies_correctly_for_sentry_apps(self):
        event = self.get_event()

        self.create_sentry_app(
            organization=event.organization, name="Test Application", is_alertable=True
        )

        rule = self.get_rule(data={"service": "test-application"})

        results = list(rule.after(event=event, state=self.get_state()))

        assert len(results) is 1
        assert results[0].callback is notify_sentry_app


import pytest
import torch as to
import torch.nn as nn
from functools import partial
from tqdm import tqdm

from pyrado.sampling.utils import gen_batches, gen_ordered_batches
from pyrado.utils.data_types import *
from pyrado.utils.functions import noisy_nonlin_fcn
from pyrado.utils.math import cosine_similarity, cov
from pyrado.environments.pysim.ball_on_beam import BallOnBeamSim
from pyrado.policies.dummy import DummyPolicy
from pyrado.sampling.rollout import rollout
from pyrado.sampling.step_sequence import StepSequence
from pyrado.utils.nn_layers import IndiNonlinLayer
from pyrado.utils.optimizers import GSS
from pyrado.utils.averaging import RunningExpDecayingAverage, RunningMemoryAverage
from pyrado.utils.standardizing import RunningStandardizer, Standardizer
from pyrado.utils.normalizing import RunningNormalizer, normalize

@pytest.mark.parametrize(
    'x, data_along_rows', [
        (np.random.rand(100, 4), True),
        (np.random.rand(4, 100), False)
    ], ids=['100_4', '4_100']
)
def test_cov(x, data_along_rows):
    rowvar = not data_along_rows
    cov_np = np.cov(x, rowvar=rowvar)
    cov_pyrado = cov(to.from_numpy(x), data_along_rows=data_along_rows).numpy()

    assert cov_pyrado.shape[0] == cov_pyrado.shape[1]
    if data_along_rows:
        assert cov_np.shape[0] == x.shape[1]
        assert cov_pyrado.shape[0] == x.shape[1]
    else:
        assert cov_np.shape[0] == x.shape[0]
        assert cov_pyrado.shape[0] == x.shape[0]
    assert np.allclose(cov_np, cov_pyrado)

@pytest.mark.parametrize(
    'env, expl_strat', [
        (BallOnBeamSim(dt=0.02, max_steps=100),
         DummyPolicy(BallOnBeamSim(dt=0.02, max_steps=100).spec)),
    ], ids=['bob_dummy']
)
def test_concat_rollouts(env, expl_strat):
    ro1 = rollout(env, expl_strat)
    ro2 = rollout(env, expl_strat)
    ro_cat = StepSequence.concat([ro1, ro2])
    assert isinstance(ro_cat, StepSequence)
    assert ro_cat.length == ro1.length + ro2.length

@pytest.mark.parametrize(
    'x, y', [
        (to.tensor([1., 2., 3.]), to.tensor([1., 2., 3.])),
        (to.tensor([1., 0., 1.]), to.tensor([1., 1e12, 1.])),
        (to.tensor([0., 0., 0.]), to.tensor([1., 2, 3.])),
        (to.tensor([1., 2., 3.]), to.tensor([2., 4., 6.])),
        (to.tensor([1., 2., 3.]), to.tensor([-1., -2., -3.])),
    ], ids=['same', 'similarity_1', 'similarity_0', 'colinear_scaled', 'colinear_opposite']
)
def test_cosine_similarity(x, y):
    # Only tested for vector inputs
    d_cos = cosine_similarity(x, y)
    assert isinstance(d_cos, to.Tensor)
    # The examples are chosen to result in 0, 1, or -1
    assert to.isclose(d_cos, to.tensor(0.)) or to.isclose(d_cos, to.tensor(1.)) or to.isclose(d_cos, to.tensor(-1.))

@pytest.mark.parametrize(
    'x, y', [
        ({'a': 1, 'b': 2}, {'c': 1, 'd': 4}),
        ({'a': 1, 'b': 2}, {'b': 3, 'd': 4}),
    ], ids=['disjoint', 'overlapping']
)
def test_merge_lod_var_dtype(x, y):
    z = merge_lod_var_dtype([x, y])
    assert z['a'] == 1
    if z['b'] == 2:  # disjoint
        assert z['c'] == 1
    elif z['b'] == 3:  # overlapping
        assert len(z) == 3
    else:
        assert False
    assert z['d'] == 4

@pytest.mark.parametrize(
    'batch_size, data_size', [
        (3, 30),
        (3, 29),
        (3, 28),
        (2, 2)
    ], ids=['division_mod0', 'division_mod1', 'division_mod2', 'edge_case']
)
def test_gen_ordered_batches(batch_size, data_size):
    from math import ceil

    generator = gen_batches(batch_size, data_size)
    unordered_batches = list(generator)
    assert len(unordered_batches) == ceil(data_size/batch_size)
    assert all(len(uob) <= batch_size for uob in unordered_batches)

    generator = gen_ordered_batches(batch_size, data_size)
    ordered_batches = list(generator)
    assert len(ordered_batches) == ceil(data_size/batch_size)
    assert all(len(ob) <= batch_size for ob in ordered_batches)
    # Check if each mini-batch is sorted
    assert all(all(ob[i] <= ob[i + 1] for i in range(len(ob) - 1)) for ob in ordered_batches)

@pytest.mark.parametrize('dtype', ['torch', 'numpy'], ids=['to', 'np'])
@pytest.mark.parametrize('axis', [0, 1], ids=['ax_0', 'ax_1'])
def test_normalize(dtype, axis):
    for _ in range(10):
        x = to.rand(5, 3) if dtype == 'torch' else np.random.rand(5, 3)
        x_norm = normalize(x, axis=axis, order=1)
        if isinstance(x_norm, to.Tensor):
            x_norm = x_norm.numpy()  # for easier checking with pytest.approx
        assert np.sum(x_norm, axis=axis) == pytest.approx(1.)

@pytest.mark.parametrize(
    'data_seq, axis', [
        ([np.array([1, 1, 2]), np.array([1, 6, 3]), np.array([1, 6, 3]), np.array([10, -20, 20])], 0),
        ([np.array([1, 1, 2]), np.array([1, 6, 3]), np.array([1, 6, 3]), np.array([10, -20, 20])], None),
        ([np.array([1, 1, 2, 2]), np.array([1, 6, 3]), np.array([1, 6, 3]), np.array([10, 10, -20, 20])], 0),
        ([np.array([1, 1, 2, 2]), np.array([1, 6, 3]), np.array([1, 6, 3]), np.array([10, 10, -20, 20])], None),
        (
            [to.tensor([1., 1., 2]), to.tensor([1., 6., 3.]), to.tensor([1., 6., 3.]),
             to.tensor([10., -20., 20.])],
            0),
        (
            [to.tensor([1., 1., 2]), to.tensor([1., 6., 3.]), to.tensor([1., 6., 3.]),
             to.tensor([10., -20., 20.])],
            -1),
        (
            [to.tensor([1., 1, 2, 2]), to.tensor([1., 6, 3]), to.tensor([1., 6, 3]),
             to.tensor([10., 10, -20, 20])],
            0),
        (
            [to.tensor([1., 1, 2, 2]), to.tensor([1., 6, 3]), to.tensor([1., 6, 3]),
             to.tensor([10., 10, -20, 20])],
            -1),
    ], ids=['np_same_length_0', 'np_same_length_None', 'np_mixed_length_0', 'np_mixed_length_None',
            'to_same_length_0', 'to_same_length_-1', 'to_mixed_length_0', 'to_mixed_length_-1']
)
def test_running_standardizer(data_seq, axis):
    rs = RunningStandardizer()
    for data in data_seq:
        z = rs(data, axis)
        assert z is not None
    rs.reset()
    assert rs._mean is None and rs._sum_sq_diffs is None and rs._iter == 0

@pytest.mark.parametrize(
    'data_seq, alpha', [
        (
            [np.array([1, 1, 2]), np.array([1, 6, 3]), np.array([1, 6, 3]), np.array([10, -20, 20])],
            0.9
        ),
        (
            [to.tensor([1., 1., 2]), to.tensor([1., 6., 3.]), to.tensor([1., 6., 3.]), to.tensor([10., -20., 20.])],
            0.1
        ),
    ], ids=['np', 'to']
)
def test_running_expdecay_average(data_seq, alpha):
    reda = RunningExpDecayingAverage(alpha)
    for data in data_seq:
        z = reda(data)
        assert z is not None
    reda.reset(alpha=0.5)
    assert reda._alpha == 0.5 and reda._prev_est is None

@pytest.mark.parametrize(
    'data_seq, capacity', [
        (
            [np.array([1., 1, 2]), np.array([1., 1, 2]), np.array([1., 1, 2]), np.array([-2., -2, -4])],
            3
        ),
        (
            [to.tensor([1., 1, 2]), to.tensor([1., 1, 2]), to.tensor([1., 1, 2]), to.tensor([-2., -2, -4])],
            3
        ),
    ], ids=['np', 'to']
)
def test_running_mem_average(data_seq, capacity):
    rma = RunningMemoryAverage(capacity)
    for i, data in enumerate(data_seq):
        z = rma(data)
        if i <= 2:
            to.testing.assert_allclose(z, to.tensor([1., 1, 2]))  # works with PyTorch Tensors and numpy arrays
        elif i == 3:
            to.testing.assert_allclose(z, to.tensor([0., 0, 0]))  # works with PyTorch Tensors and numpy arrays
    rma.reset(capacity=5)
    assert rma.capacity == 5 and rma.memory is None

@pytest.mark.parametrize(
    'data_seq', [
        [5*np.random.rand(25, 3), 0.1*np.random.rand(5, 3), 20*np.random.rand(70, 3)],
        [5*to.rand(25, 3), 0.1*to.rand(5, 3), 20*to.rand(70, 3)]
    ], ids=['np', 'to']
)
def test_running_normalizer(data_seq):
    rn = RunningNormalizer()
    for data in data_seq:
        data_norm = rn(data)
        assert (-1 <= data_norm).all()
        assert (data_norm <= 1).all()

@pytest.mark.parametrize(
    'x', [
        to.rand(1000, 1),
        to.rand(1, 1000),
        to.rand(1000, 1000),
        np.random.rand(1, 1000),
        np.random.rand(1000, 1),
        np.random.rand(1000, 1000)
    ], ids=['to_1x1000', 'to_1000x1', 'to_1000x1000', 'np_1x1000', 'np_1000x1', 'np_1000x1000']
)
def test_stateful_standardizer(x):
    ss = Standardizer()

    if isinstance(x, to.Tensor):
        x_stdized = ss.standardize(x)
        assert x_stdized.shape == x.shape
        assert to.allclose(x_stdized.mean(), to.zeros(1))
        assert to.allclose(x_stdized.std(), to.ones(1))

        x_restrd = ss.unstandardize(x_stdized)
        assert x_restrd.shape == x.shape
        assert to.allclose(x_restrd, x, rtol=1e-02, atol=1e-05)

    elif isinstance(x, np.ndarray):
        x_stdized = ss.standardize(x)
        assert x_stdized.shape == x.shape
        assert np.allclose(x_stdized.mean(), np.zeros(1))
        assert np.allclose(x_stdized.std(), np.ones(1))

        x_restrd = ss.unstandardize(x_stdized)
        assert x_restrd.shape == x.shape
        assert np.allclose(x_restrd, x, rtol=1e-02, atol=1e-05)

@pytest.mark.parametrize(
    'g, ed', [
        (1., 2.),
        (np.array([-1., 2.]), np.eye(2))
    ], ids=['scalar', 'array']
)
def test_ds_spec(g, ed):
    # Base class
    dss = DSSpec(function='name', goal=g)
    assert isinstance(dss, dict)
    assert dss['function'] == 'name'
    if isinstance(g, np.ndarray):
        assert np.all(dss['goal'] == g)
    else:
        assert dss['goal'] == g

    # Linear first order subclass
    lds = LinDSSpec(function='lin', goal=g, errorDynamics=ed)
    assert isinstance(dss, dict)
    assert lds['function'] == 'lin'
    if isinstance(g, np.ndarray):
        assert np.all(lds['goal'] == g)
        assert np.all(lds['errorDynamics'] == ed)
    else:
        assert lds['goal'] == g
        assert lds['errorDynamics'] == ed

    # Mass-Spring-Damper subclass
    msds = MSDDSSpec(function='msd', goal=g, damping=2., attractorStiffness=3., mass=4.)
    assert isinstance(dss, dict)
    assert msds['function'] == 'msd'
    if isinstance(g, np.ndarray):
        assert np.all(msds['goal'] == g)
    else:
        assert msds['goal'] == g
    assert msds['damping'] == 2.
    assert msds['attractorStiffness'] == 3.
    assert msds['mass'] == 4.

@pytest.mark.optim
@pytest.mark.visualization
@pytest.mark.parametrize(
    'identical_bounds', [
        True, False
    ], ids=['identical', 'separate']
)
def test_gss_optimizer_identical_bounds(identical_bounds):
    class Dummy:
        def loss_fcn(self):
            # Some function to minimize
            return (self.x + self.y + 4)**2

        def __init__(self):
            # Test with different lower and upper bounds
            self.x, self.y = to.tensor([0.]), to.tensor([4.])
            x_min, x_max = to.tensor([-10.]), to.tensor([5.])
            if identical_bounds:
                self.optim = GSS([{'params': self.x}, {'params': self.y}], x_min, x_max)
            else:
                x_min_override = to.tensor([-6.])
                self.optim = GSS([{'params': self.x, 'param_min': x_min_override}, {'params': self.y}], x_min, x_max)
            print(self.optim)

    dummy = Dummy()

    for i in range(2):
        dummy.optim.step(dummy.loss_fcn)
    assert dummy.x != dummy.y
    print(f'x = {dummy.x.item()} \t y = {dummy.y.item()}')

@pytest.mark.optim
def test_gss_optimizer_functional():
    class Dummy:
        def loss_fcn(self):
            # Some function to minimize
            return (self.x + 4)**2

        def __init__(self):
            # Test with different lower and upper bounds
            self.x = to.tensor([0.])
            x_min, x_max = to.tensor([-10.]), to.tensor([10.])
            self.optim = GSS([{'params': self.x}], x_min, x_max)

    dummy = Dummy()

    for i in range(100):
        dummy.optim.step(dummy.loss_fcn)
    assert to.norm(dummy.x + 4) < 1e-4

@pytest.mark.optim
@pytest.mark.visualization
def test_gss_optimizer_nlin_fcn():
    from matplotlib import pyplot as plt
    # Parameters
    x_grid = to.linspace(-2., 3., 200)
    f = 1.
    noise_std = 0.1

    # Init param and optimizer
    x_init = to.rand(1)*(x_grid.max() - x_grid.min())/2 + x_grid.min() + (x_grid.max() - x_grid.min())/4  # [.25, .75]
    x = nn.Parameter(to.tensor([x_init]), requires_grad=False)
    optim = GSS([x], param_min=x_grid.min().unsqueeze(0), param_max=x_grid.max().unsqueeze(0))
    obj_fcn = partial(noisy_nonlin_fcn, x=x, f=f, noise_std=noise_std)
    num_epochs = 10

    # Init plotting
    fig = plt.figure()
    plt.plot(x_grid, noisy_nonlin_fcn(x=x_grid, f=f), label='noise free fcn')
    plt.scatter(x.data.numpy(), obj_fcn().numpy(), s=40, marker='x', color='k', label='init guess')
    colors = plt.get_cmap('inferno')(np.linspace(0, 1, num_epochs))

    for e in tqdm(range(num_epochs), total=num_epochs):
        # Evaluate at a the current point
        optim.step(obj_fcn)

        # Plot current evaluation
        plt.plot(x_grid, noisy_nonlin_fcn(x=x_grid, f=f, noise_std=noise_std), alpha=0.2)
        plt.scatter(x.data.numpy(), obj_fcn().numpy(), s=16, color=colors[e])

    plt.xlabel('$x$')
    plt.ylabel('$f(x)$')
    plt.legend()
    plt.show()
    assert noisy_nonlin_fcn(x, f=f, noise_std=noise_std) < noisy_nonlin_fcn(x_init, f=f, noise_std=noise_std)

@pytest.mark.parametrize('in_features', [1, 3], ids=['1dim', '3dim'])
@pytest.mark.parametrize('same_nonlin', [True, False], ids=['same_nonlin', 'different_nonlin'])
@pytest.mark.parametrize('bias', [True, False], ids=['bias', 'no_bias'])
@pytest.mark.parametrize('weight', [True, False], ids=['weight', 'no_weight'])
def test_indi_nonlin_layer(in_features, same_nonlin, bias, weight):
    if not same_nonlin and in_features > 1:
        nonlin = in_features*[to.tanh]
    else:
        nonlin = to.sigmoid
    layer = IndiNonlinLayer(in_features, nonlin, bias, weight)
    assert isinstance(layer, nn.Module)

    i = to.randn(in_features)
    o = layer(i)
    assert isinstance(o, to.Tensor)
    assert i.shape == o.shape