from __future__ import (print_function, division, unicode_literals)
import os
CG_PATH = os.path.dirname(os.path.abspath(__file__))
import sys
sys.path.insert(0, os.path.join(CG_PATH, '..'))
import glimpse
from glimpse.backports import *
from glimpse.imports import (np, pandas, re, datetime, sharedmem, cv2, shapely)
import glob
import requests
try:
    from functools import lru_cache
except ImportError:
    # Python 2
    from backports.functools_lru_cache import lru_cache
try:
    FileNotFoundError
except NameError:
    # Python 2
    FileNotFoundError = IOError

# ---- Environment variables ---

print('cg: Remember to set IMAGE_PATH, KEYPOINT_PATH, and MATCH_PATH')
IMAGE_PATH = None
KEYPOINT_PATH = None
MATCH_PATH = None
FLAT_IMAGE_PATH = False

# ---- Images ----

@lru_cache(maxsize=1)
def Sequences():
    """
    Return sequences metadata.
    """
    df = pandas.read_csv(
        os.path.join(CG_PATH, 'sequences.csv'),
        parse_dates=['first_time_utc', 'last_time_utc'])
    # Floor start time subseconds for comparisons to filename times
    df.first_time_utc = df.first_time_utc.apply(
        datetime.datetime.replace, microsecond=0)
    return df.sort_values('first_time_utc').reset_index(drop=True)

@lru_cache(maxsize=1)
def Stations():
    """
    Return stations metadata.
    """
    path = os.path.join(CG_PATH, 'geojson', 'stations.geojson')
    return glimpse.helpers.read_geojson(path, crs=32606, key='id')['features']

def _station_break_index(path):
    """
    Return index of image in motion break sequence.

    Arguments:
        path (str): Image path

    Returns:
        int: Either 0 (original viewdir) or i (viewdir of break i + 1)
    """
    stations = Stations()
    ids = parse_image_path(path)
    station = stations[ids['station']]
    if 'breaks' not in station['properties']:
        return 0
    breaks = station['properties']['breaks']
    if not breaks:
        return 0
    break_images = np.array([x['start'] for x in breaks])
    idx = np.argsort(break_images)
    i = np.where(break_images[idx] <= ids['basename'])[0]
    if i.size > 0:
        return idx[i[-1]] + 1
    else:
        return 0

def paths_to_datetimes(paths):
    """
    Return datetime objects parsed from image paths.

    Arguments:
        paths (iterable): Image paths
    """
    pattern = re.compile(r'_([0-9]{8}_[0-9]{6})[^\/]*$')
    datetimes_str = [pattern.findall(path)[0] for path in paths]
    return pandas.to_datetime(datetimes_str, format='%Y%m%d_%H%M%S').to_pydatetime()

def parse_image_path(path, sequence=False):
    """
    Return metadata parsed from image path.

    Arguments:
        path (str): Image path or basename
        sequence (bool): Whether to include sequence metadata (camera, service, ...)
    """
    basename = glimpse.helpers.strip_path(path)
    station, date_str, time_str = re.findall('^([^_]+)_([0-9]{8})_([0-9]{6})', basename)[0]
    capture_time = datetime.datetime.strptime(date_str + time_str, '%Y%m%d%H%M%S')
    results = dict(basename=basename, station=station,
        date_str=date_str, time_str=time_str, datetime=capture_time)
    if sequence:
        sequences = Sequences()
        is_row = ((sequences.station == station) &
            (sequences.first_time_utc <= capture_time) &
            (sequences.last_time_utc >= capture_time))
        rows = np.where(is_row)[0]
        if len(rows) != 1:
            raise ValueError(
                'Image path has zero or multiple sequence matches: ' + path)
        results = glimpse.helpers.merge_dicts(
            sequences.loc[rows[0]].to_dict(), results)
    return results

def find_image(path):
    """
    Return path to image file.

    Arguments:
        path (str): Image path or basename
    """
    ids = parse_image_path(path, sequence=True)
    filename = ids['basename'] + '.JPG'
    if FLAT_IMAGE_PATH:
        img_path = os.path.join(IMAGE_PATH, filename)
        if os.path.isfile(img_path):
            return img_path
        else:
            raise ValueError('Image not found: ' + path)
    else:
        service_dir = os.path.join(IMAGE_PATH, ids['station'],
            ids['station'] + '_' + ids['service'])
        found_img = None
        if os.path.isdir(service_dir):
            subdirs = [''] + next(os.walk(service_dir))[1]
            for subdir in subdirs:
                img_path = os.path.join(service_dir, subdir, filename)
                if os.path.isfile(img_path):
                    found_img = img_path
                    break
        if found_img:
            return found_img
        else:
            raise ValueError('Image not found: ' + path)

def load_images(station, services, use_exif=False, service_exif=False, anchors=False,
    viewdir=True, viewdir_as_anchor=False, file_errors=True, **kwargs):
    """
    Return list of calibrated Image objects.

    Any available station, camera, image, and viewdir calibrations are loaded
    and images with image calibrations are marked as anchors.

    Arguments:
        station (str): Station identifier
        services (iterable): Service identifiers
        use_exif (bool): Whether to parse image datetimes from EXIF (slower)
            rather than parsed from paths (faster)
        service_exif (bool): Whether to extract EXIF from first image (faster)
            or all images (slower) in service.
            If `True`, `Image.datetime` is parsed from path.
            Always `False` if `use_exif=True`.
        anchors (bool): Whether to include anchor images even if
            filtered out by `kwargs['snap']`
        **kwargs: Arguments to `glimpse.helpers.select_datetimes()`
    """
    if use_exif:
        service_exif = False
    # Sort services in time
    if isinstance(services, str):
        services = services,
    services = np.sort(services)
    # Parse datetimes of all candidate images
    paths_service = [glob.glob(os.path.join(IMAGE_PATH, station, station + '_' + service, '*.JPG'))
        for service in services]
    paths = np.hstack(paths_service)
    basenames = [glimpse.helpers.strip_path(path) for path in paths]
    if use_exif:
        exifs = [glimpse.Exif(path) for path in paths]
        datetimes = np.array([exif.datetime for exif in exifs])
    else:
        datetimes = paths_to_datetimes(basenames)
    # Select images based on datetimes
    indices = glimpse.helpers.select_datetimes(datetimes, **kwargs)
    if anchors:
        # Add anchors
        # HACK: Ignore any <image>-<suffix>.json files
        anchor_paths = glob.glob(os.path.join(CG_PATH, 'images', station + '_*[0-9].json'))
        anchor_basenames = [glimpse.helpers.strip_path(path) for path in anchor_paths]
        if 'start' in kwargs or 'end' in kwargs:
            # Filter by start, end
            anchor_datetimes = np.asarray(paths_to_datetimes(anchor_basenames))
            inrange = glimpse.helpers.select_datetimes(
                anchor_datetimes, **glimpse.helpers.merge_dicts(kwargs, dict(snap=None)))
            anchor_basenames = np.asarray(anchor_basenames)[inrange]
        anchor_indices = np.where(np.isin(basenames, anchor_basenames))[0]
        indices = np.unique(np.hstack((indices, anchor_indices)))
    service_breaks = np.hstack((0, np.cumsum([len(x) for x in paths_service])))
    station_calibration = load_calibrations(
        station_estimate=station, station=station, merge=True, file_errors=False)
    images = []
    for i, service in enumerate(services):
        index = indices[(indices >= service_breaks[i]) & (indices < service_breaks[i + 1])]
        if not index.size:
            continue
        service_calibration = glimpse.helpers.merge_dicts(
            station_calibration,
            load_calibrations(path=paths[index[0]], camera=True, merge=True,
            file_errors=file_errors))
        if service_exif:
            exif = glimpse.Exif(paths[index[0]])
        for j in index:
            basename = basenames[j]
            calibrations = load_calibrations(image=basename,
                viewdir=basename if viewdir else False,
                station_estimate=station, merge=False, file_errors=False)
            if calibrations['image']:
                calibration = glimpse.helpers.merge_dicts(
                    service_calibration, calibrations['image'])
                anchor = True
            else:
                calibration = glimpse.helpers.merge_dicts(
                    service_calibration,
                    dict(viewdir=calibrations['station_estimate']['viewdir']))
                anchor = False
            if viewdir and calibrations['viewdir']:
                calibration = glimpse.helpers.merge_dicts(
                    calibration, calibrations['viewdir'])
                if viewdir_as_anchor:
                    anchor = True
            if use_exif:
                exif = exifs[j]
            elif not service_exif:
                exif = None
            if KEYPOINT_PATH:
                keypoint_path = os.path.join(KEYPOINT_PATH, basename + '.pkl')
            else:
                keypoint_path = None
            image = glimpse.Image(
                path=paths[j], cam=calibration, anchor=anchor, exif=exif,
                datetime=None if use_exif else datetimes[j],
                keypoints_path=keypoint_path)
            images.append(image)
    return images

def load_masks(images):
    """
    Return a list of boolean land masks.

    Images must all be from the same station.

    Arguments:
        images (iterable): Image objects
    """
    # All images must be from the same station (for now)
    station = parse_image_path(images[0].path)['station']
    pattern = re.compile(station + r'_[0-9]{8}_[0-9]{6}[^\/]*$')
    is_station = [pattern.search(img.path) is not None for img in images[1:]]
    assert all(is_station)
    # Find all station svg with 'land' markup
    imgsz = images[0].cam.imgsz
    svg_paths = glob.glob(os.path.join(CG_PATH, 'svg', station + '_*.svg'))
    markups = [glimpse.svg.parse_svg(path, imgsz=imgsz) for path in svg_paths]
    land_index = np.where(['land' in markup for markup in markups])[0]
    if len(land_index) == 0:
        raise ValueError('No land masks found for station')
    svg_paths = np.array(svg_paths)[land_index]
    land_markups = np.array(markups)[land_index]
    # Select svg files nearest to images, with preference within breaks
    svg_datetimes = paths_to_datetimes(svg_paths)
    svg_break_indices = np.array([_station_break_index(path)
        for path in svg_paths])
    img_datetimes = [img.datetime for img in images]
    distances = glimpse.helpers.pairwise_distance_datetimes(
        img_datetimes, svg_datetimes)
    nearest_index = []
    for i, img in enumerate(images):
        break_index = _station_break_index(img.path)
        same_break = np.where(break_index == svg_break_indices)[0]
        if same_break.size > 0:
            i = same_break[np.argmin(distances[i][same_break])]
        else:
            raise ValueError('No mask found within motion breaks for image', i)
            i = np.argmin(distances[i])
        nearest_index.append(i)
    nearest = np.unique(nearest_index)
    # Make masks and expand per image without copying
    masks = [None] * len(images)
    image_sizes = np.array([img.cam.imgsz for img in images])
    sizes = np.unique(image_sizes, axis=0)
    for i in nearest:
        polygons = land_markups[i]['land'].values()
        is_nearest = nearest_index == i
        for size in sizes:
            scale = size / imgsz
            rpolygons = [polygon * scale for polygon in polygons]
            mask = glimpse.helpers.polygons_to_mask(rpolygons, size=size).astype(np.uint8)
            mask = sharedmem.copy(mask)
            for j in np.where(is_nearest & np.all(image_sizes == size, axis=1))[0]:
                masks[j] = mask
    return masks

# ---- Calibration controls ----

