# coding=utf-8

"""
    stream.stream
    ~~~~~~~~~~~~~

    Implement the 'Stream' class and `open` method.

    :copyright: (c) 2016 by Ali Ghaffaari.
    :license: MIT, see LICENSE for more details.
"""

import gzip

from google.protobuf.internal.decoder import _DecodeVarint as decodeVarint
from google.protobuf.internal.encoder import _EncodeVarint as encodeVarint

def parse(ifp, pb_cls, **kwargs):
    """Parse a stream.

    Args:
        ifp (string or file-like object): input stream.
        pb_cls (protobuf.message.Message.__class__): The class object of
            the protobuf message type encoded in the stream.
    """
    mode = 'rb'
    if isinstance(ifp, str):
        istream = open(ifp, mode=mode, **kwargs)
    else:
        istream = open(fileobj=ifp, mode=mode, **kwargs)
    with istream:
        for data in istream:
            pb_obj = pb_cls()
            pb_obj.ParseFromString(data)
            yield pb_obj

def dump(ofp, *pb_objs, **kwargs):
    """Write to a stream.

    Args:
        ofp (string or file-like object): output stream.
        pb_objs (*protobuf.message.Message): list of protobuf message objects
            to be written.
    """
    mode = 'wb'
    if isinstance(ofp, str):
        ostream = open(ofp, mode=mode, **kwargs)
    else:
        ostream = open(fileobj=ofp, mode=mode, **kwargs)
    with ostream:
        ostream.write(*pb_objs)

def open(filename=None, mode='rb',  # pylint: disable=redefined-builtin
         **kwargs):
    """Open an stream."""
    return Stream(filename, mode, **kwargs)

class Stream(object):
    """Stream class.

    Read and write protocol buffer streams encoded by 'stream' library. Stream
    objects instantiated for reading by setting mode to 'rb' (input `Stream`s)
    are iterable. So, protobuf objects can be obtained by iterating over the
    Stream. Stream iterator yields protobuf encoded data, so it should be
    parsed by using proper methods in Google Protocol Buffer library (for
    example `ParseFromString()` method).

    In output `Stream`s (those are instantiated with 'w' mode), method
    `write()` groups the given list of protobuf objects and writes them into
    the stream in the same format (refer to the stream library documentation
    for further information).

    The stream should be closed after performing all stream operations. Streams
    can be also used by `with` statement just like files.

    Attributes:
        _fd:            file object.
        _myfd:          file object to be closed (owned).
        _buffer_size:   size of the buffer to write as a one group of messages
                        (write-mode only).
        _write_buff:    list of buffered messages for writing (write-mode only)
        _group_delim:   if true it returns an instance of the `_delimiter`
                        class after reading each group of messages to indicate
                        a group change (read-mode only).
        _delimiter:     the delimiter class (read-mode only).
    """
    def __init__(self, filename=None, mode='rb', fileobj=None, **kwargs):
        """Constructor for the Stream class.

        Args:
            filename (string): Path of the working file.
            mode (string): The mode argument can be any of 'r', 'rb', 'a',
                'ab', 'w', or 'wb', depending on whether the file will be read
                or written. The default is 'rb'.
            fileobj (file-like object): input/output stream object.

        Keyword args:
            buffer_size (int): Write buffer size. The objects will be buffered
                before writing. No buffering will be made if buffer_size is 0.
                It means that size of the group will be determined by the size
                of object list provided on `write` call. Setting `buffer_size`
                to -1 means infinite buffer size. Method `flush` should be
                called manually or by closing stream. All objects will be write
                in one group upon `flush` or `close` events.
            group_delimiter (boolean): indicate the end of a message group if
                True by yielding a delimiter after reading each group.
            delimiter_cls (class): delimiter class.
            gzip (bool): Whether or not to use gzip compression on the given
                file. (default is True)
        """
        self._myfd = None
        if fileobj is None:
            if kwargs.get('gzip', True):
                self._fd = gzip.open(filename, mode)
            else:
                import builtins
                self._fd = builtins.open(filename, mode)
            self._myfd = self._fd
        else:
            self._fd = fileobj
        if not mode.startswith('r'):
            self._buffer_size = kwargs.pop('buffer_size', 0)
            self._write_buff = []
        else:
            self._group_delim = kwargs.pop('group_delimiter', False)
            self._delimiter = kwargs.pop('delimiter_cls', None)

    def __enter__(self):
        """Enter the runtime context related to Stream class. It will be
        automatically run by `with` statement.
        """
        return self

    def __exit__(self, *args):
        """Exit the runtime context related to Stream class. It will be
        automatically run by `with` statement. It closes the stream.
        """
        self.close()

    def __iter__(self):
        """Return the iterator object of the stream."""
        return self._get_objs()

    def _read_varint(self):
        """Read a varint from file, parse it, and return the decoded integer.
        """
        buff = self._fd.read(1)
        if buff == b'':
            return 0

        while (bytearray(buff)[-1] & 0x80) >> 7 == 1:  # while the MSB is 1
            new_byte = self._fd.read(1)
            if new_byte == b'':
                raise EOFError('unexpected EOF.')
            buff += new_byte

        varint, _ = decodeVarint(buff, 0)

        return varint

    def _get_objs(self):
        """A generator yielding all protobuf object data in the file. It is the
        main parser of the stream encoding.
        """
        while True:
            count = self._read_varint()
            if count == 0:
                break
            # Read a group containing `count` number of objects.
            for _ in range(count):
                size = self._read_varint()
                if size == 0:
                    raise EOFError('unexpected EOF.')
                # Read an object from the object group.
                yield self._fd.read(size)

            if self._group_delim:
                yield self._delimiter() if self._delimiter is not None \
                                        else None

    def is_output(self):
        """Check whether the stream is output stream or not."""
        if hasattr(self, '_write_buff'):
            return True
        return False

    def close(self):
        """Close the stream."""
        self.flush()
        if self._myfd is not None:
            self._myfd.close()
            self._myfd = None

    def write(self, *pb2_obj):
        """Write a group of one or more protobuf objects to the file. Multiple
        object groups can be written by calling this method several times
        before closing stream or exiting the runtime context.

        The input protobuf objects get buffered and will be written down when
        the number of buffered objects exceed the `self._buffer_size`.

        Args:
            pb2_obj (*protobuf.message.Message): list of protobuf messages.
        """
        base = len(self._write_buff)

        for idx, obj in enumerate(pb2_obj):
            if self._buffer_size > 0 and \
                    (idx + base) != 0 and \
                    (idx + base) % self._buffer_size == 0:
                self.flush()
            self._write_buff.append(obj)

        if self._buffer_size == 0:
            self.flush()

    def flush(self):
        """Write down buffer to the file."""
        if not self.is_output():
            return

        count = len(self._write_buff)
        if count == 0:
            return

        encodeVarint(self._fd.write, count, True)

        for obj in self._write_buff:
            obj_str = obj.SerializeToString()
            encodeVarint(self._fd.write, len(obj_str), True)
            self._fd.write(obj_str)

        self._write_buff = []


#! /usr/bin/env python3
"""

Utility for building a map using installed Source SDK tools.
Call with -h or --help to see usage information.

Examples:

  # Creates/installs/runs .bsp in same dir
  python buildbsp.py --game tf2 mymap.vmf

  # Creates/installs .bsp but does not run
  python buildbsp.py --game css --no-run mymap.vmf

  # Only create .bsp, and use fast config
  python buildbsp.py --game tf2 --no-run --no-install --fast mymap.vmf

"""
import argparse
import sys
import os
import subprocess
import webbrowser
import urllib.parse
import shutil

class Game:
    def __init__(self, id, dir, common, uses_sdk):
        self.id = id              # Numeric Steam catalog ID number
        self.dir = dir            # Path to inner game directory (containing gameinfo.txt)
        self.common = common      # Game lives under "common" rather than "<username>"
        self.uses_sdk = uses_sdk  # False if game ships with its own map compilers
    def get_game_dir(self, username=False):
        """Returns joined game directory path relative to Steamapps"""
        if not self.common and not username:
            raise RuntimeError("Can't determine this game's directory without username")
        if self.common:
            subdir = "common"
        else:
            subdir = "username"
        subsubdir = self.dir
        if WIN32 or CYGWIN:
            subsubdir = subsubdir.lower()
        return os.path.join(subdir, subsubdir)

WIN32 = sys.platform.startswith('win32')
CYGWIN = sys.platform.startswith('cygwin')
LINUX = sys.platform.startswith('linux')
DARWIN = False  # Not supported yet
GAMES = {
    'tf2': Game(440, os.path.join("Team Fortress 2", "tf"), True, False),
    'css': Game(240, os.path.join("Counter-Strike Source", "cstrike"), False, False),
    'hl2': Game(220, os.path.join("Half-Life 2", "hl2"), False, True),
    'hl2mp': Game(320, os.path.join("Half-Life 2 Deathmatch", "hl2mp"), False, False),
    'gm': Game(4000, os.path.join("GarrysMod", "garrysmod"), False, True),
}

def _make_arg_parser():
    parser = argparse.ArgumentParser(description='Build, install, and test a VMF map.')
    parser.add_argument('map')
    parser.add_argument('-g', '--game', default='tf2', choices=GAMES.keys(),
        help="selects which game to use")
    parser.add_argument('--no-run', action="store_true",
        help="don't run the game after building/installing")
    parser.add_argument('--no-install', action="store_true",
        help="don't install (or run) the map after building")
    parser.add_argument('-f', '--fast', action="store_true",
        help="enable fast compile options")
    parser.add_argument('--hdr', action="store_true",
        help="enable full HDR compile")
    parser.add_argument('--final', action="store_true",
        help="use with --hdr for slow high-quality HDR compile")
    parser.add_argument('--steam-windows-path',
        help="path to your (Windows) Steam folder (for games not dependent on SDK)")
    parser.add_argument('--username',
        help="your Steam username (needed for some games)")

    return parser

