# Source code for sagenb.misc.misc

    # -*- coding: utf-8 -*-
    """
    Miscellaneous Notebook Functions

    TESTS:

    Check that github issue #195 is fixed::

        sage: from sagenb.misc.misc import mathjax_macros
        sage: type(mathjax_macros)
        <type 'list'>

    """

    #############################################################################
    #       Copyright (C) 2006, 2007 William Stein <wstein@gmail.com>
    #  Distributed under the terms of the GNU General Public License (GPL)
    #  The full text of the GPL is available at:
    #                  http://www.gnu.org/licenses/
    #############################################################################
    import sys
    from six import text_type, binary_type
    from pkg_resources import resource_filename

    PYTHON_VERSION = sys.version[0]

    if PYTHON_VERSION == '3':
        CRE = ConnectionRefusedError
    else:
        CRE = tuple

    def stub(f):
        def g(*args, **kwds):
            print("Stub: {}".format(f.func_name))
            return f(*args, **kwds)
        return g

    min_password_length = 6

    import os
    import socket
    import sys

    try:
        import cPickle as pickle
    except ImportError:
        import pickle

    def print_open_msg(address, port, secure=False, path=""):
        """
        Print a message on the screen suggesting that the user open their
        web browser to a certain URL.

        INPUT:

        - ``address`` -- a string; a computer address or name

        - ``port`` -- an int; a port number

        - ``secure`` -- a bool (default: False); whether to prefix the URL
          with 'http' or 'https'

        - ``path`` -- a string; the URL's path following the port.

        EXAMPLES::

            sage: from sagenb.misc.misc import print_open_msg
            sage: print_open_msg('localhost', 8080, True)
            ┌──────────────────────────────────────────────────┐
            │                                                  │
            │ Open your web browser to https://localhost:8080  │
            │                                                  │
            └──────────────────────────────────────────────────┘
            sage: print_open_msg('sagemath.org', 8080, False)
            ┌────────────────────────────────────────────────────┐
            │                                                    │
            │ Open your web browser to http://sagemath.org:8080  │
            │                                                    │
            └────────────────────────────────────────────────────┘
            sage: print_open_msg('sagemath.org', 90, False)
            ┌──────────────────────────────────────────────────┐
            │                                                  │
            │ Open your web browser to http://sagemath.org:90  │
            │                                                  │
            └──────────────────────────────────────────────────┘
            sage: print_open_msg('sagemath.org', 80, False)
            ┌────────────────────────────────────────────────┐
            │                                                │
            │  Open your web browser to http://sagemath.org  │
            │                                                │
            └────────────────────────────────────────────────┘
        """
        if port == 80:
            port = ''
        else:
            port = ':%s'%port
        s = "Open your web browser to http%s://%s%s%s"%('s' if secure else '', address, port, path)
        t = len(s)
        if t%2:
            t += 1
            s += ' '
        n = max(t+4, 50)
        k = n - t  - 1
        j = k // 2
        msg = '┌' + '─' * (n - 2) + '┐\n'
        msg += '│' + ' ' * (n - 2) + '│\n'
        msg += '│' + ' ' * j + s + ' ' * j + '│\n'
        msg += '│' + ' ' * (n - 2) + '│\n'
        msg += '└' + '─' * (n - 2) + '┘'
        print(msg)

    def find_next_available_port(interface, start, max_tries=100, verbose=False):
        """
        Find the next available port at a given interface, that is, a port for
        which a current connection attempt returns a 'Connection refused'
        error message.  If no port is found, raise a RuntimeError exception.

        INPUT:

        - ``interface`` - address to check

        - ``start`` - an int; the starting port number for the scan

        - ``max_tries`` - an int (default: 100); how many ports to scan

        - ``verbose`` - a bool (default: True); whether to print information
          about the scan

        OUTPUT:

        - an int - the port number

        EXAMPLES::

            sage: from sagenb.misc.misc import find_next_available_port
            sage: find_next_available_port('127.0.0.1', 9000, verbose=False)   # random output -- depends on network
            9002
        """
        from signal import alarm

        alarm_count = 0
        for port in range(start, start+max_tries+1):
            try:
                try:
                    alarm(5)
                    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    s.connect((interface, port))
                finally:
                    alarm(0)  # cancel alarm
            except socket.error as msg:
                if ((PYTHON_VERSION == '2' and msg[1] == 'Connection refused')
                        or (PYTHON_VERSION == '3' and isinstance(msg, CRE))):
                    if verbose:
                        print("Using port = %s" % port)
                    return port
            except KeyboardInterrupt:
                if verbose:
                    print("alarm")
                alarm_count += 1
                if alarm_count >= 10:
                     break
            if verbose:
                print("Port %s is already in use." % port)
                print("Trying next port...")
        raise RuntimeError("no available port.")

    def open_page(address, port, secure, path=""):
        if secure:
            rsrc = 'https'
        else:
            rsrc = 'http'

        os.system('%s %s://%s:%s%s 1>&2 > /dev/null &'%(browser(), rsrc, address, port, path))

    def pad_zeros(s, size=3):
        """
        EXAMPLES::

            sage: pad_zeros(100)
            '100'
            sage: pad_zeros(10)
            '010'
            sage: pad_zeros(10, 5)
            '00010'
            sage: pad_zeros(389, 5)
            '00389'
            sage: pad_zeros(389, 10)
            '0000000389'
        """
        return "0"*(size-len(str(s))) + str(s)

    SAGENB_ROOT = os.path.split(resource_filename(__name__, ''))[0]

    DATA = os.path.join(SAGENB_ROOT, 'data')

    if 'DOT_SAGENB' in os.environ:
        DOT_SAGENB = os.environ['DOT_SAGENB']
    elif 'DOT_SAGE' in os.environ:
        DOT_SAGENB = os.environ['DOT_SAGE']
    else:
        DOT_SAGENB = os.path.join(os.environ['HOME'], '.sagenb')

    try:
        from sage.env import SAGE_URL
    except ImportError:
        SAGE_URL = 'http://sagemath.org'

    # TODO: Get macros from server and user settings.
    try:
        import sage.all
        from sage.misc.latex_macros import sage_mathjax_macros
        mathjax_macros = sage_mathjax_macros()
    except ImportError:
        mathjax_macros = [
            "ZZ : '{\\\\Bold{Z}}'",
            "RR : '{\\\\Bold{R}}'",
            "CC : '{\\\\Bold{C}}'",
            "QQ : '{\\\\Bold{Q}}'",
            "QQbar : '{\\\\overline{\\\\QQ}}'",
            "GF : ['{\\\\Bold{F}_{#1}}', 1]",
            "Zp : ['{\\\\ZZ_{#1}}', 1]",
            "Qp : ['{\\\\QQ_{#1}}', 1]",
            "Zmod : ['{\\\\ZZ/#1\\\\ZZ}', 1]",
            "CIF : '{\\\\Bold{C}}'",
            "CLF : '{\\\\Bold{C}}'",
            "RDF : '{\\\\Bold{R}}'",
            "RIF : '{\\\\Bold{I} \\\\Bold{R}}'",
            "RLF : '{\\\\Bold{R}}'",
            "CFF : '{\\\\Bold{CFF}}'",
            "Bold : ['{\\\\mathbf{#1}}', 1]"
            ]
    except Exception:
        sage_mathjax_macros_easy = []
        raise
    try:
        from sage.misc.session import init as session_init
    except ImportError:
        @stub
        def session_init(*args, **kwds):
            pass

    try:
        from sage.misc.sage_eval import sage_eval
    except ImportError:
        def sage_eval(value, globs):
            # worry about ^ and preparser -- this gets used in interact.py,
            # which is a bit weird, but heh.
            return eval(value, globs)

    try:
        from sage.misc.package import is_package_installed
    except ImportError:
        def is_package_installed(name, *args, **kwds):
            return False

    try:
        from sage.misc.viewer import browser
    except ImportError:
        @stub
        def browser():
            return "open"

    try:
        from sage.structure.sage_object import loads, dumps, load, save
    except ImportError:
        loads = pickle.loads
        dumps = pickle.dumps
        def load(filename):
            return pickle.loads(open(filename).read())
        def save(obj, filename):
            s = pickle.dumps(obj, protocol=2)
            open(filename, 'wb').write(s)

    try:
        from sage.misc.all import verbose
    except ImportError:
        # TODO!
        @stub
        def verbose(*args, **kwds):
            pass

    ################################
    # clocks -- easy to implement
    ################################
    import time, resource

    def cputime(t=0):
        try:
            t = float(t)
        except TypeError:
            t = 0.0
        u, s = resource.getrusage(resource.RUSAGE_SELF)[:2]
        return u + s - t

    def walltime(t=0):
        return time.time() - t

    def word_wrap(s, ncols=85):
        t = []
        if ncols == 0:
            return s
        for x in s.split('\n'):
            if len(x) == 0 or x.lstrip()[:5] == 'sage:':
                t.append(x)
                continue
            while len(x) > ncols:
                k = ncols
                while k > 0 and x[k] != ' ':
                    k -= 1
                if k == 0:
                    k = ncols
                    end = '\\'
                else:
                    end = ''
                t.append(x[:k] + end)
                x = x[k:]
                k=0
                while k < len(x) and x[k] == ' ':
                    k += 1
                x = x[k:]
            t.append(x)
        return '\n'.join(t)

    try:
        from sage.repl.preparse import strip_string_literals
    except ImportError:
        def strip_string_literals(code, state=None):
            # todo -- do we need this?
            return code

    try:
        from pkg_resources import Requirement, working_set
        SAGENB_VERSION = working_set.find(Requirement.parse('sagenb')).version
    except AttributeError:
        SAGENB_VERSION = ""

    try:
        import sage.version
        SAGE_VERSION=sage.version.version
    except ImportError:
        SAGE_VERSION=""

    try:
        from sage.plot.colors import Color
    except ImportError:
        class Color:
            def __init__(self, *args, **kwds):
                pass

    ########################################
    # this is needed for @interact:
    # Color, sage_eval and is_Matrix
    # are imported from here in notebook/interact.py
    ########################################

    def is_Matrix(x):
        try:
            from sage.structure.element import is_Matrix
        except ImportError:
            return False
        return is_Matrix(x)

    def register_with_cleaner(pid):
        try:
            import sage.interfaces.cleaner
            sage.interfaces.cleaner.cleaner(pid)  # register pid of forked process with cleaner
        except ImportError:
            print("generic cleaner needs to be written")

    try:
        from sage.misc.all import tmp_filename, tmp_dir
    except ImportError:
        def tmp_filename(name='tmp'):
            # We use mktemp instead of mkstemp since the semantics of the
            # tmp_filename function simply don't allow for what mkstemp
            # provides.
            import tempfile
            return tempfile.mktemp()

        def tmp_dir(name='dir'):
            import tempfile
            return tempfile.mkdtemp()

    try:
        from sage.misc.inline_fortran import InlineFortran
    except ImportError:
        @stub
        def InlineFortran(*args, **kwds):
            pass

    try:
        from sage.misc.cython import cython
    except ImportError:
        @stub
        def cython(*args, **kwds):
            # TODO
            raise NotImplementedError("Curently %cython mode requires Sage.")

    #############################################################
    # File permissions
    # May need some changes on Windows.
    #############################################################
    import stat

    def set_restrictive_permissions(filename, allow_execute=False):
        x = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
        if allow_execute:
            x = x | stat.S_IXGRP |  stat.S_IXOTH
        os.chmod(filename, x)

    def set_permissive_permissions(filename):
        os.chmod(filename, stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH | \
                 stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | \
                 stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP)

    def encoded_str(obj, encoding='utf-8'):
        r"""
        Takes an object and returns an encoded str human-readable representation.

        string to bytes

        EXAMPLES::

            sage: from sagenb.misc.misc import encoded_str
            sage: encoded_str(u'\u011b\u0161\u010d\u0159\u017e\xfd\xe1\xed\xe9\u010f\u010e') == 'ěščřžýáíéďĎ'
            True
            sage: encoded_str(u'abc')
            'abc'
            sage: encoded_str(123)
            '123'
        """
        if isinstance(obj, text_type):
            return obj.encode(encoding, 'ignore')
        return binary_type(obj)

    def unicode_str(obj, encoding='utf-8'):
        r"""
        Takes an object and returns a unicode human-readable representation.

        (bytes or string) to string

        EXAMPLES::

            sage: from sagenb.misc.misc import unicode_str
            sage: unicode_str('ěščřžýáíéďĎ') == u'\u011b\u0161\u010d\u0159\u017e\xfd\xe1\xed\xe9\u010f\u010e'
            True
            sage: unicode_str('abc')
            u'abc'
            sage: unicode_str(123)
            u'123'
        """
        if isinstance(obj, binary_type):
            return obj.decode(encoding, 'ignore')
        elif isinstance(obj, text_type):
            return obj
        return text_type(obj)

    def ignore_nonexistent_files(curdir, dirlist):
        """
        Returns a list of non-existent files, given a directory and its
        contents.  The returned list includes broken symbolic links.  Use
        this, e.g., with :func:`shutil.copytree`, as shown below.

        INPUT:

        - ``curdir`` - a string; the name of the current directory

        - ``dirlist`` - a list of strings; names of ``curdir``'s contents

        OUTPUT:

        - a list of strings; names of ``curdir``'s non-existent files

        EXAMPLES::

            sage: import os, shutil
            sage: from sagenb.misc.misc import ignore_nonexistent_files
            sage: opj = os.path.join; ope = os.path.exists; t = tmp_dir()
            sage: s = opj(t, 'src'); t = opj(t, 'trg'); hi = opj(s, 'hi.txt');
            sage: os.makedirs(s)
            sage: f = open(hi, 'w'); f.write('hi'); f.close()
            sage: os.symlink(hi, opj(s, 'good.txt'))
            sage: os.symlink(opj(s, 'bad'), opj(s, 'bad.txt'))
            sage: slist = sorted(os.listdir(s)); slist
            ['bad.txt', 'good.txt', 'hi.txt']
            sage: [ope(opj(s, x)) for x in slist]
            [False, True, True]
            sage: [os.path.islink(opj(s, x)) for x in slist]
            [True, True, False]
            sage: shutil.copytree(s, t)
            Traceback (most recent call last):
            ...
            Error: [('.../src/bad.txt', '.../trg/bad.txt', "[Errno 2] No such file or directory: '.../src/bad.txt'")]
            sage: shutil.rmtree(t); ope(t)
            False
            sage: shutil.copytree(s, t, ignore = ignore_nonexistent_files)
            sage: tlist = sorted(os.listdir(t)); tlist
            ['good.txt', 'hi.txt']
            sage: [ope(opj(t, x)) for x in tlist]
            [True, True]
            sage: [os.path.islink(opj(t, x)) for x in tlist]  # Note!
            [False, False]
        """
        ignore = []
        for x in dirlist:
            if not os.path.exists(os.path.join(curdir, x)):
                ignore.append(x)
        return ignore

    def translations_path():
        return os.path.join(SAGENB_ROOT, 'translations')

    def get_languages():
        from babel import Locale
        from babel.core import UnknownLocaleError
        langs = []
        dir_names = [lang for lang in os.listdir(translations_path())
                           if lang != 'en_US']
        for name in dir_names:
            try:
                Locale.parse(name)
            except UnknownLocaleError:
                pass
            else:
                langs.append(name)
        langs.sort()
        langs.insert(0, 'en_US')
        return langs

    def N_(message):
        return message
    def nN_(message_singular, message_plural):
        return [message_singular, message_plural]