def svg_controls(img, svg=None, keys=None, correction=True, step=None):
    """
    Return control objects for an Image.

    Arguments:
        img (Image): Image object
        svg: Path to SVG file (str) or parsed result (dict).
            If `None`, looks for SVG file 'svg/<image>.svg'.
        keys (iterable): SVG layers to include, or all if `None`
        correction: Whether control objects should use elevation correction (bool)
            or arguments to `glimpse.helpers.elevation_corrections()`
    """
    if svg is None:
        basename = parse_image_path(img.path)['basename']
        svg = os.path.join(CG_PATH, 'svg', basename + '.svg')
    controls = []
    if isinstance(svg, (bytes, str)):
        if not os.path.isfile(svg):
            return controls
        svg = glimpse.svg.parse_svg(svg, imgsz=img.cam.imgsz)
    if keys is None:
        keys = svg.keys()
    for key in keys:
        if key in svg:
            if key == 'gcp':
                controls.append(gcp_points(img, svg[key], correction=correction))
            elif key == 'coast':
                controls.append(coast_lines(img, svg[key], correction=correction, step=step))
            elif key == 'terminus':
                controls.append(terminus_lines(img, svg[key], correction=correction, step=step))
            elif key == 'moraines':
                controls.extend(moraines_mlines(img, svg[key], correction=correction, step=step))
            elif key == 'horizon':
                controls.append(horizon_lines(img, svg[key], correction=correction, step=step))
    return controls

def gcp_points(img, markup, correction=True):
    """
    Return ground control Points object for an Image.

    Arguments:
        img (Image): Image object
        markup (dict): Parsed SVG layer
        correction: Whether Points should use elevation correction (bool)
            or arguments to `glimpse.helpers.elevation_corrections()`
    """
    uv = np.vstack(markup.values())
    geo = glimpse.helpers.read_geojson(
        os.path.join(CG_PATH, 'geojson', 'gcp.geojson'), key='id', crs=32606)
    xyz = np.vstack((geo['features'][key]['geometry']['coordinates']
        for key in markup))
    return glimpse.optimize.Points(img.cam, uv, xyz, correction=correction)

def coast_lines(img, markup, correction=True, step=None):
    """
    Return coast Lines object for an Image.

    Arguments:
        img (Image): Image object
        markup (dict): Parsed SVG layer
        correction (bool): Whether to set Lines to use elevation correction
    """
    luv = tuple(markup.values())
    geo = glimpse.helpers.read_geojson(
        os.path.join(CG_PATH, 'geojson', 'coast.geojson'), crs=32606)
    lxy = [feature['geometry']['coordinates'] for feature in geo['features']]
    lxyz = [np.hstack((xy, sea_height(xy, t=img.datetime))) for xy in lxy]
    return glimpse.optimize.Lines(img.cam, luv, lxyz, correction=correction, step=step)

def terminus_lines(img, markup, correction=True, step=None):
    """
    Return terminus Lines object for an Image.

    Arguments:
        img (Image): Image object
        markup (dict): Parsed SVG layer
        correction: Whether Lines should use elevation correction (bool)
            or arguments to `glimpse.helpers.elevation_corrections()`
    """
    luv = tuple(markup.values())
    # HACK: Select terminus with matching date and preferred type
    termini = Termini()
    date_str = img.datetime.strftime('%Y-%m-%d')
    features = [(feature, feature['properties']['type'])
        for feature in termini
        if feature['properties']['date'] == date_str]
    type_order = ('aerometric', 'worldview', 'landsat-8', 'landsat-7', 'terrasar', 'tandem', 'arcticdem', 'landsat-5')
    order = [type_order.index(f[1]) for f in features]
    xy = features[np.argmin(order)[0]]['geometry']['coordinates']
    xyz = np.hstack((xy, sea_height(xy, t=img.datetime)))
    return glimpse.optimize.Lines(img.cam, luv, [xyz], correction=correction, step=step)

def horizon_lines(img, markup, correction=True, step=None):
    """
    Return horizon Lines object for an Image.

    Arguments:
        img (Image): Image object
        markup (dict): Parsed SVG layer
        correction: Whether Lines should use elevation correction (bool)
            or arguments to `glimpse.helpers.elevation_corrections()`
    """
    luv = tuple(markup.values())
    station = parse_image_path(img.path)['station']
    geo = glimpse.helpers.read_geojson(
        os.path.join(CG_PATH, 'geojson', 'horizons', station + '.geojson'), crs=32606)
    lxyz = [coords for coords in glimpse.helpers.geojson_itercoords(geo)]
    return glimpse.optimize.Lines(img.cam, luv, lxyz, correction=correction, step=step)

def moraines_mlines(img, markup, correction=True, step=None):
    """
    Return list of moraine Lines objects for an Image.

    Arguments:
        img (Image): Image object
        markup (dict): Parsed SVG layer
        correction: Whether Lines should use elevation correction (bool)
            or arguments to `glimpse.helpers.elevation_corrections()`
    """
    date_str = img.datetime.strftime('%Y%m%d')
    geo = glimpse.helpers.read_geojson(
        os.path.join(CG_PATH, 'geojson', 'moraines', date_str + '.geojson'), key='id', crs=32606)
    mlines = []
    for key, moraine in markup.items():
        luv = tuple(moraine.values())
        xyz = geo['features'][key]['geometry']['coordinates']
        mlines.append(glimpse.optimize.Lines(img.cam, luv, [xyz], correction=correction, step=step))
    return mlines

def tide_height(t):
    if isinstance(t, datetime.datetime):
        t = [t]
    t = np.asarray(t)
    dt = datetime.timedelta(hours=1.5)
    t_begin = np.nanmin(t).replace(minute=0, second=0, microsecond=0)
    t_end = np.nanmax(t) + dt
    # https://tidesandcurrents.noaa.gov/api/
    params = dict(
        format='json',
        units='metric',
        time_zone='gmt',
        datum='MSL',
        product='hourly_height',
        station=9454240, # Valdez
        begin_date=t_begin.strftime('%Y%m%d %H:%M'),
        end_date=t_end.strftime('%Y%m%d %H:%M'))
    r = requests.get('https://tidesandcurrents.noaa.gov/api/datagetter', params=params)
    v = [float(item['v']) for item in r.json()['data']]
    return np.interp(
        [dti.total_seconds() for dti in t - t_begin],
        np.linspace(0, 3600 * len(v[1:]), len(v)), v)

def sea_height(xy, t=None):
    """
    Return the height of sea level relative to the WGS 84 ellipsoid.

    Uses the EGM 2008 geoid height and the NOAA tide gauge in Valdez, Alaska.

    Arguments:
        xy (array): World coordinates (n, 2)
        t (datetime): Datetime at which to estimate tidal height.
            If `None`, tide is ignored in result.
    """
    egm2008 = glimpse.Raster.read(os.path.join(CG_PATH, 'egm2008.tif'))
    geoid = egm2008.sample(xy).reshape(-1, 1)
    if t is not None:
        if not isinstance(t, datetime.datetime) and len(tide) > 1:
            raise ValueError('t must specify a single datetime')
        tide = tide_height(t)[0]
    else:
        tide = 0
    return geoid + tide

def synth_controls(img, step=None, directions=False):
    image = glimpse.helpers.strip_path(img.path)
    basename = os.path.join(CG_PATH, 'svg-synth', image)
    controls = []
    # Load svg
    path = basename + '.svg'
    if os.path.isfile(path):
        svg = glimpse.svg.parse_svg(path, imgsz=img.cam.imgsz)
        if 'points' in svg or 'lines' in svg or 'points-auto' in svg:
            scam = glimpse.helpers.read_json(basename + '-synth.json')
            simg = glimpse.Image(basename + '-synth.JPG', cam=scam)
            if not directions:
                depth = glimpse.Raster.read(basename + '-depth.tif')
                scale = depth.n / img.cam.imgsz
            # NOTE: Ignoring parallax potential
        if 'points-auto' in svg:
            # Length-2 paths traced from image to synthetic image
            uv = np.vstack([x[0] for x in svg['points-auto'].values()])
            suv = np.vstack([x[1] for x in svg['points-auto'].values()])
            d = 1 if directions else depth.sample(suv * scale)
            xyz = simg.cam.invproject(suv, directions=directions, depth=d)
            points = glimpse.optimize.Points(cam=img.cam, uv=uv, xyz=xyz,
                directions=directions, correction=False)
            controls.append(points)
        if 'points' in svg:
            # Length-2 paths traced from image to synthetic image
            uv = np.vstack([x[0] for x in svg['points'].values()])
            suv = np.vstack([x[1] for x in svg['points'].values()])
            d = 1 if directions else depth.sample(suv * scale)
            xyz = simg.cam.invproject(suv, directions=directions, depth=d)
            points = glimpse.optimize.Points(cam=img.cam, uv=uv, xyz=xyz,
                directions=directions, correction=False)
            controls.append(points)
        if 'lines' in svg:
            for layer in svg['lines'].values():
                # Group with paths named 'image*' and 'synth*'
                uvs = [layer[key] for key in layer if key.find('image') == 0]
                suvs = [layer[key] for key in layer if key.find('synth') == 0]
                depths = [1 if directions else depth.sample(suv * scale)
                    for suv in suvs]
                xyzs = [simg.cam.invproject(suv, directions=directions, depth=d)
                    for suv, d in zip(suvs, depths)]
                lines = glimpse.optimize.Lines(cam=img.cam, uvs=uvs, xyzs=xyzs,
                    step=step, directions=directions, correction=False)
                controls.append(lines)
    return controls

# ---- Control bundles ----

def station_svg_controls(station, size=1, force_size=False, keys=None,
    svgs=None, correction=True, step=None, station_calib=False, camera_calib=True,
    synth=True):
    """
    Return all SVG control objects for a station.

    Arguments:
        station (str): Station identifier
        size: Image scale factor (number) or image size in pixels (nx, ny)
        force_size (bool): Whether to force `size` even if different aspect ratio
            than original size.
        keys (iterable): SVG layers to include
        correction: Whether control objects should use elevation correction (bool)
            or arguments to `glimpse.helpers.elevation_corrections()`
        station_calib (bool): Whether to load station calibration. If `False`,
            falls back to the station estimate.
        camera_calib (bool): Whether to load camera calibrations. If `False`,
            falls back to the EXIF estimate.

    Returns:
        list: Image objects
        list: Control objects (Points, Lines)
        list: Per-camera calibration parameters [{'viewdir': True}, ...]
    """
    paths = glob.glob(os.path.join(CG_PATH, 'svg', station + '*.svg'))
    if synth:
        paths += glob.glob(os.path.join(CG_PATH, 'svg-synth', station + '*.pkl'))
        paths += glob.glob(os.path.join(CG_PATH, 'svg-synth', station + '*.svg'))
    basenames = np.unique([glimpse.helpers.strip_path(path) for path in paths])
    images, controls, cam_params = [], [], []
    for basename in basenames:
        calibration = load_calibrations(basename, camera=camera_calib,
            station=station_calib, station_estimate=not station_calib, merge=True)
        img_path = find_image(basename)
        img = glimpse.Image(img_path, cam=calibration)
        control = []
        if svgs is None or basename in svgs:
            control += svg_controls(img, keys=keys, correction=correction, step=step)
        if synth:
            control += synth_controls(img, step=None, directions=False)
        if control:
            for x in control:
                x.resize(size, force=force_size)
            images.append(img)
            controls.extend(control)
            cam_params.append(dict(viewdir=True))
    return images, controls, cam_params

def camera_svg_controls(camera, size=1, force_size=False, keys=None,
    svgs=None, correction=True, step=None, station_calib=False,
    camera_calib=False, synth=True):
    """
    Return all SVG control objects available for a camera.

    Arguments:
        camera (str): Camera identifer
        size: Image scale factor (number) or image size in pixels (nx, ny)
        force_size (bool): Whether to force `size` even if different aspect ratio
            than original size.
        keys (iterable): SVG layers to include
        svgs (iterable): SVG basenames to include
        correction: Whether control objects should use elevation correction (bool)
            or arguments to `glimpse.helpers.elevation_corrections()`
        station_calib (bool): Whether to load station calibration. If `False`,
            falls back to the station estimate.
        camera_calib (bool): Whether to load camera calibrations. If `False`,
            falls back to the EXIF estimate.

    Returns:
        list: Image objects
        list: Control objects (Points, Lines)
        list: Per-camera calibration parameters [{'viewdir': True}, ...]
    """
    paths = glob.glob(os.path.join(CG_PATH, 'svg', '*.svg'))
    if synth:
        paths += glob.glob(os.path.join(CG_PATH, 'svg-synth', '*.pkl'))
        paths += glob.glob(os.path.join(CG_PATH, 'svg-synth', '*.svg'))
    basenames = np.unique([glimpse.helpers.strip_path(path) for path in paths])
    images, controls, cam_params = [], [], []
    for basename in basenames:
        ids = parse_image_path(basename, sequence=True)
        if ids['camera'] == camera:
            calibration = load_calibrations(basename, camera=camera_calib,
                station=station_calib, station_estimate=not station_calib, merge=True)
            img_path = find_image(basename)
            img = glimpse.Image(img_path, cam=calibration)
            control = []
            if svgs is None or basename in svgs:
                control += svg_controls(img, keys=keys, correction=correction, step=step)
            if synth:
                control += synth_controls(img, step=None, directions=False)
            if control:
                for x in control:
                    x.resize(size, force=force_size)
                images.append(img)
                controls.extend(control)
                cam_params.append(dict(viewdir=True))
    return images, controls, cam_params