def main():
    parser = _make_arg_parser()
    args = parser.parse_args()
    game = GAMES[args.game]
    username = args.username  # May be None
    vmf_file = os.path.abspath(args.map)
    path, filename = os.path.split(vmf_file)
    mapname = filename[:-4]
    mappath = os.path.join(path, mapname)
    bsp_file = os.path.join(path, mapname + ".bsp")
    sourcesdk = None
    winsteam = args.steam_windows_path
    if not winsteam:
        winsteam = os.getenv('winsteam')

    # We need to find out where the SteamApps directory is.
    if winsteam:
        steamapps = os.path.join(winsteam, "Steamapps")
        if not os.path.isdir(steamapps):  # Try lowercase
            steamapps = os.path.join(winsteam, "steamapps")
        if not os.path.isdir(steamapps):
            raise Exception(
                "The provided Steam directory does not contain a Steamapps directory: %s" %
                os.path.abspath(winsteam)
            )
    elif WIN32 or CYGWIN:
        sourcesdk = os.getenv('sourcesdk')
        if CYGWIN:
            def cygwin2dos(path):
                return subprocess.check_output(["cygpath", '-w', '%s' % path], universal_newlines=True).strip()
            sourcesdk = subprocess.check_output(["cygpath", sourcesdk], universal_newlines=True).strip()
        sourcesdk = os.path.abspath(sourcesdk)
        steamapps = os.path.dirname(os.path.dirname(sourcesdk))
        if not os.path.isdir(steamapps):
            raise Exception("Steamapps directory could not be found. Please specify using --steam-windows-path or see --help.")
        if not username:
            username = os.path.basename(os.path.dirname(sourcesdk))
    else:
        raise Exception("Unable to determine where your (Windows) Steam installation is located. See --help.")
    steamapps = os.path.abspath(steamapps)

    # Prepare some useful paths
    gamedir = os.path.join(steamapps, game.get_game_dir(username))
    mapsdir = os.path.join(gamedir, "maps")

    # Get path to correct bin tools directory (game or SDK)
    if game.uses_sdk:
        if not sourcesdk:
            # Try finding SDK within Steamapps
            # TODO
            raise Exception("Sorry, SDK games aren't implemented right now unless you're on Windows.")
        toolsdir = os.path.join(sourcesdk, "bin", "orangebox", "bin")
    else:
        toolsdir = os.path.abspath(os.path.join(gamedir, "..", "bin"))

    # Make sure gamedir path seems legit
    if not os.path.isfile(os.path.join(gamedir, "gameinfo.txt")):
        raise Exception("Game directory does not contain a gameinfo.txt: %s" % gamedir)

    if WIN32 or CYGWIN:
        # Convert some paths if using Cygwin
        if CYGWIN:
            gamedir = cygwin2dos(gamedir)
            mappath = cygwin2dos(mappath)

        # Change working directory first because VBSP is dumb
        os.chdir(os.path.join(sourcesdk, 'bin', 'orangebox'))

        # Run the SDK tools
        vbsp_exe = os.path.join(toolsdir, "vbsp.exe")
        code = subprocess.call([vbsp_exe, '-game', gamedir, mappath])
        print("VBSP finished with status %s." % code)

        if code == 1:
            print("Looks like SteamService isn't working. Try reopening Steam.")
            exit(code)
        elif code == -11:
            print("Looks like you might have gotten the 'material not found' " +
                "error messages. Try signing into Steam, or restarting it " +
                "and signing in.")
            exit(code)
        elif code != 0:
            print("Looks like VBSP crashed, but I'm not sure why.")
            exit(code)

        vvis_exe = os.path.join(toolsdir, "vvis.exe")
        opts = [vvis_exe]
        if args.fast:
            opts.append('-fast')
        opts.extend(['-game', gamedir, mappath])
        subprocess.call(opts)

        vrad_exe = os.path.join(toolsdir, "vrad.exe")
        opts = [vrad_exe]
        if args.fast:
            opts.extend(['-bounce', '2', '-noextra'])
        if args.hdr:
            opts.append('-both')
        if args.hdr and args.final:
            opts.append('-final')
        opts.extend(['-game', gamedir, mappath])
        subprocess.call(opts)

        # Install the map to the game's map directory (unless --no-install)
        if not args.no_install:
            print("Copying map %s to %s" % (mapname, mapsdir))
            shutil.copy(bsp_file, mapsdir)
        else:
            print("Not installing map")

        # Launch the game (unless --no-run or --no-install)
        if not args.no_run and not args.no_install:
            params = urllib.parse.quote("-dev -console -allowdebug +map %s" % mapname)
            run_url = "steam://run/%d//%s" % (game['id'], params)
            print(run_url)
            webbrowser.open(run_url)
            if cygwin:
                print("\nYou're running cygwin, so I can't launch the game for you.")
                print("Double-click the URL above, right-click, and click 'Open'.")
                print("Or paste the URL above into the Windows 'Run...' dialog.")
                print("Or, just run 'map %s' in the in-game console." % mapname)
        else:
            print("Not launching game")
    elif LINUX:
        # Environment to use with wine calls
        env = os.environ.copy()
        env['WINEPREFIX'] = os.path.expanduser("~/.winesteam")

        # Define path-converting helper function
        def unix2wine(path):
            return subprocess.check_output(["winepath", '-w', '%s' % path], env=env).strip()

        # Wine-ify some of our paths
        gamedir = unix2wine(gamedir)
        mappath = unix2wine(mappath)

        # Tell wine to look for DLLs here
        #env['WINEDLLPATH'] = os.path.join(sourcesdk, "bin")

        #print("WINEDLLPATH is as follows: ", env['WINEDLLPATH'])

        # Use native maps directory instead of the Wine installation's
        mapsdir = os.path.join('~', '.steam', 'steam', 'SteamApps', game.get_game_dir(username), "maps")
        mapsdir = os.path.expanduser(mapsdir)

        # Change working directory first because VBSP is dumb
        #os.chdir(os.path.join(sourcesdk, 'bin', 'orangebox'))

        print("Using -game dir: %s" % gamedir)

        # We now need to set the VPROJECT env variable
        env['VPROJECT'] = gamedir

        # Run the SDK tools
        vbsp_exe = os.path.join(toolsdir, "vbsp.exe")
        code = subprocess.call(['wine', vbsp_exe, '-game', gamedir, mappath], env=env)
        print("VBSP finished with status %s." % code)

        # Handle various exit status codes VBPS may have returned
        if code == 1:
            print("\nLooks like VBSP crashed, possibly due to invalid geometry in the map. Check the output above.")
            print("\It could also be related to SteamService isn't working. Try re(launching) wine's Steam:")
            steambin = os.path.join(os.path.dirname(steamapps), 'steam.exe')
            print('\nWINEPREFIX="%s" wine "%s" -no-dwrite' % (env['WINEPREFIX'], steambin))
            exit(code)
        elif code == -11:
            print("\nLooks like you might have gotten the 'material not found' " +
                "error messages. Try signing into Steam, or restarting it " +
                "and signing in.")
            exit(code)
        elif code != 0:
            print("\nLooks like VBSP crashed, but I'm not sure why.")
            exit(code)

        vvis_exe = os.path.join(toolsdir, "vvis.exe")
        opts = ['wine', vvis_exe]
        if args.fast:
            opts.append('-fast')
        opts.extend(['-game', gamedir, mappath])
        code = subprocess.call(opts, env=env)

        if code != 0:
            print("\nLooks like VVIS crashed, but I'm not sure why.")
            exit(code)

        vrad_exe = os.path.join(toolsdir, "vrad.exe")
        opts = ['wine', vrad_exe]
        if args.fast:
            opts.extend(['-bounce', '2', '-noextra'])
        if args.hdr:
            opts.append('-both')
        if args.hdr and args.final:
            opts.append('-final')
        opts.extend(['-game', gamedir, mappath])
        code = subprocess.call(opts, env=env)

        if code != 0:
            print("\nLooks like VRAD crashed, but I'm not sure why.")
            exit(code)

        # Install the map to the game's map directory (unless --no-install)
        if not args.no_install:
            shutil.copy(bsp_file, mapsdir)
        else:
            print("Not installing map")

        # Launch the game (unless --no-run or --no-install)
        if not args.no_run and not args.no_install:
            params = urllib.parse.quote("-dev -console -allowdebug +map %s" % mapname)
            run_url = "steam://run/%d//%s" % (game.id, params)
            print(run_url)
            webbrowser.open(run_url)
        else:
            print("Not launching game")
    else:
        raise OSError('Your OS is not supported yet!')

if __name__ == '__main__':
    main()


from __future__ import print_function, absolute_import
import sys
if sys.version_info[0] < 3:
    from future_builtins import zip
import numpy as np
from scipy import signal
import time
import warnings
from ._tridiag_solvers import trisolve
from .utils import s2h

DTYPE = np.float64
EPS = np.finfo(DTYPE).eps

# threshold minimum step size for terminating backtracking linesearch
S_TOL = 1E-12

# factors by which step size and barrier are decreased on each iteration
S_FAC = 5.
Z_FAC = 10.

# threshold minimum value for L2 norm of d before terminating interior point
D_TOL = 5E-2

# threshold minimum barrier value for terminating interior point
Z_TOL = 1E-12

# joblib is an optional dependency, required only for processing multiple cells
# in parallel
try:
    from joblib import Parallel, delayed

    def apply_all_cells(F, n_jobs=-1, disp=1, *fnn_args, **fnn_kwargs):
        """
        Run FNN deconvolution on multiple cells in parallel

        Arguments:
        -----------------------------------------------------------------------
        F: ndarray, [nc, nt] or [nc, npix, nt]
            measured fluorescence values

        n_jobs: int scalar
            number of jobs to process in parallel. if n_jobs == -1, all cores
            are used.

        *fnn_args, **fnn_kwargs
            additional arguments to pass to deconvolve()

        Returns:
        -----------------------------------------------------------------------
        n_hat_best: ndarray, [nc, nt]
            MAP estimate of the most likely spike train

        c_hat_best: ndarray, [nc, nt]
            estimated intracellular calcium concentration (A.U.)

        LL: ndarray, [nc,]
            posterior log-likelihood of F given n_hat_best and theta_best

        theta_best: tuple, [nc,]
            model parameters, updated according to learn_theta
        """

        pool = Parallel(n_jobs=n_jobs, verbose=disp, pre_dispatch='n_jobs * 2')

        results = pool(delayed(deconvolve)
                       (rr, *fnn_args, **fnn_kwargs) for rr in F)

        n_hat, c_hat, LL, theta = zip(*results)
        n_hat, c_hat, LL = (np.vstack(a) for a in (n_hat, c_hat, LL))

        return n_hat, c_hat, LL, theta

except ImportError:
    # apply_all_cells is left undefined if joblib isn't present
    pass