def camera_motion_matches(camera, size=None, force_size=False,
    station_calib=False, camera_calib=False):
    """
    Returns all motion Matches objects available for a camera.

    Arguments:
        camera (str): Camera identifier
        size: Image scale factor (number) or image size in pixels (nx, ny)
        force_size (bool): Whether to force `size` even if different aspect ratio
            than original size.
        station_calib (bool): Whether to load station calibration. If `False`,
            falls back to the station estimate.
        camera_calib (bool): Whether to load camera calibrations. If `False`,
            falls back to the EXIF estimate.

    Returns:
        list: Image objects
        list: Matches objects
        list: Per-camera calibration parameters [{}, {'viewdir': True}, ...]
    """
    motion = glimpse.helpers.read_json(os.path.join(CG_PATH, 'motion.json'))
    sequences = [item['paths'] for item in motion
        if parse_image_path(item['paths'][0], sequence=True)['camera'] == camera]
    all_images, all_matches, cam_params = [], [], []
    for sequence in sequences:
        paths = [find_image(path) for path in sequence]
        cams = [load_calibrations(path,  camera=camera_calib,
            station=station_calib, station_estimate=not station_calib, merge=True)
            for path in paths]
        images = [glimpse.Image(path, cam=cam)
            for path, cam in zip(paths, cams)]
        matches = [load_motion_match(images[i], images[i + 1])
            for i in range(len(sequence) - 1)]
        if size is not None:
            for match in matches:
                match.resize(size, force=force_size)
        all_images.extend(images)
        all_matches.extend(matches)
        cam_params.extend([dict()] + [dict(viewdir=True)] * (len(sequence) - 1))
    return all_images, all_matches, cam_params

def build_sequential_matches(images, detect=dict(), match=dict()):
    """
    Returns Matches objects for sequential Image pairs.

    Arguments:
        images (iterable): Image objects
        detect (dict): Arguments passed to `glimpse.optimize.detect_keypoints()`
        match (dict): Arguments passed to `glimpse.optimize.match_keypoints()`
    """
    keypoints = [glimpse.optimize.detect_keypoints(img.read(), **detect) for img in images]
    matches = []
    for i in range(len(images) - 1):
        uvA, uvB = glimpse.optimize.match_keypoints(keypoints[i], keypoints[i + 1], **match)
        matches.append(glimpse.optimize.Matches(
            cams=(images[i].cam, images[i + 1].cam), uvs=(uvA, uvB)))
    return matches

def load_motion_match(imgA, imgB):
    """
    Returns motion Matches object for an Image pair.

    Arguments:
        imgA (Image): Image object
        imgB (Image): Image object
    """
    basename = glimpse.helpers.strip_path(imgA.path) + '-' + glimpse.helpers.strip_path(imgB.path)
    path = os.path.join(CG_PATH, 'motion', basename + '.pkl')
    match = glimpse.helpers.read_pickle(path)
    match.cams = (imgA.cam, imgB.cam)
    return match

# ---- Calibrations ----

def load_calibrations(path=None, station_estimate=False, station=False,
    camera=False, image=False, viewdir=False, merge=False, file_errors=True):
    """
    Return camera calibrations.

    Arguments:
        path (str): Image basename or path
        station_estimate: Whether to load station estimate (bool) or
            station identifier to load (str).
            If `True`, the station identifier is parsed from `path`.
            If `path` or `image` specified, `viewdir` is based on the position
            of the image in the motion break sequence.
        station: Whether to load station (bool) or
            station identifier to load (str).
            If `True`, the station identifier is parsed from `path`.
            viewdir is loaded from station_estimate.
        camera: Whether to load camera (bool) or
            camera identifier to load (str).
            If `True`, the camera identifier is parsed from `path`.
        image: Whether to load image (bool) or
            image to load (str).
            If `True`, the image basename is parsed from `path`.
        viewdir: Whether to load view direction (bool) or
            view direction to load (str).
            If `True`, the image basename is parsed from `path`.
        merge (bool): Whether to merge calibrations, in the order
            station_estimate, station, camera, image, viewdir
        file_errors (bool): Whether to raise an error if a requested calibration
            file is not found
    """
    def _try_except(fun, arg, **kwargs):
        try:
            return fun(arg, **kwargs)
        except FileNotFoundError as e:
            if file_errors:
                raise e
            else:
                return None
    if path:
        ids = parse_image_path(path, sequence=(camera is True))
        if station_estimate is True:
            station_estimate = ids['station']
        if station is True:
            station = ids['station']
        if camera is True:
            camera = ids['camera']
        if image is True:
            image = ids['basename']
        if viewdir is True:
            viewdir = ids['basename']
    calibrations = dict()
    if station_estimate:
        img_path = image if isinstance(image, str) else path if path else None
        calibrations['station_estimate'] = _try_except(_load_station_estimate, station_estimate, path=img_path)
    if station:
        calibrations['station'] = _try_except(_load_station_estimate, station, path=path)
        station_calib = _try_except(_load_station, station)
        if station_calib and 'xyz' in station_calib:
            calibrations['station']['xyz'] = station_calib['xyz']
    if camera:
        calibrations['camera'] = _try_except(_load_camera, camera)
    if image:
        calibrations['image'] = _try_except(_load_image, image)
    if viewdir:
        calibrations['viewdir'] = _try_except(_load_viewdir, viewdir)
    if merge:
        return merge_calibrations(calibrations)
    else:
        return calibrations

def merge_calibrations(calibrations, keys=('station_estimate', 'station', 'camera', 'image', 'viewdir')):
    """
    Merge camera calibrations.

    Arguments:
        calibrations (iterable): Dictionaries of calibration parameters
        keys (iterable): Calibration types, in order from lowest to highest
            overwrite priority
    """
    calibration = dict()
    for key in keys:
        if key in calibrations and calibrations[key]:
            calibration = glimpse.helpers.merge_dicts(calibration, calibrations[key])
    return calibration

def _load_station_estimate(station, path=None):
    stations = Stations()
    feature = stations[station]
    viewdir = feature['properties']['viewdir']
    i = _station_break_index(path) if path else 0
    if i and 'viewdir' in feature['properties']['breaks'][i - 1]:
        viewdir = feature['properties']['breaks'][i - 1]['viewdir']
    return dict(
        xyz=np.reshape(feature['geometry']['coordinates'], -1),
        viewdir=viewdir)

def _load_station(station):
    station_path = os.path.join(CG_PATH, 'stations', station + '.json')
    return glimpse.helpers.read_json(station_path)

def _load_camera(camera):
    camera_path = os.path.join(CG_PATH, 'cameras', camera + '.json')
    return glimpse.helpers.read_json(camera_path)

def _load_image(path):
    basename = glimpse.helpers.strip_path(path)
    image_path = os.path.join(CG_PATH, 'images', basename + '.json')
    return glimpse.helpers.read_json(image_path)

def _load_viewdir(path):
    basename = glimpse.helpers.strip_path(path)
    viewdir_path = os.path.join(CG_PATH, 'viewdirs', basename + '.json')
    return glimpse.helpers.read_json(viewdir_path)

def write_image_viewdirs(images, viewdirs=None):
    """
    Write Image view directions to file.

    Arguments:
        images (iterable): Image objects
        viewdirs (iterable): Camera view directions to write.
            If `None`, these are read from `images[i].cam.viewdir`.
    """
    for i, img in enumerate(images):
        basename = glimpse.helpers.strip_path(img.path)
        path = os.path.join(CG_PATH, 'viewdirs', basename + '.json')
        if viewdirs is None:
            d = dict(viewdir=tuple(img.cam.viewdir))
        else:
            d = dict(viewdir=tuple(viewdirs[i]))
        glimpse.helpers.write_json(d, path=path)

# ---- Tracking ----

@lru_cache(maxsize=1)
def Termini():
    """
    Return terminus traces.
    """
    path = os.path.join(CG_PATH, 'geojson', 'termini.geojson')
    geo = glimpse.helpers.read_geojson(path, crs=32606)['features']
    # Check that all termini run west to east
    bad = []
    for f in geo:
        x_start = f['geometry']['coordinates'][0, 0]
        x_end = f['geometry']['coordinates'][-1, 0]
        if x_start > x_end:
            bad.append((f['properties']['date'], f['properties']['type']))
    if bad:
        raise ValueError('Some termini traced east to west:' + str(bad))
    # Sort by date, then type
    geo.sort(key=lambda x: (x['properties']['date'], x['properties']['type']))
    return geo

@lru_cache(maxsize=1)
def Glacier():
    """
    Return the maximal glacier extent.
    """
    path = os.path.join(CG_PATH, 'geojson', 'glacier.geojson')
    geo = glimpse.helpers.read_geojson(path, crs=32606)
    return geo['features'][0]['geometry']['coordinates'][0]

@lru_cache(maxsize=1)
def Coast():
    """
    Return the forebay coastlines.
    """
    path = os.path.join(CG_PATH, 'geojson', 'coast.geojson')
    geo = glimpse.helpers.read_geojson(path, crs=32606, key='id')
    return {key: geo['features'][key]['geometry']['coordinates'] for key in geo['features']}

@lru_cache(maxsize=1)
def Forebay():
    """
    Return the forebay polygon.
    """
    path = os.path.join(CG_PATH, 'geojson', 'forebay.geojson')
    geo = glimpse.helpers.read_geojson(path, crs=32606)
    return geo['features'][0]['geometry']['coordinates'][0]

def parse_dem_path(path):
    """
    Return datetime and type from DEM path.
    """
    datestr = re.findall(r'([0-9]{8})', glimpse.helpers.strip_path(path))[0]
    # HACK: Aerial and satellite imagery taken around local noon (~ 22:00 UTC)
    t = datetime.datetime.strptime(datestr + str(22), '%Y%m%d%H')
    typestr = re.findall(r'dem-([^\/]+)', path)[0]
    return dict(datetime=t, type=typestr)

def get_dem_terminus(t, demtype):
    """
    Return the terminus corresponding to a DEM path.
    """
    termini = Termini()
    termini_keys = [(f['properties']['date'], f['properties']['type'])
        for f in termini]
    i = termini_keys.index((t.strftime('%Y-%m-%d'), demtype))
    return termini[i]['geometry']['coordinates']

def get_nearest_terminus(t):
    """
    Return the terminus nearest a datetime.
    """
    types = ('aerometric', 'arcticdem', 'ifsar', 'tandem',
        'landsat-8', 'landsat-7', 'terrasar')
    termini = [f for f in Termini() if
        len(f['properties']['date']) == 10 and
        f['properties']['type'] in types]
    termini.sort(key=lambda x: (x['properties']['date'],
        types.index(x['properties']['type'])))
    datetimes = [datetime.datetime.strptime(
        f['properties']['date'] + '22', '%Y-%m-%d%H') for f in termini]
    dt = np.abs(np.array(datetimes) - t)
    i = np.where(np.min(dt) == dt)[0][0]
    return termini[i]['geometry']['coordinates']

def clip_terminus_with_coast(line):
    """
    Clip a terminus with the west and east coastlines.
    """
    # Convert to shapely format
    coast = Coast()
    cline_west = shapely.geometry.LineString(coast['west'])
    cline_east = shapely.geometry.LineString(coast['east'])
    tline = shapely.geometry.LineString(line)
    # Cut terminus at west coastline
    tline = shapely.ops.split(tline, cline_west.buffer(distance=100))[-1]
    # Cut terminus at east coastline
    tline = shapely.ops.split(tline, cline_east.buffer(distance=100))[0]
    return np.asarray(tline.coords)

def clip_glacier_with_terminus(line):
    """
    Clip glacier extent with a terminus.
    """
    gpoly = shapely.geometry.Polygon(shell=Glacier())
    # Extend western edge past polygon boundary
    wpoint = shapely.geometry.Point(line[0])
    west_snaps = shapely.ops.nearest_points(wpoint, gpoly.exterior)
    if west_snaps[0] != west_snaps[1]:
        new_west = west_snaps[1].coords
        d = new_west - line[0]
        d /= np.linalg.norm(d)
        line = np.row_stack((new_west + d, line))
    # Extend eastern edge past polygon boundary
    epoint = shapely.geometry.Point(line[-1])
    east_snaps = shapely.ops.nearest_points(epoint, gpoly.exterior)
    if east_snaps[0] != east_snaps[1]:
        new_east = east_snaps[1].coords
        d = new_east - line[-1]
        d /= np.linalg.norm(d)
        line = np.row_stack((line, new_east + d))
    tline = shapely.geometry.LineString(line)
    # Split glacier at terminus
    splits = shapely.ops.split(gpoly, tline)
    if len(splits) < 2:
        raise ValueError('Glacier polygon not split by terminus')
    else:
        areas = [split.area for split in splits]
        return np.asarray(splits[np.argmax(areas)].exterior.coords)

def load_glacier_polygon(t, demtype=None):
    """
    Return the glacier extent at a datetime.
    """
    if demtype is not None:
        line = get_dem_terminus(t, demtype=demtype)
    else:
        line = get_nearest_terminus(t)
    cline = clip_terminus_with_coast(line)
    return clip_glacier_with_terminus(cline)

def load_forebay_polygon(glacier):
    """
    Return the forebay extent for a given glacier extent.
    """
    gpoly = shapely.geometry.Polygon(shell=glacier)
    fpoly = shapely.geometry.Polygon(shell=Forebay())
    diff = fpoly.difference(gpoly)
    return np.asarray(diff.exterior.coords)

def intersect_polygons(polygons):
    """
    Return intersection of polygons.
    """
    shapes = [shapely.geometry.Polygon(shell=xy) for xy in polygons]
    shape = shapes[0]
    for i in range(1, len(shapes)):
        shape = shape.intersection(shapes[i])
    if np.iterable(shape):
        i = np.argmax([poly.area for poly in shape])
        shape = shape[i]
    return np.asarray(shape.exterior.coords)

def select_track_points(xy, images, polygon, dem, max_distance):
    """
    Return track points mask for a set of starting images.

    Returns:
        array: Coordinates of points to track (n, 2)
        array: Visibility mask for each image (n, m)
    """
    # In DEM, in polygon, and DEM not NaN
    z = dem.sample(xy, bounds_error=False, fill_value=np.nan)
    mask = ~np.isnan(z) & glimpse.helpers.points_in_polygon(xy, polygon)
    # Visible in one or more images
    xyz = np.column_stack((xy, z))[mask]
    visible = np.tile(mask.reshape(-1, 1), reps=(1, len(images)))
    for i, img in enumerate(images):
        uv = img.cam.project(xyz, correction=True)
        # In image frame
        visible[mask, i] &= img.cam.inframe(uv)
        # In range
        distance = np.linalg.norm(xyz[:, 0:2] - img.cam.xyz[0:2], axis=1)
        visible[mask, i] &= distance < max_distance
        # In DEM viewshed
        viewshed = glimpse.Raster(
            Z=dem.viewshed(img.cam.xyz), x=dem.xlim, y=dem.ylim)
        visible[mask, i] &= viewshed.sample(xyz[:, 0:2], order=1) > 0.99
        # Not in land mask
        land_mask = glimpse.Raster(load_masks([img])[0])
        visible[mask, i] &= land_mask.sample(uv, order=1, bounds_error=False,
            fill_value=1.0) == 0
    return visible


"""
----------------
accessChecker.py
----------------

To run:

Input csv file should have two columns
 - column one is the name of the vendor selected from the list below
 - column two is the url
 - column three is the title of journal

Execute the command: "python accessChecker.py {name_of_csv_file}"

Output will be written to accessCheckerResults.csv
"""

import sys
from bs4 import BeautifulSoup
import csv
import re
import requests
from selenium import webdriver
import yaml
import time

def url_check(url):
    if url == "title_url":
        return "Default URL"
    else:
        return url

def get_columns():
    yml = open('AccessCheckerSettings.yml', 'r')
    settings = yaml.load(yml)
    columns = []
    for setting in settings["Columns"]:
        if settings["Columns"][setting]:
            columns.append(setting)
    yml.close()
    return columns

def get_rows(filename):
    file_extension = filename.split('.')[-1]
    if file_extension == "csv":
        f = open(filename,'r')
        reader=csv.reader(f)
    if file_extension == "txt":
        f = open(filename,'rU')
        reader=csv.reader((line.replace('\0','') for line in f),delimiter='\t')
        for _ in range(28):
            header = next(reader) #skip (**) lines
            if (len(header) > 1) and ("itle" in header[0]): break
    if len(header) == 0: header = next(reader) # to account for weird case where an extra blank line exists in the txt file.
    header=list(map(url_check, header))
    rows = [dict(zip(header, map(str, row))) for row in reader]
    f.close()
    for index,row in enumerate(rows):
        if not bool(row):
            del rows[index]
    return rows

def main(filename, vendor):
    columns = get_columns()
    rows= get_rows(filename)
    output = csv.writer(open('accessCheckerResults.csv', "w", newline=''))
    num_lines = len(rows)
    output_header = ["Vendor"] + columns + ["Have Access?"]
    output.writerow(output_header)
    for index, row in enumerate(rows):
        message = globals().get(vendor)(row["Default URL"])
        print(str(index + 1) +  " of " + str(num_lines) + " | " + message + " | " + row["Default URL"])

        output_row = [vendor]
        for column in columns:
            if column in row:
                output_row.append(row[column])
            else:
                output_row.append('')
        output_row.append(message)
        output.writerow(output_row)
    #[vendor, row["Title"], row["Default URL"], message]

def adam(url):
    r = requests.get(url)
    soup = BeautifulSoup(r.text, 'lxml')
    if soup.find_all(text = re.compile("Download whole document")) or soup.find_all(text = re.compile("Download entire document")):
        return "Right On!"
    else:
        return "Look into this . . . "

def asp(url, count, num_lines):
    r = requests.get(url)
    soup = BeautifulSoup(r.text, 'lxml')
    if soup.find_all("div", id = "1"):
        return "Right On!"
    elif soup.find_all("title", text = re.compile("Trial login")):
        return "Nope!"
    else:
        return "Look into this . . . "

def curio(url):
    r = requests.get(url)
    soup = BeautifulSoup(r.text, 'lxml')
    if soup.find_all("div", class_="container content"):
        return "Right On!"
    elif soup.find_all("h2", class_ = "pull-right"):
        return "Nope!"
    else:
        return "Look into this . . . "

def ebookcentral(url): #updated Feb 2018 PP
    r = requests.get(url)
    soup = BeautifulSoup(r.text, 'lxml')
    if soup.find_all(text = re.compile("Your institution has access")):
        return "Right On!"
    elif soup.find_all(text = re.compile("Sorry, this book is not available")):
        return "Nope!"
    else:
        return "Look into this . . . "
"""
    browser = webdriver.PhantomJS()
    browser.get(url)
    soup=BeautifulSoup(browser.page_source)
    if soup.find_all(text = re.compile("Your institution has access")):
        return "Right On!"
    elif soup.find_all(text = re.compile("Your institution has access to 1 copy of this book.")):
        return "Right On! [supo]"
    elif soup.find_all(text = re.compile("Sorry, this book is not available")):
        return "Nope!"
    else:
        return "Look into this . . . "
"""
def ebsco(url):
    browser = webdriver.PhantomJS()
    browser.get(url)
    soup=BeautifulSoup(browser.page_source)
    if soup.find_all("a", class_= "record-type pdf-ft") or soup.find_all("a", class_= "record-type epub"):
        return "Right on!"
    else:
        return "Nope!"

def fod(url):
    r = requests.get(url)
    soup = BeautifulSoup(r.text, 'lxml')
    if soup.find_all("i", class_= "fa fa-play") or soup.find_all(text = re.compile("Show Segments")):
        return "Right On!"
    elif soup.find_all("span", id = "MainContent_lblSearchTerm"):
        return "Nope!"
    else:
        return "Look into this . . . "

def gale(url):
    r = requests.get(url)
    soup = BeautifulSoup(r.text, 'lxml')
    if soup.find_all(text = re.compile("Table of Contents")):
        return "Right On!"
    else:
        return "Look into this . . . "

def harvard(url):
    r = requests.get(url)
    soup = BeautifulSoup(r.text, 'lxml')
    if soup.find_all("li", class_= "paidAccess"):
        return "Right On!"
    elif soup.find_all("a", class_= "get-access"):
        return "Nope!"
    else:
        return "Look into this . . . "

def ilib(url):
    r = requests.get(url)
    soup = BeautifulSoup(r.text, 'lxml')
    if soup.find_all(text = re.compile("Download Page Range")):
        return "Right On!"
    elif soup.find_all(text = re.compile("Access Error")):
        return "Nope!"
    else:
        return "Look into this . . . "

#igi updated Oct 2017/PP
def igiglobal(url):
    r = requests.get(url)
    soup = BeautifulSoup(r.text, 'lxml')
    if soup.find_all(text = re.compile("Full-Book Download")):
        return "Right On!"
    elif soup.find_all(text = re.compile("Purchase")):
        return "Nope!"
    else:
        return "Look into this . . . "

#ingenta created Feb 2018/PP
def ingenta(url):
    r = requests.get(url)
    soup = BeautifulSoup(r.text, 'lxml')
    if soup.find_all(src="/images/icon_s_square.gif"):
        return "Right On!"
    elif soup.find_all(src="/images/icon_f_square.gif"):
        return "Free content"
    elif soup.find_all(text = re.compile("Wiley-Blackwell")):
        return "has changed publishers to Wiley"
    elif soup.find_all(text = re.compile("Content Not Found")):
        return "Content Not Found"
    else:
        return "Look into this . . . "
def jstor(url):
    r = requests.get(url)
    soup = BeautifulSoup(r.text, 'lxml')
    if soup.find_all("a", class_= "pdfLink tt-track-nolink"):
        return "Right On!"
    elif soup.find_all("span", text = re.compile("Your institution has not purchased this book from JSTOR.")):
        return "Nope!"
    else:
        return "Look into this . . . "

def muse(url):
    r = requests.get(url)
    soup = BeautifulSoup(r.text, 'lxml')
    if soup.find_all("span", class_= "access_yes"):
        return "Right On!"
    elif soup.find_all("span", class_= "access_no"):
        return "Nope!"
    else:
        return "Look into this . . . "

def nfb(url):
    r = requests.get(url[1])
    soup = BeautifulSoup(r.text, 'lxml')
    if soup.find_all(text = re.compile("Access your rentals without")):
        return "Nope!"
    elif soup.find_all("div", class_= "embed-player-container"):
        return "Right On!"
    else:
        return "Look into this . . . "

#cannot get oxford to work? OCt 2017/PP
def oxford(url):
    time.sleep(5)
    r = requests.get(url)
    soup = BeautifulSoup(r.text, 'lxml')
    if soup.find_all(text = re.compile("availabilityIcon unlocked")):
        return "Right On!"
    elif soup.find_all(text = re.compile("availabilityIcon locked")):
        return "Nope!"
    else:
        return "Look into this . . . "