def deconvolve(F, c0=None, theta0=((None,) * 5), dt=0.02, rate=0.5, tau=1.,
               learn_theta=((0,) * 5), norm_alpha=True, params_tol=1E-3,
               spikes_tol=1E-3, spikes_maxiter=100, params_maxiter=20,
               decimate=0, verbosity=0):
    """

    Fast Non-Negative Deconvolution
    ---------------------------------------------------------------------------
    This function uses an interior point method to solve the following
    optimization problem:

        n_hat = argmax_{n >= 0} P(n | F)

    where n_hat_best is a maximum a posteriori estimate for the most likely
    spike train, given the fluorescence signal F, and the model:

    c_{t} = gamma * c_{t-1} + n_{t},            n_{t} ~ Poisson(lambda * dt)
    F_{t} = alpha * c_{t} + beta + epsilon,     epsilon ~ N(0, sigma)

    It is also possible to estimate the model parameters sigma, alpha, beta and
    lambda from the data using pseudo-EM updates.

    Arguments:
    ---------------------------------------------------------------------------
    F: ndarray, [nt] or [npix, nt]
        measured fluorescence values

    c0: ndarray, [nt]
        initial estimate of the calcium concentration for each time bin

    theta0: len(5) sequence
        initial estimates of the model parameters (sigma, alpha, beta, lambda,
        gamma). any parameters in theta0 that are None will be estimated from
        F.

    dt: float scalar
        duration of each time bin (s)

    rate: float scalar
        estimate of mean firing rate (Hz), ignored if theta0[3] is not None

    tau: float scalar
        estimate of calcium decay time constant (s), ignored if theta0[4] is
        not None

    learn_theta: len(5) bool sequence
        specifies which of the model parameters to attempt learn via pseudo-EM
        iterations. currently gamma cannot be optimised.

    norm_alpha: bool
        if True (default), we impose that sum(alpha) == 1 by scaling n_hat and
        c_hat

    spikes_tol: float scalar
        termination condition for interior point spike train estimation:
            params_tol > abs((LL_prev - LL) / LL)

    params_tol: float scalar
        as above, but for the model parameter estimation

    spikes_maxiter: int scalar
        maximum number of interior point iterations to estimate MAP spike train

    params_maxiter: int scalar
        maximum number of pseudo-EM iterations to estimate model parameters

    decimate: int scalar
        option to decimate the input array over time when initalizing and
        updating the theta parameters, i.e. Fd = F[:, ::decimate]. this can
        be useful for very large input arrays. the default value (0) means no
        decimation is performed.

    verbosity: int scalar
        0: no convergence messages (default)
        1: convergence messages for model parameters
        2: convergence messages for model parameters & MAP spike train

    Returns:
    ---------------------------------------------------------------------------
    n_hat_best: ndarray, [nt]
        MAP estimate of the most likely spike train

    c_hat_best: ndarray, [nt]
        estimated intracellular calcium concentration (A.U.)

    LL_best: float scalar
        posterior log-likelihood of F given n_hat_best and theta_best

    theta_best: len(5) tuple
        model parameters, updated according to learn_theta

    Reference:
    ---------------------------------------------------------------------------
    Vogelstein, J. T., Packer, A. M., Machado, T. A., Sippy, T., Babadi, B.,
    Yuste, R., & Paninski, L. (2010). Fast nonnegative deconvolution for spike
    train inference from population calcium imaging. Journal of
    Neurophysiology, 104(6), 3691-704. doi:10.1152/jn.01073.2009

    """

    tstart = time.time()

    F = np.atleast_2d(F.astype(DTYPE))

    npix, nt = F.shape

    # ensure that F is non-negative
    offset = F.min() - EPS
    F = F - offset

    theta = _init_theta(F, theta0, offset, dt=dt, rate=rate, tau=tau,
                        decimate=decimate)

    sigma, alpha, beta, lamb, gamma = theta

    if c0 is None:

        # let n0 be a uniform vector of estimated mean spike probability, then
        # push this through the forward model to get c0. this way c0 is
        # guaranteed not to result in negative spike probabities in n_hat on
        # the first iteration.
        n0 = lamb * dt * np.ones(nt, dtype=DTYPE)
        c0 = signal.lfilter(np.r_[1.], np.r_[1., -gamma], n0, axis=0)

    else:
        # force a copy because c0 will be modified in place.
        c0 = c0.copy()

    # if we're not learning the parameters, this step is all we need to do
    n_hat, c_hat, LL = _get_MAP_spikes(F, c0, theta, dt, spikes_tol,
                                       spikes_maxiter, verbosity)

    # pseudo-EM iterations to optimize the model parameters
    if any(learn_theta):

        if verbosity >= 1:
            print('params: iter=%3i; LL=%-10.4g; delta_LL= N/A' % (0, LL))

        nloop1 = 1
        done = False

        while not done:

            # a 'full' parameter update, as used in the Vogelstein paper/code
            theta1 = _update_theta(n_hat, c_hat, F, theta, dt, learn_theta,
                                     decimate)

            # get the new n_hat, c_hat, and LL
            n1, c_hat1, LL1 = _get_MAP_spikes(F, c_hat, theta1, dt, spikes_tol,
                                              spikes_maxiter, verbosity)

            # test for convergence
            delta_LL = -((LL1 - LL) / LL)

            if verbosity >= 1:
                print('params: iter=%3i; LL=%-10.4g; delta_LL=%-10.4g'
                      % (nloop1, LL1, delta_LL))

            if delta_LL > 0:

                # keep the new parameters
                n_hat, c_hat, LL, theta = n1, c_hat1, LL1, theta1

                # if the LL is not improving significantly, time to terminate
                if delta_LL < params_tol:
                    if verbosity >= 1:
                        print("Parameter optimization converged after %i "
                              "iterations" % nloop1)
                    done = True

                elif nloop1 == params_maxiter:
                    if verbosity >= 1:
                        print('Parameter optimization failed to converge '
                              'before maxiter was reached (%i)' % nloop1)
                    done = True

            else:
                if verbosity >= 1:
                    print('Terminating parameter optimization on %i '
                          'iterations: LL is decreasing' % nloop1)
                done = True

            if done:
                if verbosity >= 1:
                    print("Last delta log-likelihood:\t%-10.4g" % delta_LL)
                    print("Best posterior log-likelihood:\t%10.3f" % LL)

            # increment the loop counter
            nloop1 += 1

    if verbosity >= 1:
        time_taken = time.time() - tstart
        print("Completed: %s" % s2h(time_taken))

    sigma, alpha, beta, lamb, gamma = theta

    # correct for the offset we originally applied to F
    beta += offset

    # we can impose that sum(alpha) == 1 by scaling c_hat and n_hat to
    # compensate
    if norm_alpha:

        alpha_sum = np.sum(alpha)
        if alpha_sum < 0:
            warnings.warn('sum(alpha) is negative - n_hat may therefore '
                          'contain negative values')
        alpha /= alpha_sum
        c_hat *= alpha_sum

        n_hat = c_hat[1:] - gamma * c_hat[:-1]

    # since we can't use FNND to estimate the spike probabilities in the 0th
    # timebin, for convenience we just concatenate 0 to the start of
    # n_hat so that it has the same shape as F and c_hat
    n_hat = np.r_[0, n_hat]

    theta = sigma, alpha, beta, lamb, gamma

    return n_hat, c_hat, LL, theta

def _get_MAP_spikes(F, c_hat, theta, dt, tol=1E-6, maxiter=100, verbosity=0):
    """
    Used internally by deconvolve to compute the maximum a posteriori
    spike train for a given set of fluorescence traces and model parameters.

    See the documentation for deconvolve for the meaning of the
    arguments

    Returns:    n_hat_best, c_hat_best, LL_best

    """
    npix, nt = F.shape

    sigma, alpha, beta, lamb, gamma = theta

    # we project everything onto the alpha mask so that we only ever have to
    # deal with 1D vector norms
    alpha_ss = np.dot(alpha, alpha)
    c = np.dot(alpha, F) - np.dot(alpha, beta)

    # used for computing the LL and gradient
    scale_var = 1. / (2 * sigma * sigma)
    lD = lamb * dt

    # used for computing the gradient (M.T.dot(lamb * dt))
    grad_lnprior = np.zeros(nt, dtype=DTYPE)
    grad_lnprior[1:] = lD
    grad_lnprior[:-1] -= lD * gamma

    # initialize the weight of the barrier term to 1
    z = 1.

    # initial estimate of spike probabilities
    n_hat = c_hat[1:] - gamma * c_hat[:-1]
    # assert not np.any(n_hat < 0), "spike probabilities < 0"

    # (actual - predicted) fluorescence
    res = c - alpha_ss * c_hat

    # best overall posterior log-likelihood of the fluorescence
    LL_best = _post_LL(n_hat, res, scale_var, lD, z)
    LL_barrier = LL_best

    nloop1 = 0
    terminate_interior = False

    # in the outer loop we'll progressively reduce the weight of the barrier
    # term and check the interior point termination criteria
    while not terminate_interior:

        nloop2 = 0
        terminate_barrier = False

        # converge for this barrier weight
        while not terminate_barrier:

            # by projecting everything onto alpha, we reduce this to a 1D
            # vector norm
            res = c - alpha_ss * c_hat

            # compute direction of newton step
            d = _direction(n_hat, res, alpha_ss, gamma, scale_var,
                           grad_lnprior, z)

            terminate_linesearch = False

            # find the largest step we can take in direction d without
            # violating the non-negativity constraint on n_hat
            s_upper_bnd = -n_hat / (d[1:] - gamma * d[:-1])

            # we are only interested in positive step sizes
            feasible = (s_upper_bnd > 0)

            if np.any(feasible):
                # largest allowable step size is 1.
                s = min(1., 0.999 * np.min(s_upper_bnd[feasible]))
            else:
                # if there is no step size that will keep n_hat >= 0, just
                # reduce the barrier weight and try again
                terminate_linesearch = True
                terminate_barrier = True

                if verbosity >= 2:
                    print("skipping linesearch: no positive step size will "
                          "keep n_hat >= 0")

            nloop3 = 0

            # backtracking line search for the largest step size that increases
            # the posterior log-likelihood of the fluorescence
            while not terminate_linesearch:

                # update estimated calcium
                c_hat_line = c_hat + (s * d)

                # update spike probabilities
                n_hat_line = c_hat_line[1:] - gamma * c_hat_line[:-1]
                # assert not np.any(n_hat_line < 0), "spike probabilities < 0"

                # (actual - predicted) fluorescence
                res = c - alpha_ss * c_hat_line

                # compute the new posterior log-likelihood
                LL_line = _post_LL(n_hat_line, res, scale_var, lD, z)
                # assert not np.any(np.isnan(LL1)), "nan LL"

                if verbosity >= 2:
                    print('spikes: iter=%3i, %3i, %3i; z=%-10.4g; s=%-10.4g;'
                          ' LL=%-10.4g'
                          % (nloop1, nloop2, nloop3, z, s, LL_line))

                # if the step size gets too small without making any progress,
                # we terminate the linesearch and reduce the barrier weight
                if s < S_TOL:
                    if verbosity >= 2:
                        print('--> terminated linesearch: s < %.3g on %i '
                              'iterations' % (S_TOL, nloop3))
                    terminate_linesearch = True
                    terminate_barrier = True

                # only update c_hat & LL if LL improved
                if LL_line > LL_barrier:
                    LL_barrier, n_hat, c_hat = LL_line, n_hat_line, c_hat_line
                    terminate_linesearch = True

                # reduce the step size
                else:
                    s /= S_FAC

                nloop3 += 1

            # if d gets too small, reduce the barrier weight
            if (np.linalg.norm(d) < D_TOL):
                terminate_barrier = True

            nloop2 += 1

        # only test for convergence if we were actually able to enter the
        # linesearch
        if nloop3:
            delta_LL = -(LL_barrier - LL_best) / LL_best
            LL_best = LL_barrier

            if (delta_LL < tol):
                terminate_interior = True

        elif z < Z_TOL:
            if verbosity >= 2:
                print('MAP spike train failed to converge before z < %.3g'
                      % Z_TOL)
            terminate_interior = True

        elif nloop1 > maxiter:
            if verbosity >= 2:
                print('MAP spike train failed to converge within maxiter (%i)'
                       % maxiter)
            terminate_interior = True

        # increment the outer loop counter, reduce the barrier weight
        nloop1 += 1
        z /= Z_FAC

    return n_hat, c_hat, LL_best

def _post_LL(n_hat, res, scale_var, lD, z):

    # barrier term
    with np.errstate(invalid='ignore'):     # suppress log(0) error messages
        barrier = np.log(n_hat).sum()       # this is currently a bottleneck

    # sum of squared (predicted - actual) fluorescence
    res_ss = res.ravel().dot(res.ravel())   # fast sum-of-squares

    # weighted posterior log-likelihood of the fluorescence
    LL = -(scale_var * res_ss) - (n_hat.sum() / lD) + (z * barrier)

    return LL

def _direction(n_hat, res, alpha_ss, gamma, scale_var,
               grad_lnprior, z):

    nt = grad_lnprior.shape[0]

    # gradient
    n_term = np.zeros(nt, dtype=DTYPE)
    n_term[:(nt - 1)] = -gamma / n_hat
    n_term[-(nt - 1):] += 1. / n_hat
    g = (2 * scale_var * res - grad_lnprior + z * n_term)

    # main diagonal of the hessian
    n2 = n_hat * n_hat
    Hd0 = np.zeros(nt, dtype=DTYPE)
    Hd0[:(nt - 1)] = (gamma * gamma) / n2
    Hd0[-(nt - 1):] += 1 / n2
    Hd0 *= -z
    Hd0 -= 2 * alpha_ss * scale_var

    # upper/lower diagonals of the hessian
    Hd1 = z * gamma / n2

    # solve the tridiagonal system Hd = -g (we use -g, since we want to
    # *ascend* the LL gradient)
    d = trisolve(Hd1, Hd0, Hd1.copy(), -g, inplace=True)

    return d

def _update_theta(n_hat, c_hat, F, theta, dt, learn_theta, decimate=0):

    if decimate > 0:
        c_hat = c_hat[::decimate]
        n_hat = n_hat[::decimate]
        F = F[:, ::decimate]

    sigma, alpha, beta, lamb, gamma = theta
    learn_sigma, learn_alpha, learn_beta, learn_lamb, learn_gamma = learn_theta

    npix, nt = F.shape

    if learn_alpha:

        if learn_beta:
            A = np.vstack((c_hat, np.ones(nt, dtype=DTYPE)))
        else:
            A = c_hat[None, :]

        Y, residuals, rank, singular_vals = np.linalg.lstsq(A.T, F.T)

        # # enforce non-negativity of coefficients?
        # Y[Y < EPS] = EPS

        if learn_beta:
            alpha, beta = Y

        else:
            alpha = Y[0]

    elif learn_beta:
        beta = (F - alpha[:, None] * c_hat[None, :]).sum(1) / nt

    if learn_sigma:
        res = F - (alpha[:, None] * c_hat[None, :] - beta[:, None])
        ssd = res.ravel().dot(res.ravel())  # fast sum-of-squares
        sigma = np.sqrt(ssd / nt)           # RMS error

    if learn_lamb:
        lamb = n_hat.sum() / (nt * dt)

    if learn_gamma:
        warnings.warn('optimising gamma is not yet supported (ignoring)')

    return (sigma, alpha, beta, lamb, gamma)

def _init_theta(F, theta0, offset, dt=0.02, rate=1., tau=1.0, decimate=0):

    if decimate > 0:
        F = F[:, ::decimate]

    npix = F.shape[0]
    sigma, alpha, beta, lamb, gamma = theta0

    if None in (sigma, alpha, beta):
        med_F = np.median(F, axis=1)

    # noise parameter
    if sigma is None:
        # K is the correction factor when using the median absolute deviation
        # as a robust estimator of the standard deviation of a normal
        # distribution <http://en.wikipedia.org/wiki/Median_absolute_deviation>
        K = 1.4826
        mad = np.median(np.abs(F - med_F[:, None]))
        sigma = mad * K                         # scalar

    # amplitude
    if alpha is None:
        alpha = np.atleast_1d(med_F)            # vector

    # baseline
    if beta is None:
        if npix == 1:
            beta = np.atleast_1d(np.percentile(F, 5., axis=1))
        else:
            beta = np.atleast_1d(med_F)
    else:
        # beta should absorb the offset parameter
        beta = np.atleast_1d(beta - offset)

    # firing rate
    if lamb is None:
        lamb = rate                             # scalar

    # decay parameter (fraction of remaining fluorescence after one time step)
    if gamma is None:
        gamma = np.exp(-dt / tau)               # scalar

    return sigma, alpha, beta, lamb, gamma


from __future__ import print_function
import numpy as np
import ctypes
from ctypes import byref
from ctypes.util import find_library
from numpy.ctypeslib import ndpointer

# try and find a LAPACK shared library
dgtsv, sgtsv = None, None
for name in ('openblas', 'lapack'):
    libname = find_library(name)
    if libname:
        lapack_lib = ctypes.cdll.LoadLibrary(libname)
        try:
            dgtsv = lapack_lib.dgtsv_
            sgtsv = lapack_lib.sgtsv_
            break
        except AttributeError:
            # occurs if the library doesn't define the necessary symbols
            continue
if None in (dgtsv, sgtsv):
    raise EnvironmentError('Could not locate a LAPACK shared library', 2)

# pointer ctypes
_c_int_p = ctypes.POINTER(ctypes.c_int)
_c_float_p = ctypes.POINTER(ctypes.c_float)
_c_double_p = ctypes.POINTER(ctypes.c_double)

def trisolve(dl, d, du, b, inplace=False):
    """
    The tridiagonal matrix (Thomas) algorithm for solving tridiagonal systems
    of equations:

        a_{i}x_{i-1} + b_{i}x_{i} + c_{i}x_{i+1} = y_{i}

    in matrix form:
        Mx = b

    TDMA is O(n), whereas standard Gaussian elimination is O(n^3).

    Arguments:
    -----------
        dl: (n - 1,) vector
            the lower diagonal of M
        d: (n,) vector
            the main diagonal of M
        du: (n - 1,) vector
            the upper diagonal of M
        b: (n,) vector
            the result of Mx
        inplace:
            if True, and if d and b are both float64 vectors, they will be
            modified in place (may be faster)

    Returns:
    -----------
        x: (n,) vector
            the solution to Mx = b

    References:
    -----------
    http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm
    http://www.netlib.org/lapack/explore-html/d1/db3/dgtsv_8f.html
    """

    if (dl.shape[0] != du.shape[0] or (d.shape[0] != dl.shape[0] + 1)
            or d.shape[0] != b.shape[0]):
        raise ValueError('Invalid diagonal shapes')

    bshape_in = b.shape
    rtype = np.result_type(dl, d, du, b)

    if not inplace:
        # force a copy
        dl = np.array(dl, dtype=rtype, copy=True, order='F')
        d = np.array(d, dtype=rtype, copy=True, order='F')
        du = np.array(du, dtype=rtype, copy=True, order='F')
        b = np.array(b, dtype=rtype, copy=True, order='F')

    # this may also force copies if arrays have inconsistent types / incorrect
    # order
    dl, d, du, b = (np.array(v, dtype=rtype, copy=False, order='F')
                    for v in (dl, d, du, b))

    # use the LAPACK implementation
    _lapack_trisolve(dl, d, du, b, rtype)

    return b.reshape(bshape_in)

def _lapack_trisolve(dl, d, du, b, rtype):

    if b.ndim == 1:
        # needs to be (ldb, nrhs)
        b = b[:, None]

    _n = ctypes.c_int(d.shape[0])
    _nrhs = ctypes.c_int(b.shape[1])
    _ldb = ctypes.c_int(b.shape[0])
    _info = ctypes.c_int(1)

    # b will now be modified in place to give the result
    if rtype == np.float32:
        sgtsv(byref(_n), byref(_nrhs),
              dl.ctypes.data_as(_c_float_p),
              d.ctypes.data_as(_c_float_p),
              du.ctypes.data_as(_c_float_p),
              b.ctypes.data_as(_c_float_p),
              byref(_ldb), byref(_info))

    elif rtype == np.float64:
        dgtsv(byref(_n), byref(_nrhs),
              dl.ctypes.data_as(_c_double_p),
              d.ctypes.data_as(_c_double_p),
              du.ctypes.data_as(_c_double_p),
              b.ctypes.data_as(_c_double_p),
              byref(_ldb), byref(_info))
    else:
        raise ValueError('Unsupported result type: %s' % rtype)