#springer updated Oct 2017/PP
def springer(url):
    r = requests.get(url)
    soup = BeautifulSoup(r.text, 'lxml')
    if soup.find_all("a", class_= "webtrekk-track pdf-link"):
        return "Right On!"
    elif soup.find_all("a", class_= "test-bookpdf-link"):
        return "Right On!"
    elif soup.find_all("a", class_= "access-link"):
        return "Nope!"
    elif soup.find_all("title", text = re.compile("Deleted DOI")):
        return "Deleted DOI!"
    elif soup.find_all("div", id = "error"):
        return "Page not found"
    else:
        return "Look into this . . . "

def tandf(url):
    r = requests.get(url[1])
    soup = BeautifulSoup(r.text, 'lxml')
    if soup.find_all("a", text = re.compile("Download a copy")) or soup.find_all("a", text = re.compile("Quick access")):
        return "Right On!"
    elif soup.find_all("span", text = re.compile("Sorry, you do not have access to this book.")):
        return "Nope!"
    else:
        return "Look into this . . . "

 #wiley updated May 2018/PP
def wiley(url):
    r = requests.get(url)
    soup = BeautifulSoup(r.text, 'lxml')
    if soup.find_all(text = re.compile("full access")):
        return "Right On!"
    elif soup.find_all(text = re.compile("Full access")):
        return "Right on!"
    elif soup.find_all(text = re.compile("We're sorry, the page you've requested does not exist at this address")):
        return "Page not found"
    elif soup.find_all(text = re.compile("Hindawi")):
        return "Hindawi open access"
    elif soup.find_all(text = re.compile("Open access")):
        return "Open access"
    elif soup.find_all(text = re.compile("Birth Defects Research")):
        return "Journal merge Birth Defects Research"

    elif soup.find_all(text = re.compile("You have free access to this content")):
        return "Free Access"
    elif soup.find_all(text = re.compile("This journal is now published as")):
        return "Change of title"
    elif soup.find_all(text = re.compile("Open Access")):
        return "Open access"
    elif soup.find_all(text = re.compile("free access")):
        return "Free Access"
    else:
        return "Look into this . . . "
# Run it!
if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] == "vendors":
        methods = list(globals())
        main_ind =  methods.index('main')
        for vendor in methods[main_ind+1:]:
            print(vendor)
    elif len(sys.argv) != 3:
        print("Correct Usage: "+sys.argv[0]+" file/path/and/name.ext vendor")
        print("To see a list of vendors run: "+sys.argv[0]+" vendors")
    else:
        main(sys.argv[1],sys.argv[2])


import logging
import superdesk
from superdesk.etree import etree
from superdesk.celery_app import celery
from superdesk.locators.locators import find_cities
from superdesk.metadata.item import Priority

parsers = []
providers = {}
allowed_providers = []
provider_errors = {}
publish_errors = []
logger = logging.getLogger(__name__)

from .commands.remove_expired_content import RemoveExpiredContent
from .commands.update_ingest import UpdateIngest

def init_app(app):
    from .ingest_provider_model import IngestProviderResource, IngestProviderService
    endpoint_name = 'ingest_providers'
    service = IngestProviderService(endpoint_name, backend=superdesk.get_backend())
    IngestProviderResource(endpoint_name, app=app, service=service)

    from .io_errors import IOErrorsService, IOErrorsResource
    endpoint_name = 'io_errors'
    service = IOErrorsService(endpoint_name, backend=superdesk.get_backend())
    IOErrorsResource(endpoint_name, app=app, service=service)

    from .ingest import IngestResource, IngestService
    endpoint_name = 'ingest'
    service = IngestService(endpoint_name, backend=superdesk.get_backend())
    IngestResource(endpoint_name, app=app, service=service)
    from .commands.add_provider import AddProvider  # NOQA

def register_provider(type, provider, errors):
    providers[type] = provider
    allowed_providers.append(type)
    provider_errors[type] = dict(errors)

@celery.task(soft_time_limit=15)
def update_ingest():
    UpdateIngest().run()

@celery.task
def gc_ingest():
    RemoveExpiredContent().run()

class ParserRegistry(type):
    """Registry metaclass for parsers."""

    def __init__(cls, name, bases, attrs):
        """Register sub-classes of Parser class when defined."""
        super(ParserRegistry, cls).__init__(name, bases, attrs)
        if name != 'Parser':
            parsers.append(cls())

class Parser(metaclass=ParserRegistry):
    """Base Parser class for all types of Parsers like News ML 1.2, News ML G2, NITF, etc."""

    def parse_message(self, xml, provider):
        """Parse the ingest XML and extracts the relevant elements/attributes values from the XML."""
        raise NotImplementedError()

    def can_parse(self, xml):
        """Test if parser can parse given xml."""
        raise NotImplementedError()

    def qname(self, tag, ns=None):
        if ns is None:
            ns = self.root.tag.rsplit('}')[0].lstrip('{')
        elif ns is not None and ns == 'xml':
            ns = 'http://www.w3.org/XML/1998/namespace'

        return str(etree.QName(ns, tag))

    def set_dateline(self, item, city=None, text=None):
        """
        Sets the 'dateline' to the article identified by item. If city is passed then the system checks if city is
        available in Cities collection. If city is not found in Cities collection then dateline's located is set with
        default values.

        :param item: article.
        :param city: Name of the city, if passed the system will search in Cities collection.
        :param text: dateline in full. For example, "STOCKHOLM, Aug 29, 2014"
        """

        item['dateline'] = {}

        if city:
            cities = find_cities()
            located = [c for c in cities if c['city'] == city]
            item['dateline']['located'] = located[0] if len(located) > 0 else {'city_code': city, 'city': city,
                                                                               'tz': 'UTC', 'dateline': 'city'}
        if text:
            item['dateline']['text'] = text

    def map_priority(self, source_priority):
        """
        Maps the source priority to superdesk priority
        :param str source_priority:
        :return int: priority of the item
        """
        if source_priority and source_priority.isdigit():
            if int(source_priority) in Priority.values():
                return int(source_priority)

        return Priority.Ordinary.value

def get_xml_parser(etree):
    """Get parser for given xml.

    :param etree: parsed xml
    """
    for parser in parsers:
        if parser.can_parse(etree):
            return parser

# must be imported for registration
import superdesk.io.nitf
import superdesk.io.newsml_2_0
import superdesk.io.newsml_1_2
import superdesk.io.wenn_parser
import superdesk.io.teletype
import superdesk.io.email
register_provider('search', None, [])

superdesk.privilege(name='ingest_providers', label='Ingest Channels', description='User can maintain Ingest Channels.')


from datetime import datetime
from pytz import FixedOffset

from vilya.libs.consts import LANGUAGES
from vilya.libs.store import cache, ONE_DAY
from vilya.models.user import get_author_by_email
from .consts import conf
from .utils import getcommitrange, getkeyssortedbyvaluekey
from .collector import DataCollector

REPO_EXTENSION_KEY = "repo:%s:stats:extension1"