def bench_trisolve():

    import time
    from scipy import sparse
    import scipy.sparse.linalg

    N = np.logspace(2, 6, 5).astype(np.int)
    nreps = 5

    k = 1

    dgtsv_times = []
    LU_times = []

    for n in N:

        d0 = np.random.randn(n)
        d1 = np.random.randn(n - k)

        H = sparse.diags((d1, d0, d1), (-k, 0, k), format='csc')
        x = np.random.randn(n)
        g = H.dot(x)

        start = time.time()
        for _ in xrange(nreps):
            xhat = trisolve(d1, d0, d1, g, inplace=False)
        t1 = (time.time() - start) / nreps
        norm1 = np.linalg.norm(x - xhat)

        start = time.time()
        for _ in xrange(nreps):
            xhat = sparse.linalg.spsolve(H, g)
        t2 = (time.time() - start) / nreps
        norm2 = np.linalg.norm(x - xhat)

        print("\nn = %i" % n)
        print("Time (sec):\ttridiag: %g\tLU: %g" % (t1, t2))
        print("||x - xhat||2:\ttridiag: %g\tLU: %g" % (norm1, norm2))

        dgtsv_times.append(t1)
        LU_times.append(t2)

    return N, np.array(dgtsv_times), np.array(LU_times)


from __future__ import absolute_import
import numpy as np
from scipy import signal
from ._fnndeconv import deconvolve
from . import plotting