class GitDataCollector(DataCollector):

    def __init__(self, gyt_repo):
        super(GitDataCollector, self).__init__()
        self.gyt_repo = gyt_repo

    @classmethod
    def _get_author_email(cls, author_email):
        author, email = author_email.split('<', 1)
        author = author.rstrip()
        email = email.rstrip('>')
        return (author, email)

    def commitrange(self, head, end=None):
        return getcommitrange(head, end)

    def fill_shortstat(self, commitrange):
        try:
            to_ref, from_ref = commitrange
            block_of_lines = self.gyt_repo.repo.log(to_ref,
                                                    from_ref=from_ref,
                                                    shortstat=True,
                                                    no_merges=True,
                                                    reverse=True
                                                    )
        except:
            block_of_lines = []
        return block_of_lines

    @cache(REPO_EXTENSION_KEY % '{proj_name}', expire=ONE_DAY)
    def compute_file_size_and_extensions(self, proj_name):
        # extensions and size of files
        # this should only run once than only compute delta
        extensions = {}
        total_size = 0
        total_files = 0
        source_files = 0
        source_lines = 0
        lines = self.gyt_repo.repo.ls_tree('HEAD', recursive=True, size=True)
        for line in lines:
            if line[0] == '160000' and line[3] == '-':
                # skip submodules
                continue
            sha1 = line[2]
            size = int(line[3])
            fullpath = line[4]

            total_size += size
            total_files += 1

            filename = fullpath.split('/')[-1]  # strip directories
            if filename.find('.') == -1 or filename.rfind('.') == 0:
                ext = ''
            else:
                ext = filename[(filename.rfind('.') + 1):]
            if len(ext) > conf['max_ext_length']:
                ext = ''
            name = LANGUAGES.get(ext, None)

            if name not in extensions:
                if ext in LANGUAGES.keys():
                    name = LANGUAGES[ext]
                    extensions[name] = {'files': 0, 'lines': 0}
                else:
                    continue

            extensions[name]['files'] += 1
            source_files += 1
            try:
                # should be text files
                count = self.getLinesInBlob(sha1)
                extensions[name]['lines'] += count
                source_lines += count
            except:
                pass
        return extensions, total_files, total_size, source_files, source_lines

    def fill_rev_list_commitrange(self, commitrange):
        to_ref, from_ref = commitrange
        commits = self.gyt_repo.repo.rev_list(to_ref, from_ref)
        for commit in commits:
            author = commit.author.name
            # email = email_normalizer(commit.author.name,
            #                          commit.author.email)
            email = commit.author.email
            stamp = commit.committer.time
            date = datetime.fromtimestamp(
                commit.committer.time,
                FixedOffset(commit.committer.offset)
            )

            author = get_author_by_email(email, author)
            if author in conf['merge_authors']:
                author = conf['merge_authors'][author]

            # First and last commit stamp
            # (may be in any order because of cherry-picking and patches)
            if stamp > self.last_commit_stamp:
                self.last_commit_stamp = stamp
            if self.first_commit_stamp == 0 or stamp < self.first_commit_stamp:
                self.first_commit_stamp = stamp

            # yearly/weekly activity
            yyw = date.strftime('%Y-%W')
            self.year_week_act[yyw] += 1
            if self.year_week_act_peak < self.year_week_act[yyw]:
                self.year_week_act_peak = self.year_week_act[yyw]

            # author stats
            if author not in self.authors:
                self.authors[author] = {
                    'lines_added': 0,
                    'lines_removed': 0,
                    'commits': 0,
                }
            # commits, note again that commits may be in any date order
            # because of cherry-picking and patches
            if 'last_commit_stamp' not in self.authors[author]:
                self.authors[author]['last_commit_stamp'] = stamp
            if stamp > self.authors[author]['last_commit_stamp']:
                self.authors[author]['last_commit_stamp'] = stamp
            if 'first_commit_stamp' not in self.authors[author]:
                self.authors[author]['first_commit_stamp'] = stamp
            if stamp < self.authors[author]['first_commit_stamp']:
                self.authors[author]['first_commit_stamp'] = stamp
            if 'email' not in self.authors[author]:
                self.authors[author]['email'] = email

    def fill_short_stats_commitrange(self, commitrange):
        to_ref, from_ref = commitrange
        if to_ref == 'HEAD' and from_ref is None:
            total_lines = 0
        else:
            total_lines = self.total_lines
        for commit in self.fill_shortstat(commitrange):
            files = commit['files']
            inserted = commit['additions']
            deleted = commit['deletions']
            total_lines += inserted
            total_lines -= deleted
            self.total_lines_added += inserted
            self.total_lines_removed += deleted
            stamp = commit['committer_time']
            author = commit['author_name']
            email = commit['author_email']
            author = get_author_by_email(email, author)
            if author in conf['merge_authors']:
                author = conf['merge_authors'][author]
            self.changes_by_date[stamp] = {
                'files': files,
                'ins': inserted,
                'del': deleted,
                'lines': total_lines
            }
            self.process_line_user(author, stamp, inserted, deleted)
        self.total_lines = total_lines

    def collect(self, dir, proj_name, head, n_author):
        DataCollector.collect(self, dir)
        self.loadCache(proj_name, '/stats/' + proj_name, n_author)
        last_sha = self.cache and self.cache.get('last_sha', '')
        if last_sha:
            commitrange = self.commitrange('HEAD', last_sha)
        else:
            commitrange = self.commitrange('HEAD')

        self.total_authors += n_author
        self.fill_rev_list_commitrange(commitrange)

        # TODO Optimize this, it's the worst bottleneck
        # outputs "<stamp> <files>" for each revision
        to_ref, from_ref = commitrange
        revlines = self.gyt_repo.repo.rev_list(to_ref, from_ref=from_ref)
        for commit in revlines:
            timest = commit.author.time
            rev = commit.tree.hex
            linecount = self.getFilesInCommit(rev)
            self.files_by_stamp[int(timest)] = int(linecount)
        self.total_commits += len(revlines)

        extensions, total_files, total_size, source_files, source_lines \
            = self.compute_file_size_and_extensions(proj_name)

        self.extensions = extensions
        self.total_files = total_files
        self.total_size = total_size
        self.source_files = source_files
        self.source_lines = source_lines

        self.fill_short_stats_commitrange(commitrange)

        self.refine()
        # here update new data after head sha

        # here need to save to cache up to head sha
        self.cache['last_sha'] = head
        self.saveCache(proj_name, '/stats/' + proj_name)

    def process_line_user(self, author, stamp, inserted, deleted):
        if author not in self.authors:
            self.authors[author] = {
                'lines_added': 0,
                'lines_removed': 0,
                'commits': 0,
            }
        self.authors[author]['commits'] += 1
        self.authors[author]['lines_added'] += inserted
        self.authors[author]['lines_removed'] += deleted
        if stamp not in self.changes_by_date_by_author:
            self.changes_by_date_by_author[stamp] = {}
        if author not in self.changes_by_date_by_author[stamp]:
            self.changes_by_date_by_author[stamp][author] = {}
        linesadd = self.authors[author]['lines_added']
        commits_n = self.authors[author]['commits']
        self.changes_by_date_by_author[stamp][author]['lines_added'] = linesadd
        self.changes_by_date_by_author[stamp][author]['commits'] = commits_n

    def refine(self):
        # authors
        # name -> {place_by_commits, commits_frac, date_first, date_last,
        # timedelta}
        self.authors_by_commits = getkeyssortedbyvaluekey(
            self.authors, 'commits')
        self.authors_by_commits.reverse()  # most first
        for i, name in enumerate(self.authors_by_commits):
            self.authors[name]['place_by_commits'] = i + 1

        for name in self.authors.keys():
            a = self.authors[name]
            a['commits_frac'] = (
                100 * float(a['commits'])) / self.getTotalCommits()
            date_first = datetime.fromtimestamp(a['first_commit_stamp'])
            date_last = datetime.fromtimestamp(a['last_commit_stamp'])
            delta = date_last - date_first
            a['date_first'] = date_first.strftime('%Y-%m-%d')
            a['date_last'] = date_last.strftime('%Y-%m-%d')
            a['timedelta'] = delta
            if 'lines_added' not in a:
                a['lines_added'] = 0
            if 'lines_removed' not in a:
                a['lines_removed'] = 0

    def getActiveDays(self):
        return self.active_days

    def getActivityByDayOfWeek(self):
        return self.d_of_week_act

    def getActivityByHourOfDay(self):
        return self.h_of_day_act

    def getAuthorInfo(self, author):
        return self.authors[author]

    def getAuthors(self, limit=None):
        res = getkeyssortedbyvaluekey(self.authors, 'commits')
        res.reverse()
        return res[:limit]

    def getCommitDeltaDays(self):
        return (self.last_commit_stamp / 86400 - self.first_commit_stamp / 86400) + 1  # noqa

    def getFilesInCommit(self, rev):
        try:
            res = self.cache['files_in_tree'][rev]
        except:
            res = len(self.gyt_repo.repo.ls_tree(rev,
                                                 recursive=True,
                                                 name_only=True))
            if 'files_in_tree' not in self.cache:
                self.cache['files_in_tree'] = {}
            self.cache['files_in_tree'][rev] = res

        return res

    def getFirstCommitDate(self):
        return datetime.fromtimestamp(self.first_commit_stamp)

    def getLastCommitDate(self):
        return datetime.fromtimestamp(self.last_commit_stamp)

    def getLinesInBlob(self, sha1):
        try:
            res = self.cache['lines_in_blob'][sha1]
        except:
            res = len(self.gyt_repo.repo.cat_file(sha1).split('\n'))
            if 'lines_in_blob' not in self.cache:
                self.cache['lines_in_blob'] = {}
            self.cache['lines_in_blob'][sha1] = res
        return res

    def getTotalAuthors(self):
        return self.total_authors

    def getTotalCommits(self):
        return self.total_commits

    def getTotalFiles(self):
        return self.total_files

    def getTotalLOC(self):
        return self.total_lines

    def getTotalSize(self):
        return self.total_size


from django.shortcuts import render
from django.http import HttpResponse
from django.contrib.auth.decorators import login_required, permission_required
from datetime import datetime
from travels.models import Travel
from clients.models import Client
from drivers.models import Driver
from django.core.exceptions import ObjectDoesNotExist

def sendTravelList(request, error=None):
    client = Client.objects.get(username=request.user)
    travels = Travel.objects.filter(client=client)
    context = {
        'travels': [t.getTravelDict() for t in travels],
        'error': error
    }
    return render(request, 'clients/clients.html', context=context)

@login_required
@permission_required("clients.client")
def travelsList(request):
    if 'refundReq' in request.GET:
        try:
            travel = Travel.objects.get(pk=request.GET['refundReq'])

            if travel.client.username != request.user.username:
                return sendTravelList(request, error='Permission Error')
            travel.refound_request = True
            travel.save()
        except ObjectDoesNotExist:
            return sendTravelList(request, error='Travel not exists')

    if 'reportDriver' in request.GET:
        try:
            travel = Travel.objects.get(pk=request.GET['reportDriver'])
            if travel.client.username != request.user.username:
                return sendTravelList(request, error='Permission Error')
            status = travel.reportDriver()
            if not status:
                return sendTravelList(request, error="Double report error")
        except ObjectDoesNotExist:
            return sendTravelList(request, error='Travel not exists')
    if 'removeTravel' in request.GET:
        try:
            travel = Travel.objects.get(pk=request.GET['removeTravel'])
            if not travel.isRemovable() or travel.client.username != request.user.username:
                return sendTravelList(request, error='Permission Error')
            travel.delete()

        except ObjectDoesNotExist:
            return sendTravelList(request, error='Travel not exists')

    return sendTravelList(request)


"""
Interpolated Antenna array gain
================================
"""
import time
import matplotlib.pyplot as plt

import pyant

beam = pyant.beam_of_radar("e3d_stage1", "array")

interp_beam = pyant.models.InterpolatedArray()
interp_beam.generate_interpolation(beam, resolution=(200, 200, None))

fig, axes = plt.subplots(1, 2)

start_time = time.time()
pyant.plotting.gain_heatmap(beam, ax=axes[0], resolution=100, min_elevation=80.0)
axes[0].set_title("Array")
array_time = time.time() - start_time

start_time = time.time()
pyant.plotting.gain_heatmap(interp_beam, ax=axes[1], resolution=100, min_elevation=80.0)
axes[1].set_title("Interpolated")
interp_time = time.time() - start_time

print(f"Heatmap plot antenna array: {array_time:.1f} seconds")
print(f"Heatmap plot interpolated array: {interp_time:.1f} seconds")
print(f"Speedup = factor of {array_time/interp_time:.2f}")

plt.show()


# Function to find the largest of three numbers
def find_largest(num1, num2, num3):
    largest = max(num1, num2, num3)
    return largest

# Taking user input for three numbers
num1 = float(input("Enter the first number: "))
num2 = float(input("Enter the second number: "))
num3 = float(input("Enter the third number: "))

# Calling the function to find the largest number
largest_number = find_largest(num1, num2, num3)

# Displaying the result
print("The largest number among", num1, ",", num2, ", and", num3, "is:", largest_number)


__author__ = 'chywoo.park'
from subprocess import Popen, PIPE
from threading import Thread
from queue import Queue, Empty

CMD = ["/Users/chywoo.park/tizen23/tools/sdb", "devices"]

stdout_fp = open("stdout.txt", "w")
stderr_fp = open("stderr.txt", "w")
p = Popen(CMD, stdout=stdout_fp, stderr=stderr_fp)

print("Object: ", p)
print("")
eof = False
print("Wait: ", p.wait())
# while not eof:
#     v = p.poll()
#     print("Poll: ", v)
#     data = p.stdout.read()
#     # data, err = p.communicate(timeout=10)
#
#     if data == b'':
#         print("EOF")
#         eof = True
#
#     print(data.decode('utf-8'))

stdout_fp.close()
stderr_fp.close()

with open("stdout.txt", "r") as fp:
    for line in iter(fp.readline, ''):
        print(line)

with open("stderr.txt", "r") as fp:
    for line in iter(fp.readline, ''):
        print(line)


## Title:       send.py
## Author:      Jeroen Venema
## Created:     25/10/2022
## Last update: 11/09/2023

## syntax
## send.py FILENAME <PORT> <BAUDRATE>
##

## Modinfo:
## 25/10/2022 initial version
## 10/09/2023 Script converts binary file to Intel Hex during transmission.
##            Using defaults as constants.
## 11/09/2023 Wait time variable introduced for handling PC serial drivers with low buffer memory

DEFAULT_START_ADDRESS = 0x40000
DEFAULT_SERIAL_PORT   = 'COM11'
DEFAULT_BAUDRATE      = 115200
DEFAULT_LINE_WAITTIME = 0       ## A value of +/- 0.003 Helps PC serial drivers with low buffer memory

def errorexit(message):
  print(message)
  print('Press ENTER to continue')
  input()
  exit()
  return

import sys
import time
import os.path
import tempfile
try:
  import serial
except ModuleNotFoundError:
  errorexit('Please install the \'pyserial\' module with pip')
try:
  from intelhex import IntelHex
except ModuleNotFoundError:
  errorexit('Please install the \'intelhex\' module with pip')

if len(sys.argv) == 1 or len(sys.argv) >4:
  sys.exit('Usage: send.py FILENAME <PORT> <BAUDRATE>')

if not os.path.isfile(sys.argv[1]):
  sys.exit(f'Error: file \'{sys.argv[1]}\' not found')

if len(sys.argv) == 2:
  serialport = DEFAULT_SERIAL_PORT

if len(sys.argv) >= 3:
  serialport = sys.argv[2]

if len(sys.argv) == 4:
  baudrate = int(sys.argv[3])
else:
  baudrate = DEFAULT_BAUDRATE

nativehexfile = ((sys.argv[1])[-3:] == 'hex') or ((sys.argv[1])[-4:] == 'ihex')

# report parameters used
print(f'Sending \'{sys.argv[1]}\' ', end="")
if nativehexfile: print('as native hex file')
else:
  print('in Intel Hex format')
  print(f'Using start address 0x{DEFAULT_START_ADDRESS:x}')
print(f'Using serial port {serialport}')
print(f'Using Baudrate {baudrate}')

if nativehexfile:
  file = open(sys.argv[1], "r")
  content = file.readlines()
else:
  # Instantiate ihex object and load binary file to it, write out as ihex format to temp file
  ihex = IntelHex()
  file = tempfile.TemporaryFile("w+t")
  ihex.loadbin(sys.argv[1], offset=DEFAULT_START_ADDRESS)
  ihex.write_hex_file(file)
  file.seek(0)

try:
  with serial.Serial(serialport, baudrate,rtscts=False,dsrdtr=False,timeout=None) as ser:
    print('Opening serial port...')
    time.sleep(1)
    print('Sending data...')

    if nativehexfile:
      for line in content:
        ser.write(str(line).encode('ascii'))
        time.sleep(DEFAULT_LINE_WAITTIME)
    else:
      for line in file:
        ser.write(str(line).encode('ascii'))
        time.sleep(DEFAULT_LINE_WAITTIME)

except serial.SerialException:
  errorexit('Error: serial port unavailable')

file.close()


from django.urls import path
from . import views
from .views import ProductList, ProductDetailView, ProductCreateView, ProductDeleteView, ProductUpdateView, login

urlpatterns = [
    # path — означает путь. В данном случае путь ко всем товарам у нас останется пустым, позже станет ясно почему
    path('', ProductList.as_view(), name='products'),
    path('<int:pk>', ProductDetailView.as_view(), name='product'),
    path('create/', ProductCreateView.as_view(), name='product_create'),
    path('update/<int:pk>', ProductUpdateView.as_view(), name='product_update'),
    path('delete/<int:pk>', ProductDeleteView.as_view(), name='product_delete'),

]


from unittest import TestCase
from app import app
from flask import session
from boggle import Boggle