def make_fake_movie(nframes, mask_shape=(64, 64), mask_center=None,
                    bg_intensity=0.1, mask_sigma=10, dt=0.02, rate=1.0,
                    tau=1., sigma=0.001, seed=None):
    """
    Generate 2D fake fluorescence movie

    Arguments:
    ---------------------------------------------------------------------------
        nframes:        number of timebins to simulate
        mask_shape:     tuple (nrows, ncols), shape of a single movie frame
        mask_center:    tuple (x, y), pixel coords of cell center
        bg_intensity:   scalar, amplitude of (static) baseline fluorescence
        mask_sigma:     scalar, standard deviation of Gaussian mask
        dt:             timestep (s)
        rate:           mean spike rate (Hz)
        tau:            time constant of decay in calcium concentration (s)
        sigma:          SD of additive noise on fluorescence
        seed:           Seed for RNG

    Returns:
    ---------------------------------------------------------------------------
        F:          fluorescence [npixels, nframes]
        c:          calcium concentration [nframes,]
        n:          spike train [nframes,]
        theta:      tuple of true model parameters:
                    (sigma, alpha, beta, lambda, gamma)

    """

    gen = np.random.RandomState(seed)

    # poisson spikes
    n = gen.poisson(rate * dt, size=nframes)

    # internal calcium dynamics
    gamma = np.exp(-dt / tau)
    c = signal.lfilter(np.r_[1], np.r_[1, -gamma], n, axis=0)

    # pixel weights (sum == 1)
    nr, nc = mask_shape
    npix = nr * nc
    if mask_center is None:
        mask_center = (nc // 2., nr // 2.)
    a, b = mask_center
    y, x = np.ogrid[:nr, :nc]
    xs = (x - a) ** 2.
    ys = (y - b) ** 2.
    twoss = 2. * mask_sigma ** 2.
    alpha = np.exp(-1 * ((xs / twoss) + (ys / twoss))).ravel()
    alpha /= alpha.sum()

    # background fluorescence
    beta = gen.randn(npix) * bg_intensity

    # firing rate (spike probability per sec)
    lamb = rate

    # spatially & temporally white noise
    epsilon = gen.randn(npix, nframes) * sigma

    # simulated fluorescence
    F = c[None, :] * alpha[:, None] + beta[:, None] + epsilon

    theta = (sigma, alpha, beta, lamb, gamma)

    return F, c, n, theta

def make_demo_plots(seed=0):

    # single pixel
    F, c, n, theta = make_fake_movie(1000, dt=0.02, mask_shape=(1, 1),
                                     sigma=0.1, seed=seed)
    n_best, c_best, LL, theta_best = deconvolve(
        F, dt=0.02, verbosity=1, learn_theta=(0, 1, 1, 0, 0),
        spikes_tol=1E-6, params_tol=1E-6, norm_alpha=True, decimate=0,
    )

    plotting.ground_truth_1D(F, n_best, c_best, theta_best, n, c, theta, 0.02)

    # 2D movie
    F, c, n, theta = make_fake_movie(1000, dt=0.02, mask_shape=(64, 64),
                                     sigma=0.001, seed=seed)
    n_best, c_best, LL, theta_best = deconvolve(
        F, dt=0.02, verbosity=1, learn_theta=(0, 1, 1, 0, 0),
        spikes_tol=1E-6, params_tol=1E-6, norm_alpha=True, decimate=0,
    )
    plotting.ground_truth_2D(F, n_best, c_best, theta_best, n, c, theta, 0.02,
                             64, 64)

if __name__ == "__main__":
    make_demo_plots()


import numpy as np
from scipy import ndimage, signal

def detrend(x, dt=0.02, stop_hz=0.01, order=5):
    orig_shape = x.shape
    x = np.atleast_2d(x)
    nyquist = 0.5 / dt
    stop = stop_hz / nyquist
    b, a = signal.butter(order, Wn=stop, btype='lowpass')
    y = signal.filtfilt(b, a, x, axis=1)
    return (x - y).reshape(orig_shape)

def boxcar(F, dt=0.02, avg_win=1.0):
    orig_shape = F.shape
    F = np.atleast_2d(F)
    npix, nt = F.shape
    win_len = max(1, avg_win / dt)
    win = np.ones(win_len) / win_len
    Fsmooth = ndimage.convolve1d(F, win, axis=1, mode='reflect')
    return Fsmooth.reshape(orig_shape)

def s2h(ss):
    """convert seconds to a pretty "d hh:mm:ss.s" format"""
    mm, ss = divmod(ss, 60)
    hh, mm = divmod(mm, 60)
    dd, hh = divmod(hh, 24)
    tstr = "%02i:%04.1f" % (mm, ss)
    if hh > 0:
        tstr = ("%02i:" % hh) + tstr
    if dd > 0:
        tstr = ("%id " % dd) + tstr
    return tstr


"""
NAD has an RS232 interface to control the receiver.

Not all receivers have all functions.
Functions can be found on the NAD website: http://nadelectronics.com/software
"""

import codecs
import socket
from time import sleep
from nad_receiver.nad_commands import CMDS
import serial  # pylint: disable=import-error
import threading
import time
import telnetlib

DEFAULT_TIMEOUT = 1

class NADReceiver(object):
    """NAD receiver."""

    def __init__(self, serial_port):
        """Create RS232 connection."""
        self.ser = serial.Serial(serial_port, baudrate=115200)
        self.lock = threading.Lock()

    def exec_command(self, domain, function, operator, value=None):
        """
        Write a command to the receiver and read the value it returns.

        The receiver will always return a value, also when setting a value.
        """
        if operator in CMDS[domain][function]['supported_operators']:
            if operator is '=' and value is None:
                raise ValueError('No value provided')

            if value is None:
                cmd = ''.join([CMDS[domain][function]['cmd'], operator])
            else:
                cmd = ''.join(
                    [CMDS[domain][function]['cmd'], operator, str(value)])
        else:
            raise ValueError('Invalid operator provided %s' % operator)

        if not self.ser.is_open:
            self.ser.open()

        try:
            self.lock.acquire()

            self.ser.write(''.join(['\r', cmd, '\r']).encode('utf-8'))
            time.sleep(0.1)
            # not sure why, but otherwise it is not ready yet to do the read.

            msg = self.ser.read(self.ser.in_waiting)

            try:
                msg = msg.decode()[1:-1]
                msg = msg.split('=')[1]
                return msg
            except IndexError:
                pass

        finally:
            self.lock.release()

    def main_dimmer(self, operator, value=None):
        """Execute Main.Dimmer."""
        return self.exec_command('main', 'dimmer', operator, value)

    def main_mute(self, operator, value=None):
        """Execute Main.Mute."""
        return self.exec_command('main', 'mute', operator, value)

    def main_power(self, operator, value=None):
        """Execute Main.Power."""
        return self.exec_command('main', 'power', operator, value)

    def main_volume(self, operator, value=None):
        """
        Execute Main.Volume.

        Returns int
        """
        try:
            res = int(self.exec_command('main', 'volume', operator, value))
            return res

        except (ValueError, TypeError):
            pass

        return None

    def main_ir(self, operator, value=None):
        """Execute Main.IR."""
        return self.exec_command('main', 'ir', operator, value)

    def main_listeningmode(self, operator, value=None):
        """Execute Main.ListeningMode."""
        return self.exec_command('main', 'listeningmode', operator, value)

    def main_sleep(self, operator, value=None):
        """Execute Main.Sleep."""
        return self.exec_command('main', 'sleep', operator, value)

    def main_source(self, operator, value=None):
        """
        Execute Main.Source.

        Returns int
        """
        try:
            source = int(self.exec_command('main', 'source', operator, value))
            return source
        except (ValueError, TypeError):
            pass

        return None

    def main_version(self, operator, value=None):
        """Execute Main.Version."""
        return self.exec_command('main', 'version', operator, value)

    def tuner_am_frequency(self, operator, value=None):
        """Execute Tuner.AM.Frequence."""
        return self.exec_command('tuner', 'am_frequency', operator, value)

    def tuner_am_preset(self, operator, value=None):
        """Execute Tuner.AM.Preset."""
        return self.exec_command('tuner', 'am_preset', operator, value)

    def tuner_band(self, operator, value=None):
        """Execute Tuner.Band."""
        return self.exec_command('tuner', 'band', operator, value)

    def tuner_fm_frequency(self, operator, value=None):
        """Execute Tuner.FM.Frequence."""
        return self.exec_command('tuner', 'fm_frequency', operator, value)

    def tuner_fm_mute(self, operator, value=None):
        """Execute Tuner.FM.Mute."""
        return self.exec_command('tuner', 'fm_mute', operator, value)

    def tuner_fm_preset(self, operator, value=None):
        """Execute Tuner.FM.Preset."""
        return self.exec_command('tuner', 'fm_preset', operator, value)

class NADReceiverTCP(object):
    """
    Support NAD amplifiers that use tcp for communication.

    Known supported model: Nad D 7050.
    """

    POLL_VOLUME = "0001020204"
    POLL_POWER = "0001020209"
    POLL_MUTED = "000102020a"
    POLL_SOURCE = "0001020203"

    CMD_POWERSAVE = "00010207000001020207"
    CMD_OFF = "0001020900"
    CMD_ON = "0001020901"
    CMD_VOLUME = "00010204"
    CMD_MUTE = "0001020a01"
    CMD_UNMUTE = "0001020a00"
    CMD_SOURCE = "00010203"

    SOURCES = {'Coaxial 1': '00', 'Coaxial 2': '01', 'Optical 1': '02',
               'Optical 2': '03', 'Computer': '04', 'Airplay': '05',
               'Dock': '06', 'Bluetooth': '07'}
    SOURCES_REVERSED = {value: key for key, value in
                        SOURCES.items()}

    PORT = 50001
    BUFFERSIZE = 1024

    def __init__(self, host):
        """Setup globals."""
        self._host = host

    def _send(self, message, read_reply=False):
        """Send a command string to the amplifier."""
        sock = None
        for tries in range(0, 3):
            try:
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                sock.connect((self._host, self.PORT))
                break
            except (ConnectionError, BrokenPipeError):
                if tries == 3:
                    print("socket connect failed.")
                    return
                sleep(0.1)
        sock.send(codecs.decode(message, 'hex_codec'))
        if read_reply:
            sleep(0.1)
            reply = ''
            tries = 0
            max_tries = 20
            while len(reply) < len(message) and tries < max_tries:
                try:
                    reply += codecs.encode(sock.recv(self.BUFFERSIZE), 'hex')\
                        .decode("utf-8")
                except (ConnectionError, BrokenPipeError):
                    pass
                tries += 1
            sock.close()
            if tries >= max_tries:
                return
            return reply
        sock.close()

    def status(self):
        """
        Return the status of the device.

        Returns a dictionary with keys 'volume' (int 0-200) , 'power' (bool),
         'muted' (bool) and 'source' (str).
        """
        nad_reply = self._send(self.POLL_VOLUME +
                               self.POLL_POWER +
                               self.POLL_MUTED +
                               self.POLL_SOURCE, read_reply=True)
        if nad_reply is None:
            return

        # split reply into parts of 10 characters
        num_chars = 10
        nad_status = [nad_reply[i:i + num_chars]
                      for i in range(0, len(nad_reply), num_chars)]

        return {'volume': int(nad_status[0][-2:], 16),
                'power': nad_status[1][-2:] == '01',
                'muted': nad_status[2][-2:] == '01',
                'source': self.SOURCES_REVERSED[nad_status[3][-2:]]}

    def power_off(self):
        """Power the device off."""
        status = self.status()
        if status['power']:  # Setting power off when it is already off can cause hangs
            self._send(self.CMD_POWERSAVE + self.CMD_OFF)

    def power_on(self):
        """Power the device on."""
        status = self.status()
        if not status['power']:
            self._send(self.CMD_ON, read_reply=True)
            sleep(0.5)  # Give NAD7050 some time before next command

    def set_volume(self, volume):
        """Set volume level of the device. Accepts integer values 0-200."""
        if 0 <= volume <= 200:
            volume = format(volume, "02x")  # Convert to hex
            self._send(self.CMD_VOLUME + volume)

    def mute(self):
        """Mute the device."""
        self._send(self.CMD_MUTE, read_reply=True)

    def unmute(self):
        """Unmute the device."""
        self._send(self.CMD_UNMUTE)

    def select_source(self, source):
        """Select a source from the list of sources."""
        status = self.status()
        if status['power']:  # Changing source when off may hang NAD7050
            if status['source'] != source:  # Setting the source to the current source will hang the NAD7050
                if source in self.SOURCES:
                    self._send(self.CMD_SOURCE + self.SOURCES[source], read_reply=True)

    def available_sources(self):
        """Return a list of available sources."""
        return list(self.SOURCES.keys())

class NADReceiverTelnet(NADReceiver):
    """
    Support NAD amplifiers that use telnet for communication.
    Supports all commands from the RS232 base class

    Known supported model: Nad T787.
    """
    def _open_connection(self):
        if not self.telnet:
            try:
                self.telnet = telnetlib.Telnet(self.host, self.port, 3)
                # Some versions of the firmware report Main.Model=T787.
                # some versions do not, we want to clear that line
                self.telnet.read_until('\n'.encode(), self.timeout)
                # Could raise eg. EOFError, UnicodeError
            except:
                return False

        return True

    def _close_connection(self):
        """
        Close any telnet session
        """
        if self.telnet:
            self.telnet.close()

    def __init__(self, host, port=23, timeout=DEFAULT_TIMEOUT):
        """Create NADTelnet."""
        self.telnet = None
        self.host = host
        self.port = port
        self.timeout = timeout
        # __init__ must never raise

    def __del__(self):
        """
        Close any telnet session
        """
        self._close_connection()

    def exec_command(self, domain, function, operator, value=None):
        """
        Write a command to the receiver and read the value it returns.
        """
        if operator in CMDS[domain][function]['supported_operators']:
            if operator is '=' and value is None:
                raise ValueError('No value provided')

            if value is None:
                cmd = ''.join([CMDS[domain][function]['cmd'], operator])
            else:
                cmd = ''.join(
                    [CMDS[domain][function]['cmd'], operator, str(value)])
        else:
            raise ValueError('Invalid operator provided %s' % operator)

        if self._open_connection():
            # For telnet the first \r / \n is recommended only
            self.telnet.write((''.join(['\r', cmd, '\n']).encode()))
            # Could raise eg. socket.error, UnicodeError, let the client handle it

            # Test 3 x buffer is completely empty
            # With the default timeout that means a delay at
            # about 3+ seconds
            loop = 3
            while loop:
                msg = self.telnet.read_until('\n'.encode(), self.timeout)
                # Could raise eg. EOFError, UnicodeError, let the client handle it

                if msg == "":
                    # Nothing in buffer
                    loop -= 1
                    continue

                msg = msg.decode().strip('\r\n')
                # Could raise eg. UnicodeError, let the client handle it

                #print("NAD reponded with '%s'" % msg)
                # Wait for the response that equals the requested domain.function
                if msg.strip().split('=')[0].lower() == '.'.join([domain, function]).lower():
                    # b'Main.Volume=-12\r will return -12
                    return msg.strip().split('=')[1]

            raise RuntimeError('Failed to read response')

        raise RuntimeError('Failed to open connection')


# -*- coding: utf-8 -*-
"""Django view handlers."""

from django.http import HttpResponseNotFound

try:
    from urllib.parse import unquote, urlparse
except ImportError:
    from urllib import unquote
    from urlparse import urlparse

try:
    from django.urls import Resolver404, resolve
except ImportError:
    from django.core.urlresolvers  import Resolver404, resolve

from unfriendly import settings
from unfriendly.utils import CheckSumError, InvalidKeyError, decrypt

def deobfuscate(request, key, juice=None):
    """
    Deobfuscates the URL and returns HttpResponse from source view.
    SEO juice is mostly ignored as it is intended for display purposes only.
    """
    try:
        url = decrypt(str(key),
                      settings.UNFRIENDLY_SECRET,
                      settings.UNFRIENDLY_IV,
                      checksum=settings.UNFRIENDLY_ENFORCE_CHECKSUM)
    except (CheckSumError, InvalidKeyError):
        return HttpResponseNotFound()

    try:
        url = url.decode('utf-8')
    except UnicodeDecodeError:
        return HttpResponseNotFound()

    url_parts = urlparse(unquote(url))
    path = url_parts.path
    query = url_parts.query

    try:
        view, args, kwargs = resolve(path)
    except Resolver404:
        return HttpResponseNotFound()

    # fix-up the environ object
    environ = request.environ.copy()
    environ['PATH_INFO'] = path[len(environ['SCRIPT_NAME']):]
    environ['QUERY_STRING'] = query

    # init a new request
    patched_request = request.__class__(environ)

    # copy over any missing request attributes - this feels hackish
    missing_items = set(dir(request)) - set(dir(patched_request))
    while missing_items:
        missing_item = missing_items.pop()
        patched_request.__setattr__(missing_item,
                                    request.__getattribute__(missing_item))

    # mark this request as obfuscated
    patched_request.META['obfuscated'] = True

    response = view(patched_request, *args, **kwargs)

    # offer up a friendlier juice-powered filename if downloaded
    if juice and not response.has_header('Content-Disposition'):
        response['Content-Disposition'] = 'inline; filename=%s' % juice

    return response


# -*- coding: utf-8 -*-
"""Various encryption utilites."""

import base64
import binascii
import struct
import zlib

import six

from Crypto.Cipher import AES

class InvalidKeyError(Exception):
    """Invalid key error class."""
    pass

class CheckSumError(Exception):
    """Checksum mismatch error class."""
    pass

def _lazysecret(secret, blocksize=32, padding='}'):
    """Pads secret if not legal AES block size (16, 24, 32)"""
    if not len(secret) in (16, 24, 32):
        return secret + (blocksize - len(secret)) * padding
    return secret

def _crc(plaintext):
    """Generates crc32. Modulo keep the value within int range."""
    if not isinstance(plaintext, six.binary_type):
        plaintext = six.b(plaintext)
    return (zlib.crc32(plaintext) % 2147483647) & 0xffffffff

def _pack_crc(plaintext):
    """Packs plaintext crc32 as binary data."""
    return struct.pack('i', _crc(plaintext))

def encrypt(plaintext, secret, inital_vector, checksum=True, lazy=True):
    """Encrypts plaintext with secret
    plaintext      - content to encrypt
    secret         - secret to encrypt plaintext
    inital_vector  - initial vector
    lazy           - pad secret if less than legal blocksize (default: True)
    checksum       - attach crc32 byte encoded (default: True)
    returns ciphertext
    """
    if not isinstance(plaintext, six.binary_type):
        plaintext = six.b(plaintext)

    secret = _lazysecret(secret) if lazy else secret
    encobj = AES.new(secret, AES.MODE_CFB, inital_vector)

    if checksum:
        packed = _pack_crc(plaintext)
        plaintext += base64.urlsafe_b64encode(packed)

    encoded = base64.urlsafe_b64encode(encobj.encrypt(plaintext))
    if isinstance(plaintext, six.binary_type):
        encoded = encoded.decode()

    return encoded.replace('=', '')

def decrypt(ciphertext, secret, inital_vector, checksum=True, lazy=True):
    """Decrypts ciphertext with secret
    ciphertext     - encrypted content to decrypt
    secret         - secret to decrypt ciphertext
    inital_vector  - initial vector
    lazy           - pad secret if less than legal blocksize (default: True)
    checksum       - verify crc32 byte encoded checksum (default: True)
    returns plaintext
    """
    secret = _lazysecret(secret) if lazy else secret
    encobj = AES.new(secret, AES.MODE_CFB, inital_vector)
    try:
        padded = ciphertext + ('=' * (len(ciphertext) % 4))
        decoded = base64.urlsafe_b64decode(str(padded))
        plaintext = encobj.decrypt(decoded)
    except (TypeError, binascii.Error):
        raise InvalidKeyError("invalid key")

    if checksum:
        try:
            crc, plaintext = (base64.urlsafe_b64decode(
                plaintext[-8:]), plaintext[:-8])
        except (TypeError, binascii.Error):
            raise CheckSumError("checksum mismatch")

        if not crc == _pack_crc(plaintext):
            raise CheckSumError("checksum mismatch")

    return plaintext


# -*- coding: utf-8 -*-
"""Module contains package templatetags."""

from django import template
from django.template.defaultfilters import slugify
from unfriendly import settings
from unfriendly.utils import encrypt

try:
    from django.urls import reverse
except ImportError:
    from django.core.urlresolvers import reverse

register = template.Library()

@register.filter
def obfuscate(value, juice=None):
    """
    Template filter that obfuscates whatever text it is applied to. The text is
    supposed to be a URL, but it will obfuscate anything.

    Usage:
        Extremely unfriendly URL:
        {{ "/my-secret-path/"|obfuscate }}

        Include some SEO juice:
        {{ "/my-secret-path/"|obfuscate:"some SEO friendly text" }}
    """
    if not settings.UNFRIENDLY_ENABLE_FILTER:
        return value
    kwargs = {
        'key': encrypt(value,
                       settings.UNFRIENDLY_SECRET,
                       settings.UNFRIENDLY_IV,
                       checksum=settings.UNFRIENDLY_ENFORCE_CHECKSUM),
    }
    if juice:
        kwargs['juice'] = slugify(juice)
    return reverse('unfriendly-deobfuscate', kwargs=kwargs)


from .MusicWebsiteParser import MusicWebsiteParser
from ..FileDownload import FileDownload
from BeautifulSoup import BeautifulSoup
import re

class MrJattParser(MusicWebsiteParser):
	'''
	Parser for https://mr-jatt.com/

	'''

	def missing_schema(self,html,song_name):
		'''
		It will print the list of songs that can be downloaded
		'''
		#html=self.get_html_response(url)
		soup=BeautifulSoup(html)
		name=' '.join(song_name)
		print '%s not found'%name
		print "But you can download any of the following songs :"
		a_list=soup.findAll('a','touch')
		for x in xrange(len(a_list)-1):
			r=a_list[x]
			p=str(r)
			q=re.sub(r'||||||','',p)
			print q

	def list_of_all_href(self,html):
		'''
		It will return all hyper links found in the mr-jatt page for download
		'''
		soup=BeautifulSoup(html)
		links=[]
		a_list=soup.findAll('a','touch')
		for x in xrange(len(a_list)-1):
			link = a_list[x].get('href')
			name = a_list[x]
			name = str(name)
			name=re.sub(r'||||||','',name)
			name=re.sub(r'^[0-9]+\.','',name)
			links.append([link,name])

		#quit()
		return links

	def check_if_song_name(self,html):
		'''
		Returns true if user entered artist or movie name
		'''
		soup=BeautifulSoup(html)
		a_list=soup.findAll('a','touch')
		#print a_list
		text=[str(x) for x in a_list]
		text=''.join(text)
		text=text.lower()
		string1='download in 48 kbps'
		string2='download in 128 kbps'
		string3='download in 320 kbps'

		href=''
		if string3 in text:
			#print 'Downloading in 320 kbps'
			href=a_list[2].get('href')

		elif string2 in text:
			#print 'Downloading in 128 kbps'
			href=a_list[1].get('href')

		elif string1 in text:
			#print 'Downloading in 48 kbps'
			href=a_list[0].get('href')
		else:
			return (True,'nothing')

		return (False,href)

	def Parse(self,url,song_name,flag):
		'''
		It will the resource URL if song is found,
		Otherwise it will return the list of songs that can be downloaded
		'''
		file_download=FileDownload()
		html=file_download.get_html_response(url)
		if flag == False:
			soup=BeautifulSoup(html)
			a_list=soup.findAll('a','touch')
			#print a_list
			text=[str(x) for x in a_list]
			text=''.join(text)
			text=text.lower()
			string1='download in 48 kbps'
			string2='download in 128 kbps'
			string3='download in 320 kbps'

			href=''
			if string3 in text:
				print 'Downloading in 320 kbps'
				href=a_list[2].get('href')

			elif string2 in text:
				print 'Downloading in 128 kbps'
				href=a_list[1].get('href')

			elif string1 in text:
				print 'Downloading in 48 kbps'
				href=a_list[0].get('href')
			else:
				self.missing_schema(html,song_name)
				quit()

			return href
		else:
			x,href=self.check_if_song_name(html)
			links = []
			if x==True:
				links=self.list_of_all_href(html)
			else:
				file_download=FileDownload()
				file_download.file_download_cross_platform(href)
				quit()

			return links


from .SearchEngineParser import SearchEngineParser
from ..FileDownload import FileDownload
from BeautifulSoup import BeautifulSoup

class GoogleParser(SearchEngineParser):
	'''
	A parser for google search Engine
	'''

	def google_url(self,song_name,website):
		''' It will return the google url to be searched'''
		name='+'.join(song_name)
		prefix='https://www.google.co.in/search?q='
		website=website.split(" ")
		suffix='+'.join(website)
		url=prefix+name+suffix
		#print url
		return url

	def parse_google(self,html):
		'''It will parse google html response
			and return the first url
		 '''
		soup = BeautifulSoup(html)
		href=soup.find('div','g').find('a').get('href')
		href_list=href.split('&')
		download_url=href_list[0]
		download_url=download_url.strip()
		download_url=download_url.replace('/url?q=','')
		return download_url

	def Parse(self,song_name,website):
		'''
		song_name is a list of strings
		website is a string
		It will return the url from where music file needs to be downloaded
		'''
		url_to_be_parsed=self.google_url(song_name,website)
		file_download=FileDownload()
		html=file_download.get_html_response(url_to_be_parsed)
		website_url=self.parse_google(html)
		return website_url


import requests
import os
import os.path
import subprocess
from tqdm import tqdm

class FileDownload:

	def get_html_response(self,url):
		'''It will download the html page specified by url and return the html response '''
		print "Downloading page %s .."%url
		try:
			response=requests.get(url,timeout=50)
		except requests.exceptions.SSLError:
			try:
				response=requests.get(url,verify=False,timeout=50)
			except requests.exceptions.RequestException as e:
				print e
				quit()
		except requests.exceptions.RequestException as e:
			print e
			quit()

		return response.content

	def file_download_using_requests(self,url):
		'''It will download file specified by url using requests module'''
		file_name=url.split('/')[-1]

		if os.path.exists(os.path.join(os.getcwd(),file_name)):
			print 'File already exists'
			return
		#print 'Downloading file %s '%file_name
		#print 'Downloading from %s'%url

		try:
			r=requests.get(url,stream=True,timeout=200)
		except requests.exceptions.SSLError:
			try:
				response=requests.get(url,stream=True,verify=False,timeout=200)
			except requests.exceptions.RequestException as e:
				print e
				quit()
		except requests.exceptions.RequestException as e:
				print e
				quit()
		chunk_size = 1024
		total_size = int(r.headers['Content-Length'])
		total_chunks = total_size/chunk_size

		file_iterable = r.iter_content(chunk_size = chunk_size)
		tqdm_iter = tqdm(iterable = file_iterable,total = total_chunks,unit = 'KB',
			leave = False
			)
		with open(file_name,'wb') as f:
			for data in tqdm_iter:
				f.write(data)

		#total_size=float(r.headers['Content-Length'])/(1024*1024)
		'''print 'Total size of file to be downloaded %.2f MB '%total_size
		total_downloaded_size=0.0
		with open(file_name,'wb') as f:
			for chunk in r.iter_content(chunk_size=1*1024*1024):
				if chunk:
					size_of_chunk=float(len(chunk))/(1024*1024)
					total_downloaded_size+=size_of_chunk
					print '{0:.0%} Downloaded'.format(total_downloaded_size/total_size)
					f.write(chunk)'''

		print 'Downloaded file %s '%file_name

	def file_download_using_wget(self,url):
		'''It will download file specified by url using wget utility of linux '''
		file_name=url.split('/')[-1]
		print 'Downloading file %s '%file_name
		command='wget -c --read-timeout=50 --tries=3 -q --show-progress --no-check-certificate '
		url='"'+url+'"'
		command=command+url
		os.system(command)

	def file_download_cross_platform(self,url):
		file_name=url.split('/')[-1]
		if os.path.exists(os.path.join(os.getcwd(),file_name)):
			print 'File already exists'
			return
		print 'Downloading file %s '%file_name,'wb'
		#command='wget -c --read-timeout=50 --tries=3 -q --show-progress --no-check-certificate '
		try:
			#raise Exception('I know Python!')
			#print 'file download using wget'
			subprocess.call(['wget','-c','--read-timeout=50','--tries=3','-q','--show-progress','--no-check-certificate',url])
		except:
			#print 'file download using requests'
			self.file_download_using_requests(url);

	def file_download_using_youtube_dl(self,url):
		#command = 'youtube-dl -cit --embed-thumbnail --no-warnings --extract-audio --audio-quality 0 --audio-format mp3 '
		subprocess.call(['youtube-dl','-cit','--embed-thumbnail','--no-warnings','--extract-audio','--audio-quality', '0','--audio-format', 'mp3',url])


"""
song

Usage:
  song -d [--download-all] TEXT...
  song -h | --help
  song --version
  song -y TEXT...
  song --ty SONGFILE...
  song --td SONGFILE...

Options:
  -h --help                         Show this screen.
  --version                         Show version.

Examples:
  song -d wo lamhe

Help:
  For help using this tool, please open an issue on the Github repository:
  https://github.com/ankitmathur3193/song-cli
"""
from inspect import getmembers, isclass

from docopt import docopt

from __init__ import __version__ as VERSION

def main():
    """Main CLI entrypoint."""
    #print VERSION
    from commands.download import Download
    options = docopt(__doc__, version=VERSION)
    #print "You reached here"
    #print options
    print "working."
    p=Download(options)
    p.run()


import _hydra

def ReadingBloomFilter(filename, want_lock=False):
    """
    Create a read-only bloom filter with an upperbound of
    (num_elements, max_fp_prob) as a specification and using filename
    as the backing datastore.
    """
    with open('{}.desc'.format(filename), 'r') as descriptor:
        num_elements = int(descriptor.readline())
        max_fp_prob = float(descriptor.readline())
        ignore_case = int(descriptor.readline())

    return _hydra.BloomFilter.getFilter(
        num_elements, max_fp_prob,
        filename=filename, ignore_case=ignore_case,
        read_only=True, want_lock=want_lock)

def UpdatingBloomFilter(filename, want_lock=False, fdatasync_on_close=True):
    """
    Load an existing bloom filter in read-write mode using filename
    as the backing datastore.
    """
    with open('{}.desc'.format(filename), 'r') as descriptor:
        num_elements = int(descriptor.readline())
        max_fp_prob = float(descriptor.readline())
        ignore_case = int(descriptor.readline())

    return _hydra.BloomFilter.getFilter(
        num_elements, max_fp_prob,
        filename=filename, ignore_case=ignore_case,
        read_only=False, want_lock=want_lock,
        fdatasync_on_close=fdatasync_on_close)

def WritingBloomFilter(num_elements, max_fp_prob, filename=None,
                       ignore_case=False, want_lock=False,
                       fdatasync_on_close=True):
    """
    Create a read/write bloom filter with an upperbound of
    (num_elements, max_fp_prob) as a specification and using filename
    as the backing datastore.
    """
    new_filter = _hydra.BloomFilter.getFilter(
        num_elements, max_fp_prob,
        filename=filename, ignore_case=ignore_case,
        read_only=False, want_lock=want_lock,
        fdatasync_on_close=fdatasync_on_close)
    if filename:
        with open('{}.desc'.format(filename), 'w') as descriptor:
            descriptor.write("{}\n".format(num_elements))
            descriptor.write("{:0.8f}\n".format(max_fp_prob))
            descriptor.write("{:d}\n".format(ignore_case))
    return new_filter

# Expose the murmur hash
murmur_hash = _hydra.hash


"""
A Python wrapper for AQICN API.

The library can be used to search and retrieve Air Quality Index data.
Please refer to AQICN website to obtain token that must be used for access.
"""
import logging
import requests

API_ENDPOINT = 'https://api.waqi.info/'
API_ENDPOINT_SEARCH = API_ENDPOINT + 'search/'
API_ENDPOINT_OBS = API_ENDPOINT + 'feed/@%d/'
API_ENDPOINT_GEO = API_ENDPOINT + 'feed/geo:%d;%d/'

def findStationCodesByCity(city_name, token):
    """Lookup AQI database for station codes in a given city."""
    req = requests.get(
        API_ENDPOINT_SEARCH,
        params={
            'token': token,
            'keyword': city_name
        })

    if req.status_code == 200 and req.json()["status"] == "ok":
        return [result["uid"] for result in req.json()["data"]]
    else:
        return []

def get_location_observation(lat, lng, token):
    """Lookup observations by geo coordinates."""
    req = requests.get(
        API_ENDPOINT_GEO % (lat, lng),
        params={
            'token': token
        })

    if req.status_code == 200 and req.json()["status"] == "ok":
        return parse_observation_response(req.json()["data"])
    return {}

def parse_observation_response(json):
    """Decode AQICN observation response JSON into python object."""
    logging.debug(json)

    iaqi = json['iaqi']
    result = {
        'idx': json['idx'],
        'city': json.get('city', ''),
        'aqi': json['aqi'],
        'dominentpol': json.get("dominentpol", ''),
        'time': json['time']['s'],
        'iaqi': [{'p': item, 'v': iaqi[item]['v']} for item in iaqi]
    }

    return result

def get_station_observation(station_code, token):
    """Request station data for a specific station identified by code.

    A language parameter can also be specified to translate location
    information (default: "en")
    """
    req = requests.get(
        API_ENDPOINT_OBS % (station_code),
        params={
            'token': token
        })

    if req.status_code == 200 and req.json()['status'] == "ok":
        return parse_observation_response(req.json()['data'])
    else:
        return {}


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

import os
import re
from .utils import cached_property

class AssetAttributes(object):
    """Provides access to asset path properties. The attributes object is
    created with environment object and relative (or logical) asset path.

    Some properties may be useful or not, depending on the type of passed path.
    If it is a relative asset path, you can use all properties except
    :attr:`search_paths`. In case of a logical asset path it makes sense to
    use only those properties that are not related to processors and compressor.

    :param environment: an instance of :class:`~gears.environment.Environment`
                        class.
    :param path: a relative or logical path of the asset.
    """

    def __init__(self, environment, path):
        #: Used to access the registries of compilers, processors, etc.
        #: It can be also used by asset. See
        #: :class:`~gears.environment.Environment` for more information.
        self.environment = environment

        #: The relative (or logical) path to asset.
        self.path = path

        #: The relative path to the directory the asset.
        self.dirname = os.path.dirname(path)

    @cached_property
    def search_paths(self):
        """The list of logical paths which are used to search for an asset.
        This property makes sense only if the attributes was created with
        logical path.

        It is assumed that the logical path can be a directory containing a
        file named ``index`` with the same suffix.

        Example::

            >>> attrs = AssetAttributes(environment, 'js/app.js')
            >>> attrs.search_paths
            ['js/app.js', 'js/app/index.js']

            >>> attrs = AssetAttributes(environment, 'js/app/index.js')
            >>> attrs.search_paths
            ['js/models/index.js']
        """
        paths = [self.path]
        if os.path.basename(self.path_without_suffix) != 'index':
            path = os.path.join(self.path_without_suffix, 'index')
            paths.append(path + ''.join(self.suffix))
        return paths

    @cached_property
    def path_without_suffix(self):
        """The relative path to asset without suffix.
        Example::

            >>> attrs = AssetAttributes(environment, 'js/app.js')
            >>> attrs.path_without_suffix
            'js/app'
        """
        if self.suffix:
            return self.path[:-len(''.join(self.suffix))]
        return self.path

    @property
    def logical_path(self):
        """The logical path to asset.
        Example::

            >>> attrs = AssetAttributes(environment, 'js/models.js.coffee')
            >>> attrs.logical_path
            'js/models.js'
        """
        format_extension = self.format_extension or self.compiler_format_extension
        if format_extension is None:
            return self.path
        return self.path_without_suffix + format_extension

    @cached_property
    def extensions(self):
        """The list of asset extensions.
        Example::

            >>> attrs = AssetAttributes(environment, 'js/models.js.coffee')
            >>> attrs.extensions
            ['.js', '.coffee']

            >>> attrs = AssetAttributes(environment, 'js/lib/external.min.js.coffee')
            >>> attrs.format_extension
            ['.min', '.js', '.coffee']
        """
        return re.findall(r'\.[^.]+', os.path.basename(self.path))

    @cached_property
    def format_extension(self):
        """The format extension of asset.
        Example::

            >>> attrs = AssetAttributes(environment, 'js/models.js.coffee')
            >>> attrs.format_extension
            '.js'

            >>> attrs = AssetAttributes(environment, 'js/lib/external.min.js.coffee')
            >>> attrs.format_extension
            '.js'
        """
        for extension in reversed(self.extensions):
            compiler = self.environment.compilers.get(extension)
            if not compiler and self.environment.mimetypes.get(extension):
                return extension

    @cached_property
    def suffix(self):
        """The list of asset extensions starting from the first known extension.
        Example::

            >>> attrs = AssetAttributes(environment, 'js/lib/external.min.js.coffee')
            >>> attrs.suffix
            ['.js', '.coffee']
        """
        return self.extensions[len(self.unknown_extensions):]

    @cached_property
    def unknown_extensions(self):
        """The list of unknown extensions, which are actually parts of asset
        filename. Example::

            >>> attrs = AssetAttributes(environment, 'js/lib-2.0.min.js')
            >>> attrs.suffix
            ['.0', '.min']
        """
        unknown_extensions = []
        for extension in self.extensions:
            compiler = self.environment.compilers.get(extension)
            if compiler or self.environment.mimetypes.get(extension):
                return unknown_extensions
            unknown_extensions.append(extension)
        return unknown_extensions

    @cached_property
    def compiler_extensions(self):
        """The list of compiler extensions.
        Example::

            >>> attrs = AssetAttributes(environment, 'js/lib/external.min.js.coffee')
            >>> attrs.compiler_extensions
            ['.coffee']
        """
        try:
            index = self.extensions.index(self.format_extension)
        except ValueError:
            index = 0
        extensions = self.extensions[index:]
        return [e for e in extensions if self.environment.compilers.get(e)]

    @cached_property
    def compilers(self):
        """The list of compilers used to build asset."""
        return [self.environment.compilers.get(e) for e in self.compiler_extensions]

    @property
    def preprocessors(self):
        """The list of preprocessors used to build asset."""
        return self.environment.preprocessors.get(self.mimetype)

    @property
    def postprocessors(self):
        """The list of postprocessors used to build asset."""
        return self.environment.postprocessors.get(self.mimetype)

    @property
    def processors(self):
        """The list of all processors (preprocessors, compilers,
        postprocessors) used to build asset.
        """
        return self.preprocessors + list(reversed(self.compilers)) + self.postprocessors

    @property
    def compressor(self):
        """The compressors used to compress the asset."""
        return self.environment.compressors.get(self.mimetype)

    @cached_property
    def mimetype(self):
        """MIME type of the asset."""
        return (self.environment.mimetypes.get(self.format_extension) or
                self.compiler_mimetype or 'application/octet-stream')

    @cached_property
    def compiler_mimetype(self):
        """Implicit MIME type of the asset by its compilers."""
        for compiler in reversed(self.compilers):
            if compiler.result_mimetype:
                return compiler.result_mimetype
        return None

    @cached_property
    def compiler_format_extension(self):
        """Implicit format extension on the asset by its compilers."""
        for extension, mimetype in self.environment.mimetypes.items():
            if mimetype == self.compiler_mimetype:
                return extension
        return None