class FlaskTests(TestCase):
    def test_start_game(self):
        with app.test_client() as client:
            res=client.get('/')
            html=res.get_data(as_text=True)

            self.assertEqual(res.status_code,200)
            self.assertIn('
Boggle Game
',html)

    def test_make_board(self):
        with app.test_client() as client:
            res=client.get('/make_board')
            html=res.get_data(as_text=True)

            self.assertEqual(res.status_code,200)
            self.assertIn('
Word:
',html)

    def test_input_board(self):
        with app.test_client() as client:
            res=client.post('/get_input/',data={'a':'ok'})

            self.assertEqual(res.status_code,415)


from string import Template
from typing import Optional

from gql import Client, gql
from gql.transport.aiohttp import AIOHTTPTransport

MECH_SUBGRAPH_URL = "https://api.studio.thegraph.com/query/46780/mech/v0.0.1"
AGENT_QUERY_TEMPLATE = Template(
    """{
    createMeches(where:{agentId:$agent_id}) {
        mech
    }
}
"""
)

def query_agent_address(agent_id: int) -> Optional[str]:
    """
    Query agent address from subgraph.

    :param agent_id: The ID of the agent.
    :type agent_id: int
    :return: The agent address if found, None otherwise.
    :rtype: Optional[str]
    """
    client = Client(transport=AIOHTTPTransport(url=MECH_SUBGRAPH_URL))
    response = client.execute(
        document=gql(
            request_string=AGENT_QUERY_TEMPLATE.substitute({"agent_id": agent_id})
        )
    )
    mechs = response["createMeches"]  # pylint: disable=unsubscriptable-object
    if len(mechs) == 0:
        return None

    (record,) = mechs
    return record["mech"]


from empresa import *
from funcionario import *
from projeto import *
from ocorrencia import *
import unittest

# Classe para testar criacao de uma instancia de Empresa
class TDD_Empresa(unittest.TestCase):
    def teste_CriaEmpresa(self):
        self.angeloni = Empresa([])
        self.assertEqual([], self.angeloni.funcionarios)

# Classe para testar funcionalidades da classe Funcionario
class TDD_Funcionario(unittest.TestCase):
    def setUp(self):
        self.angeloni = Empresa([])
        self.listaDeFuncionarios = self.angeloni.getListaDeFuncionarios()
        self.ivan = Funcionario("Ivan")
        self.joao = Funcionario("Joao")
        self.listaDeFuncionarios.append(self.ivan.nomeFuncionario)
        self.listaDeFuncionarios.append(self.joao.nomeFuncionario)
        self.angeloni.adicionaFuncionario(self.joao)
        self.angeloni.adicionaFuncionario(self.ivan)

    def tearDown(self):
        self.angeloni.__del__()
        self.angeloni = None
        self.ivan.__del__()
        self.ivan = None
        self.joao.__del__()
        self.joao = None
        self.listaDeFuncionarios = []

    def teste_criaFuncionario(self):
        self.funcionario1 = Funcionario("Ivan")
        self.assertEqual(self.funcionario1.nomeFuncionario, "Ivan")

    def teste_adicionaFuncionario(self):
        self.listaDeFuncionarios.sort()
        self.assertEqual(["Ivan", "Joao"], self.angeloni.getListaDeFuncionarios())

    def teste_removeFuncionario(self):
        self.angeloni.removeFuncionario(self.joao)
        self.listaDeFuncionarios.remove("Joao")
        self.assertEqual(["Ivan"], self.angeloni.getListaDeFuncionarios())

# Classe para testar funcionalidades da classe Projeto
class TDD_projeto(unittest.TestCase):
    def setUp(self):
        self.projeto = Projeto("Gerenciador de Tarefas", [])

    def tearDown(self):
        self.projeto.__del__()
        self.projeto = None

    def testeCriaProjeto(self):
        self.projeto1 = Projeto("Gerenciador", [])
        self.assertEqual("Gerenciador", self.projeto1.nomeProjeto)

    def testeAdicionaOcorrencia(self):
        self.ocorrencia1 = Ocorrencia("Bug A", "Bug", "Alta", "Aberta", "ocorrencia1")
        self.projeto.addOcorrencia(self.ocorrencia1)
        self.assertEqual(["Bug A"], self.projeto.getOcorrencias())

    def testeAdicionaVariasOcorrencias_ForaDeOrdem(self):
        self.ocorrencia2 = Ocorrencia("Bug B", "Bug", "Alta", "Aberta", "ocorrencia2")
        self.ocorrencia1 = Ocorrencia("Bug A", "Bug", "Alta", "Aberta", "ocorrencia1")
        self.ocorrencia3 = Ocorrencia("Melhoria A", "Melhoria", "Alta", "Aberta", "ocorrencia3")
        self.projeto.addOcorrencia(self.ocorrencia1)
        self.projeto.addOcorrencia(self.ocorrencia2)
        self.projeto.addOcorrencia(self.ocorrencia3)
        self.assertEqual(["Bug A", "Bug B", "Melhoria A"], self.projeto.getOcorrencias())

    def testeVerificaOcorrenciaPorID(self):
        self.ocorrencia2 = Ocorrencia("Bug B", "Bug", "Alta", "Aberta", "ocorrencia2")
        self.ocorrencia1 = Ocorrencia("Bug A", "Bug", "Alta", "Aberta", "ocorrencia1")
        self.ocorrencia3 = Ocorrencia("Melhoria A", "Melhoria", "Alta", "Aberta", "ocorrencia3")
        self.projeto.addOcorrencia(self.ocorrencia1)
        self.projeto.addOcorrencia(self.ocorrencia2)
        self.projeto.addOcorrencia(self.ocorrencia3)
        self.assertEqual(self.ocorrencia3.getNomeOcorrencia(),self.projeto.getOcorrenciaPorID(3).getNomeOcorrencia())

# Classe para testar funcionalidades da classe Ocorrencia
class TDD_ocorrencia(unittest.TestCase):
    def setUp(self):
        self.ocorrencia1 = Ocorrencia("Bug A", "Bug", "Alta", "Aberta", "ocorrencia1")
        self.projeto = Projeto("Gerenciador de Tarefas", [])
        self.ivan = Funcionario("Ivan")

    def tearDown(self):
        self.ocorrencia1.__del__()
        self.ocorrencia1 = None
        self.projeto.__del__()
        self.projeto = None
        self.ivan.__del__()
        self.ivan = None

    def testeOcorrenciaCriada(self):
        self.assertEqual("Bug A", self.ocorrencia1.getNomeOcorrencia())
        self.assertEqual("Bug", self.ocorrencia1.getTipoOcorrencia())
        self.assertEqual("Alta", self.ocorrencia1.getPrioridade())
        self.assertEqual("Aberta", self.ocorrencia1.getStatus())
        self.assertEqual("ocorrencia1", self.ocorrencia1.getResumo())

    def testeAtribuiFuncionario(self):
        self.projeto.atribuiOcorrencia(self.ocorrencia1, self.ivan)
        self.ivan.adicionaOcorrencia(self.ocorrencia1)
        self.assertEqual("Ivan", self.projeto.getOcorrenciaPorID(1).getResponsavel().getNome())
        self.assertEqual(1, self.ivan.getNumeroOcorrencias())
        self.assertTrue(self.ivan.checaOcorrencia(self.ocorrencia1.getNomeOcorrencia()))

    def testeModificaPrioridadeBaixa(self):
        self.ocorrencia1.setPrioridadeBaixa()
        self.assertEqual("Baixa", self.ocorrencia1.getPrioridade())

    def testeModificaPrioridadeMedia(self):
        self.ocorrencia1.setPrioridadeMedia()
        self.assertEqual("Media", self.ocorrencia1.getPrioridade())

    def testeModificaPrioridadeBaixa(self):
        self.ocorrencia2 = Ocorrencia("Bug B", "Bug", "Media", "Aberta", "ocorrencia2")
        self.ocorrencia2.setPrioridadeAlta()
        self.assertEqual("Alta", self.ocorrencia2.getPrioridade())

    def testeModificaResponsavel(self):
        self.joao = Funcionario("Joao")
        self.projeto.atribuiOcorrencia(self.ocorrencia1, self.ivan)
        self.projeto.getOcorrenciaPorID(1).setResponsavel(self.joao)
        self.joao.adicionaOcorrencia(self.projeto.getOcorrenciaPorID(1))
        self.assertEqual("Joao", self.projeto.getOcorrenciaPorID(1).getResponsavel().getNome())
        self.assertEqual(1, self.joao.getNumeroOcorrencias())
        self.assertTrue(self.joao.checaOcorrencia(self.ocorrencia1.getNomeOcorrencia()))

    def testeTerminaOcorrencia(self):
        self.ocorrencia1.finalizaOcorrencia()
        self.assertEqual("Fechada" ,self.ocorrencia1.getStatus())

    def testeLimiteOcorrenciasPorUsuario(self):
        for i in range(15):
            self.ivan.adicionaOcorrencia(self.ocorrencia1)
        self.assertEqual(10, self.ivan.getNumeroOcorrencias())

    def testeAlteracaoOcorrenciaFechada(self):
        self.joao = Funcionario("Joao")
        self.projeto.atribuiOcorrencia(self.ocorrencia1, self.ivan)
        self.ocorrencia1.finalizaOcorrencia()
        self.ocorrencia1.setPrioridadeMedia()
        self.projeto.getOcorrenciaPorID(1).setResponsavel(self.joao)
        self.assertEqual("Ivan", self.projeto.getOcorrenciaPorID(1).getResponsavel().getNome())
        self.assertEqual("Alta", self.projeto.getOcorrenciaPorID(1).getPrioridade())

# Executa todos os testes
if __name__ == "__main__":
    unittest.main()


#!/usr/bin/env python3
#----------------------------------------------------------------------------
# Created By  : Pedro V Quijano Carde
# Created Date: 05/31/2022
# Revision ='1.0' Add all modules currently used and make a simple script to
#                 use the rotary switch, pumps, and load cell.
# ---------------------------------------------------------------------------
""" This script is to demo all the functions for the hardware used in the
shrew box. """
# ---------------------------------------------------------------------------

from time import strftime, perf_counter, perf_counter_ns, sleep

# ---------------------------------------------------------------------------

from capTouch import *
from fileManage import *
from platformWeight import *
from pumpLib import *
from pumpTimeWrap import *
from rotSwitch import *
from test import *
from weightLib import *

# ---------------------------------------------------------------------------

global timeStart
timeStart = perf_counter_ns()

try:

    timeStamp = strftime("%Y-%m-%d_%H:%M:%S")  #String that contains the date in YYYY-MM-DD_HH:MM:SS format
    fileName = "test"
    storeEvent(timeStamp, fileName, ['Event Time,', 'Event,', 'Details', '\n'])

    pSW = False   #Flags for determining which position the rotary switch is in
    p1Clean = False
    p2Clean = False
    p3Clean = False
    standby = False
    testScript = False
    p1cf = False  #Flag for having the corresponding pump turned on only once (send the command)
    p2cf = False
    p3cf = False
    testcf = False
    nosel = False  #Flag for having the pumps turned off only once when no valid position is selected on rotary switch
    cleaningDone = True #Flag to re-initialize the pumps after cleaning, set to true for first
    weightM = 0

    Highdriver4_init()  #Initialize highdriver4 and have Powermode register set

    platformWeight(True)

    while True:  #main loop

        standby, p1Clean, p2Clean, p3Clean, testScript, pSW = rotSwitchState()   #Read the GPIO pin's current state of the rotary switch

        if pSW:  #Pumps run with software

            if cleaningDone:   #Only run when rotary switch first  selected pSW
                cleaningDone = False  #Will not run this if statement if the rotary switch stays on this position
                p1cf = False   #Resetting other flags
                p2cf = False
                p3cf = False
                nosel = False
                testcf = True
                Highdriver4_setfrequency(100)     #Frequency for dispensing liquids
                Highdriver4_init()
                readRegisters()
                print("Pumps working normally\n")

            platformWeight()
            timedPump(1, 250, 0.2)
            sleep(0.5)
            timedPump(2, 250, 0.2)
            sleep(0.5)
            timedPump(3, 250, 0.2)
            sleep(0.5)
            #weightM = weight()  #Get current weight on platform
            #print(weightM)
            sleep(3)
        elif testScript:
            if testcf:
                cleaningDone = True
                p1cf = False   #Resetting other flags
                p2cf = False
                p3cf = False
                nosel = False
                testcf = False

                Highdriver4_setfrequency(100)     #Frequency for test script default (may be changed from other functions within
                Highdriver4_init()
                readRegisters()
                print("Test Script Running!\n")

            testFunc()

            sleep(1)

        elif p1Clean or p2Clean or p3Clean:  #Checks for other pins for cleaning
            i2cbus.write_byte_data(I2C_HIGHDRIVER_ADRESS, I2C_POWERMODE, 0x01) #Pumps active
            Highdriver4_setfrequency(50)    #Different frequency for cleaning or priming

            cleaningDone = True #Resetting other flags
            testcf = True
            nosel = False

            if p1Clean:
                if not p1cf: #Only run when rotary switch first  selected this
                    p1cf = True #Resetting other flags
                    p2cf = False
                    p3cf = False

                    Highdriver4_setvoltage(1, 250)
                    Highdriver4_setvoltage(2, 0)
                    Highdriver4_setvoltage(3, 0)

                    readRegisters()
                    print('Cleaning Pump 1!\n')

            elif p2Clean:
                if not p2cf: #Only run when rotary switch first  selected this
                    p1cf = False #Resetting other flags
                    p2cf = True
                    p3cf = False

                    Highdriver4_setvoltage(1, 0)
                    Highdriver4_setvoltage(2, 250)
                    Highdriver4_setvoltage(3, 0)

                    readRegisters()
                    print('Cleaning Pump 2!\n')

            elif p3Clean:
                if not p3cf: #Only run when rotary switch first  selected this
                    p1cf = False #Resetting other flags
                    p2cf = False
                    p3cf = True

                    Highdriver4_setvoltage(1, 0)
                    Highdriver4_setvoltage(2, 0)
                    Highdriver4_setvoltage(3, 250)

                    readRegisters()
                    print('Cleaning Pump 3!\n')

        else:
            if not nosel: #Only run when rotary switch first  selected this
                Highdriver4_init()
                i2cbus.write_byte_data(I2C_HIGHDRIVER_ADRESS, I2C_POWERMODE, 0x00) #Turn off highdriver4

                cleaningDone = True #Resetting other flags
                p1cf = False
                p2vf = False
                p3cf = False
                nosel = True
                testcf = True

                readRegisters()
                print("No pumps selected, highdriver off\n")

except (KeyboardInterrupt, SystemExit):  #Terminate program with CTRL + C
    print('Bye ;)')

    Highdriver4_init()  #Set all registers to default
    i2cbus.write_byte_data(I2C_HIGHDRIVER_ADRESS, I2C_POWERMODE, 0x00) #Highdriver4 pumps inactive

finally:
    GPIO.cleanup()  #resets any ports you have used in this program back to input mode


from urllib import response
from django.test import TestCase,Client
from django.urls import reverse
from home.models import Blog,Contact

class TestViews(TestCase):
    def setup(self):
        self.client = Client()

    def test_index_GET(self):
        response = self.client.get(reverse('index:index'))
        self.assertEqual(response.status_code,200)
        self.assertTemplateUsed(response,'home/index.html')

    def test_contact_POST(self):
        response = self.client.post(reverse('index:contact'),{
            'name':'automated-test',
            'email':'aryanjainak@gmail.com',
            'phone_no': '3434',
            'subject' : 'Subject',
            'message' : 'message hai'
        })
        self.assertEqual(response.status_code,302)
        query= Contact.objects.get(id=1)
        self.assertEqual(query.name,'automated-test')


import random

def leer_archivo(archivo):
    data = []
    with open(archivo,"r",encoding="utf-8") as a:
        data = a.readlines()
        data = [cadena.strip("\n") for cadena in data]
    return data

def leer_tablero(n: int)->dict:
    d = {}
    for i in range(0,n+1):
        d[i] = leer_archivo(f"ahorcado-{i}.txt")
    return d

def despliega_tablero(tablero:list)->None:
    for renglon in tablero:
        print(renglon)

def adivina(lista_palabras:list, tableros:dict)->None:
    palabra = random.choice(lista_palabras)
    palabra = palabra.upper()
    lista_letras = [[x,False] for x in palabra]
    abecedario = {chr(x):chr(x) for x in range(ord('A'),ord('z'))+1}
    strikes = 0
    en_juego = True
    while en_juego == True:
        despliega_palabra(lista_letras)
        despliega_abc(abecedario)
        despliega_tablero(tableros[strikes])
        for lista in lista_letras:
            if lista[1] == "_":
                completo = False
        if completo == True:
            en_juego = False
            continue
        else:
            if strikes == 6:
                en_juego = False
                break
        letra = input("Selecciona una letra")
        letra = letra.upper()

        if len(letra) != 1:
            continue
        else:
            intento = False
            for lista in lista_letras:
                if letra == lista[0]:
                    lista[1]
                    intento = True
        if intento == False:
            strikes +=1
        if letra in abecedario:
            abecedario[letra] = "*"
        en_juego = False

def despliega_palabra(lista_letras:list):
    lista = [x[1]for x in lista_letras]
    palabra = " ".join(lista)
    print(palabra)

def despliega_abc(diccionario:dict)->None:
    abc= [value for key,value in diccionario.item()]
    abc = "".join(abc)
    print(abc)

def main(archivo):
    lista_palabras = leer_archivo(archivo)
    palabra = random.choice(lista_palabras)
    print(palabra)
    tableros = leer_tablero(7)
    despliega_tablero(tableros[0])
    adivina(lista_palabras,tableros)

if __name__ =="__main__":
    archivo = "palabras.txt"
    main(archivo)


from sys import exit

def gold_room():
	print("this room is full of gold. how much?")

	next = input("> ")
	if "0" in next or "1" in next:
		how_much = int(next)
	else:
		dead("learn to type a number.")

	if how_much < 50:
		print("not greedy. win!")
		exit(0)#表示程序是正常退出，exit(1)表示是有错误退出
	else:
		dead("greedy bastard!")

def bear_room():
	print("bear here.")
	print("a bunch of honey.")
	print("in front of another door.")
	print("how to move the bea?")
	bear_moved = False

	while True:
		next = input("> ")

		if next == "take honey":
			dead("the bear slaps your face off.")
		elif next == "taunt bear" and not bear_moved:
			print("the bear has moved from the door. you can go.")
			bear_moved = True  # 当再次输入taunt bear的时候，就会运行到下一段，所以这个命令的目的是同样的输入会得到不同的输出。
		elif next == "taunt bear" and bear_moved:
			dead("the bear chews your leg off.")
		elif next == "open door" and bear_moved: #这一句保证直接 open door 命令无法进入gold room，需要首先bear_moved为True才可以。所以得到唯一解。
			gold_room()
		else:
			print("no idea.")

def cthulhu_room():
	print("see the great evil cthulhu.")
	print("go insane.")
	print("do you flee?")

	next = input("> ")
	if "flee" in next:
		start()
	elif "head" in next:
		dead("tasty!")
	else:
		cthulhu_room()

def dead(why):
	print(why, "good job!")
	exit(0)

def start():
	print("a dark room.")
	print("a door to your right and left.")
	print("which one?")

	next = input("> ")

	if next == "left":
		bear_room()
	elif next == "right":
		cthulhu_room()
	else:
		dead("you stumble.")

start()


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

"""
@Author: xiezizhe
@Date: 14/6/2020 下午6:48
"""
from typing import List

class Solution:

    def dfs(self, nums: List[int], idx: int) -> List[List[int]]:
        if len(nums) <= idx + 1:
            return [[nums[idx]]]

        f = nums[idx]
        res = []
        for p in self.dfs(nums, idx + 1):
            for i in range(1, len(p)):
                res.append(p[:i] + [f] + p[i:])
            res.append([f] + p)
            res.append(p + [f])

        return res

    def permute(self, nums: List[int]) -> List[List[int]]:
        if nums is None or len(nums) == 0:
            return [[]]

        if len(nums) == 1:
            return [nums]

        return self.dfs(nums, 0)

if __name__ == "__main__":
    s = Solution()
    print(s.permute([1]))


from .transforms import *

class TrainAugmentation:
    def __init__(self, size, mean=0, std=1.0, cvt2gray=False):
        """
        训练时的数据增强操作
        Args:
            size: the size the of final image.
            mean: mean pixel value per channel.
        """
        self.mean = mean
        self.size = size
        self.augment = Compose([
            ConvertFromInts(),      # img, np.ndarray
            PhotometricDistort(),
            Expand(self.mean),      # img, boxes
            RandomSampleCrop(),     # img, boxes, labels
            RandomMirror(),
            ToPercentCoords(),      # boxes
            Resize(self.size),      # img
            SubtractMeans(self.mean),
            lambda img, boxes=None, labels=None: (img / std, boxes, labels),
            ToTensor(),
        ])
        if cvt2gray:
            self.augment.replace(7, Cvt2Gray())  # 转灰度去训练,SubtractMeans是BGR减去均值处理，转灰度后不需要SubtractMeans

    def __call__(self, img, boxes, labels):
        """
        Args:
            img: the output of cv.imread in RGB layout.
            boxes: boundding boxes in the form of (x1, y1, x2, y2).
            labels: labels of boxes.
        """
        return self.augment(img, boxes, labels)

class TestTransform:
    def __init__(self, size, mean=0.0, std=1.0, cvt2gray=False):
        """
        test 时的数据增强处理
        """

        self.transform = Compose([
            ToPercentCoords(),
            Resize(size),
            SubtractMeans(mean),
            lambda img, boxes=None, labels=None: (img / std, boxes, labels),
            ToTensor(),
        ])
        if cvt2gray:
            self.transform.replace(2, Cvt2Gray())  # 转灰度去训练,SubtractMeans是BGR减去均值处理，转灰度后不需要SubtractMeans

    def __call__(self, image, boxes, labels):
        return self.transform(image, boxes, labels)

class PredictionTransform:
    def __init__(self, size, mean=0.0, std=1.0, cvt2gray=False):
        """
        预测时的数据增强处理
        """
        self.transform = Compose([
            Resize(size),
            SubtractMeans(mean),
            lambda img, boxes=None, labels=None: (img / std, boxes, labels),
            ToTensor()
        ])
        if cvt2gray:
            self.transform.replace(1, Cvt2Gray())  # 转灰度去训练,SubtractMeans是BGR减去均值处理，转灰度后不需要SubtractMeans

    def __call__(self, image):
        image, _, _ = self.transform(image)
        return image