from fastapi import FastAPI, Path, Query, HTTPException, status, APIRouter
from fastapi.openapi.utils import get_openapi
from typing import List, Optional, Set
from pydantic import BaseModel, Field
import os, json, requests, asyncio, sys, aiohttp, shutil, git, uuid, subprocess, stat
from importlib import import_module
from datetime import datetime
from aiohttp import ClientSession
from rocrate.rocrate import ROCrate
from pathlib import Path as pads
from collections import MutableMapping
from fastapi.staticfiles import StaticFiles
from fastapi.openapi.docs import (
    get_redoc_html,
    get_swagger_ui_html,
    get_swagger_ui_oauth2_redirect_html,
)
import app.ro_crate_reader_functions as ro_read
import logging
log=logging.getLogger(__name__)
#all diff subroutes
from .content import router as content_router
from .git import router as git_router
from .annotation import router as annotation_router
from app.model.location import Locations
from app.model.profile import Profile

router = APIRouter(
    prefix="",
    tags=["Profiles"],
    responses={404: {"description": "Not found"}},
)

#profiles_by_uuid = Profile.load_all()

### define class profiles for the api ###

class ProfileModel(BaseModel):
    name: str = Field(None, description="Name of the profile")
    logo: Optional[str] = Field(None, example = 'https://www.researchobject.org/ro-crate/assets/img/ro-crate-w-text.png', description = "Logo to be displayed in RO crate UI")
    description: Optional[str] = Field(None, description = "description of the RO-profile")
    url_ro_profile: str = Field(None, description = "github url where the rocrate profile is located")

class SpaceModel(BaseModel):
    storage_path: str = Field(None, description = "Valid path on local storage where ROcrate data will be stored")
    RO_profile: str = Field(None, description = "Ro-Profile name that will be used for the space")
    remote_url: Optional[str] = Field(None, description = "git repo url to get project from")

class FileModel(BaseModel):
    name     : str = Field(None, description = "Name of the file that will be added, can be filepath")
    content  : str = Field(None, description = "Filepath that needs to be added to the space, can also be a directory or url")

class AnnotationModel(BaseModel):
    URI_predicate_name : str = Field(None, description = "Name of the URI that will be added, must be part of the RO-crate profile provided metadata predicates.\
                                                for more info about the allowed predicates, use TODO: insert api call for predicates here.")
    value    : str = Field(None, description = "Value linked to the URI predicate name chosen")

class AnnotationsModel(BaseModel):
    Annotations: List[AnnotationModel] = Field(None, description = "List of annotations to add to resource. \
                                              for more info about the allowed annotation predicates, use TODO: insert api call for predicates here.")

class ContentModel(BaseModel):
    content: List[FileModel] = Field(None, description = "List of files that need to be added, this list can also contain directories")

class DeleteContentModel(BaseModel):
    content: List[str] = Field(None, description = "List of files to delete , if full path given it will delete one file , of only file name given it will delete all entities in the system with file name.")

### define helper functions for the api ###

def check_space_name(spacename):
    with open(Locations().join_abs_path('spaces.json'), "r+")as file:
        data = json.load(file)
    for space, info in data.items():
        if spacename == space:
            return True
    return False

def on_rm_error(func, path, exc_info):
    #from: https://stackoverflow.com/questions/4829043/how-to-remove-read-only-attrib-directory-with-python-in-windows
    os.chmod(path, stat.S_IWRITE)
    os.unlink(path)

async def check_path_availability(tocheckpath,space_id):
    if os.path.isdir(os.path.join(tocheckpath)) == False:
        raise HTTPException(status_code=400, detail="Given storage path does not exist on local storage")
    #check if given path is already used by another project
    toposturl = 'http://localhost:6656/apiv1/spaces' #TODO : figure out how to not hardcode this <---
    async with ClientSession() as session:
        response = await session.request(method='GET', url=toposturl)
        text = await response.content.read()
        all_spaces = json.loads(text.decode('utf8').replace("'", '"'))
        for space,info_space in all_spaces.items():
            print(info_space["storage_path"], file=sys.stderr)
            print(str("/".join((tocheckpath,str(space_id)))), file=sys.stderr)
            if info_space['storage_path'] == str("/".join((tocheckpath,str(space_id)))) or info_space['storage_path'] == str(tocheckpath):
                raise HTTPException(status_code=400, detail="Given storage path is already in use by another project")
    if len(os.listdir(os.path.join(tocheckpath)) ) != 0:
        try:
            os.mkdir(os.path.join(tocheckpath,str(space_id)))
            tocheckpath = os.path.join(tocheckpath,str(space_id))
            returnmessage = "Space created in folder: " + str(os.path.join(tocheckpath))
            return [returnmessage,tocheckpath]
        except:
            tocheckpath = os.path.join(tocheckpath,str(space_id))
            returnmessage = "Space created in existing folder: " + str(os.path.join(tocheckpath))
            return [returnmessage,tocheckpath]

### api paths ###

@router.get('/')
def get_all_profiles_info():
    log.info(f"profile get all begin")
    with open(Locations().join_abs_path('profiles.json'), "r+") as file:
        data = json.load(file)
        return data

@router.get('/{profile_id}/')
def get_profile_info(profile_id: str = Path(None,description="profile_id name")):
    log.info(f"profile get begin")
    with open(Locations().join_abs_path('profiles.json'), "r+") as file:
        data = json.load(file)
        try:
            toreturn = data[profile_id]
            return toreturn
        except Exception as e:
            log.error(f"profile get profile by id error")
            log.exception(f"{e}")
            raise HTTPException(status_code=404, detail="profile not found")

@router.delete('/{profile_id}/', status_code=202)
def delete_profile(profile_id: str = Path(None,description="profile_id name")):
    log.info(f"profile delete begin")
    with open(Locations().join_abs_path('profiles.json')) as data_file:
            data = json.load(data_file)
            try:
                del data[profile_id]
            except Exception as e:
                log.error(f"profile delete profile by id error")
                log.exception(f"{e}")
                raise HTTPException(status_code=404, detail=f"profile {profile_id} was not found in profiles")
    with open(Locations().join_abs_path('profiles.json'), 'w') as data_file:
        data = json.dump(data, data_file)
        return {'message':'successfully deleted profile'}

@router.post('/', status_code=201)
def add_profile(*,item: ProfileModel):
    log.info(f"profile add begin")
    if item.logo != None or item.description != None  or item.url_ro_profile != None or item.name != None:
        #add check for the url of the profile:
        try:
            #tocheckrocrate = ro_read.MakeNewProfile(profile_id=item.name, logo=item.logo ,description= item.description, repo_url=item.url_ro_profile)
            Profile(
                repo_url = item.url_ro_profile,
                name = item.name,
                description = item.description,
                logo_url = item.logo,
            )
        except Exception as e:
            log.error(f"profile make profile error")
            log.exception(f"{e}")
            raise HTTPException(status_code=500, detail="error : {}".format(e))
        return {'Message':'Profile added'}
    else:
        keys = dict(item).keys()
        raise HTTPException(status_code=400, detail="supplied body must have following keys: {}".format(keys))

@router.put('/{profile_id}/', status_code=202)
def update_profile(*,profile_id: str = Path(None,description="profile_id name"), item: ProfileModel):
    log.info(f"profile update begin")
    with open(Locations().join_abs_path('profiles.json'), "r+")as file:
        data = json.load(file)
    for profile in data.keys():
        if profile_id == profile:
            try:
                if item.logo != None or item.description != None or item.url_ro_profile != None:
                    data[profile_id].update({'logo_url':item.logo,'description':item.description,'repo_url':item.url_ro_profile})
                    with open(Locations().join_abs_path('profiles.json'), "w") as file:
                        json.dump(data, file)
                        return {'Data':'Update successfull'}
                else:
                    log.info(f"profile update fail")
                    log.info(f"supplied body must have following keys: {format(keys)}")
                    keys = dict(item).keys()
                    raise HTTPException(status_code=400, detail="supplied body must have following keys: {}".format(keys))
            except Exception as e:
                log.error(f"profile update profile error")
                log.exception(f"{e}")
                keys = dict(item).keys()
                raise HTTPException(status_code=400, detail="supplied body must have following keys: {}".format(keys))
    raise HTTPException(status_code=404, detail="profile not found")


import string
from nltk.corpus import stopwords
import spacy
from spacy.lang.es.examples import sentences

def clean_text(text, filterStopwords=False, filterPos=None, lang="es"):

	cleanTokens = []
	stopwordList = []
	nlp = None

	if lang=="es":
		nlp = spacy.load('es_core_news_sm')
		stopwordList = stopwords.words('spanish')
		doc = nlp(text)

	elif lang =="en":
		stopwordList = stopwords.words('english')
		nlp = spacy.load('en_core_web_sm')
		doc = nlp(text)

	else:
		return None

	for token in doc:
		#ignore usernames, hashtags, urls and numbers
		if not token.text.startswith("@") and not token.like_url and not token.like_email and not token.like_num and not token.text.startswith("#"):
			if filterPos and not filterStopwords:
				if token.pos_ in filterPos:
					cleanTokens.append(token.text)

			elif filterStopwords and not filterPos:
				if token.text not in stopwordList:
					cleanTokens.append(token.text)

			elif filterStopwords and filterPos:
				if token.text not in stopwordList and token.pos_ in filterPos:
					cleanTokens.append(token.text)

			elif not filterStopwords and not filterPos:
				cleanTokens.append(token.text)

	return cleanTokens

def clean_get_lemmas(text):
	nlp = spacy.load('es_core_news_sm')
	cleanTokens = []
	doc = nlp(text)

	for token in doc:
		cleanTokens.append(token.lemma_)

	return cleanTokens


from aiogram import Dispatcher
from aiogram.dispatcher.webhook import WebhookRequestHandler
from aiohttp import web
from functools import partial
from .singleton import Singleton
from logging import getLogger
logger = getLogger('GitHubBot.telegram.Executor')

class HookExecutor(metaclass=Singleton):
    def __init__(self, dispatcher, **kwargs):
        self.dispatcher = dispatcher
        self.skip_updates = kwargs['skip_updates']
        self.check_ip = kwargs['check_ip']
        self.retry_after = kwargs['retry_after']
        self.loop = kwargs['loop']
        self.on_startup = kwargs['on_startup']
        self.on_shutdown = kwargs['on_shutdown']
        self._web_app = None
        self._frozen = False

        from aiogram import Bot, Dispatcher
        Bot.set_current(dispatcher.bot)
        Dispatcher.set_current(dispatcher)

        self._setup()

    @property
    def _assert_not_frozen(self):
        if self._frozen:
            raise RuntimeError("Execution is frozen!")

    @property
    def web_app(self):
        return self._web_app

    def _setup(self, webhook_path='/', handler=WebhookRequestHandler):
        self._assert_not_frozen()
        self._frozen = True

        if self.web_app: return

        self._web_app = web.Application()

        self._web_app['RETRY_AFTER'] = self.retry_after | None

        self._web_app.router.add_route('*', webhook_path, handler, name="tg_webhook_handler")

        async def _wrap_callback(cb, _):
            return await cb(self.dispatcher)

        for callback in self.on_startup:
            self._web_app.on_startup.append(partial(_wrap_callback, callback))

        for callback in self.on_shutdown:
            self._web_app.on_shutdown.append(partial(_wrap_callback, callback))

        self._web_app['APP_EXECUTOR'] = self
        self._web_app['BOT_DISPATCHER'] = self.dispatcher
        self._web_app['_check_ip'] = self.check_ip


"""
# Sample code to perform I/O:

name = input()                  # Reading input from STDIN
print('Hi, %s.' % name)         # Writing output to STDOUT

# Warning: Printing unwanted or ill-formatted data to output will cause the test cases to fail
"""

# Write your code here
from functools import reduce
from operator import or_

t = int(input())
for _ in range(t):
    n, x = map(int, input().strip().split())
    a = list(map(int, input().strip().split()))
    temp = reduce(or_, a, 0)
    if temp < x:
        print(0)
    else:
        subarrays = []
        temp = a[:]
        for i in range(n):
            flag = False
            for j in range(n - i):
                temp[j] = temp[j] | a[i + j]
                if temp[j] >= x:
                    subarrays.append([j + 1, j + i + 1])
                    flag = True
            if flag:
                break
        print(len(subarrays))
        for l, r in subarrays:
            print(l, r)


#!/usr/bin/env python

import theano
import theano.tensor as T
import numpy as np

class Optimizer:

    """
    Optimization methods for backpropagation
    """

    def __init__(self, clip=5.0):

        """
        Initialize Optimizer with gradient clipping norm
        """

        self.clip = np.float32(clip)

    def sgd(self, cost, params, lr=0.01):
        """
        Stochatic Gradient Descent.
        """
        lr = theano.shared(np.float32(lr).astype(theano.config.floatX))

        gradients = T.grad(cost, params)

        updates = []
        for param, gradient in zip(params, gradients):
            updates.append((param, param - lr * gradient))

        return updates

    def adagrad(self, cost, params, lr=0.01, epsilon=1e-6):
        """
        Adaptive Gradient Optimization.
        """
        lr = theano.shared(np.float32(lr).astype(theano.config.floatX))
        epsilon = theano.shared(np.float32(epsilon).astype(theano.config.floatX))

        gradients = T.grad(
            theano.gradient.grad_clip(cost, -1 * self.clip, self.clip),
            params
        )

        updates = []
        for param, gradient in zip(params, gradients):
            accumulated_gradient = theano.shared(np.zeros_like(param.get_value(borrow=True)).astype(np.float32), borrow=True)
            accumulated_gradient_new = accumulated_gradient + gradient  ** 2
            accumulated_gradient_new = T.cast(accumulated_gradient_new, theano.config.floatX)
            updates.append((accumulated_gradient, accumulated_gradient_new))
            param_update = T.cast(param - lr * gradient / T.sqrt(accumulated_gradient_new + epsilon), theano.config.floatX)
            updates.append((param, param - param_update))
        return updates

    def rmsprop(self, cost, params, lr=0.01, rho=0.9, epsilon=1e-6):
        """
        RMSProp - Root Mean Square
        Reference - http://www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf
        """

        lr = theano.shared(np.float32(lr).astype(theano.config.floatX))
        epsilon = theano.shared(np.float32(epsilon).astype(theano.config.floatX))
        rho = theano.shared(np.float32(rho).astype(theano.config.floatX))

        gradients = T.grad(
            theano.gradient.grad_clip(cost, -1 * self.clip, self.clip),
            params
        )

        updates = []
        for param, gradient in zip(params, gradients):
            accumulated_gradient = theano.shared(np.zeros_like(param.get_value(borrow=True)).astype(np.float32), borrow=True)
            accumulated_gradient_new = accumulated_gradient * rho + gradient ** 2 * (1 - rho)
            accumulated_gradient_new = T.cast(accumulated_gradient_new, theano.config.floatX)
            updates.append((accumulated_gradient, accumulated_gradient_new))
            param_update = T.cast(param - lr * gradient / T.sqrt(accumulated_gradient_new + epsilon), theano.config.floatX)
            updates.append((param, param_update))
        return updates

    def adam(self, cost, params, lr=0.001, beta_1=0.9, beta_2=0.999, epsilon=1e-8):
        """
        ADAM
        Reference - http://arxiv.org/pdf/1412.6980v8.pdf - Page 2
        """

        lr = theano.shared(np.float32(lr).astype(theano.config.floatX))
        epsilon = theano.shared(np.float32(epsilon).astype(theano.config.floatX))
        beta_1 = theano.shared(np.float32(beta_1).astype(theano.config.floatX))
        beta_2 = theano.shared(np.float32(beta_2).astype(theano.config.floatX))
        t = theano.shared(np.float32(1.0).astype(theano.config.floatX))

        gradients = T.grad(
            theano.gradient.grad_clip(cost, -1 * self.clip, self.clip),
            params
        )

        updates = []
        for param, gradient in zip(params, gradients):
            param_value = param.get_value(borrow=True)
            m_tm_1 = theano.shared(np.zeros_like(param_value).astype(np.float32), borrow=True)
            v_tm_1 = theano.shared(np.zeros_like(param_value).astype(np.float32), borrow=True)

            m_t = beta_1 * m_tm_1 + (1 - beta_1) * gradient
            v_t = beta_2 * v_tm_1 + (1 - beta_2) * gradient ** 2

            m_hat = m_t / (1 - beta_1)
            v_hat = v_t / (1 - beta_2)

            updated_param = param - (lr * m_hat) / (T.sqrt(v_hat) + epsilon)
            updates.append((m_tm_1, m_t))
            updates.append((v_tm_1, v_t))
            updates.append((param, updated_param))

        updates.append((t, t + 1.0))
        return updates


#! /usr/bin/python
# -*- coding:utf-8 -*-

from __future__ import print_function
from __future__ import division
import sys
import traceback
import pyopencl as cl
import ctypes

import OpenGL
OpenGL.FORWARD_COMPATIBLE_ONLY = True
#OpenGL.FULL_LOGGING=True
#OpenGL.ERROR_LOGGING=True
import OpenGL.GL as gl
import logging
logging.basicConfig(level = logging.INFO)

import numpy as np

from traits.etsconfig.api import ETSConfig
ETSConfig.toolkit = 'qt4'
#export QT_API=pyqt

from pyface.qt import QtGui, QtCore
from pyface.qt.QtOpenGL import QGLWidget, QGLFormat

from traits.api import (HasTraits, Int, Float, Bool, Range, Str, Tuple, List, Array, Property, Instance, Python)
from traitsui.api import View, Item, TupleEditor, RangeEditor, ListEditor
from traitsui.qt4.editor import Editor
from traitsui.qt4.basic_editor_factory import BasicEditorFactory

from GLShader import Shader
from camimage import CamImage

is_qt5 = QtCore.__version_info__ >= (5,)

class GLImgWidget(QGLWidget):
    """
    with feature: fill texture directly from cl kernel, using CL/GL
    interoperability (shared texture/image)
    """

    vertex_shader_src = """
    #version 330
    uniform float zoom;
    uniform vec2 img_center;
    in vec3 vert;
    in vec2 texcoord;
    out vec2 coord;
    void main() {
        coord = texcoord;
        gl_Position = vec4((vert.x+img_center.x)*zoom, (vert.y+img_center.y)*zoom, 0.5f, 1.0f);
    }
    """
    pixel_shader_color_src = """
    #version 330
    uniform sampler2D tex;
    in vec2 coord;
    out vec4 color;
    void main() {
        vec4 t = texture(tex, coord);
        //color = texture(tex, coord);
        color = vec4(t.r, t.g, t.b, 1.0f);
    }
    """

    pixel_shader_gray_src = """
    #version 330
    uniform sampler2D tex;
    in vec2 coord;
    out vec4 color;
    void main() {
        vec4 t = texture(tex, coord);
        color = vec4(t.r, t.r, t.r, 1.0f);
    }
    """
    #texture = None

    def __init__(self, parent=None, size = (640,480), interpolation=False, bits=8):
        #turned of interpolation for phase stepping spots (Franziska Juni 2019)
        #require OpenGL 3.2 Core
        format = QGLFormat()
        format.setSampleBuffers(True)

        if sys.platform == 'darwin':
            format.setVersion(3,2)
            format.setProfile(QGLFormat.CoreProfile)

        QGLWidget.__init__(self, format, parent)
        #print 'GL version: %d.%d'%(self.format().majorVersion(), self.format().minorVersion())

        try:
            self.pixel_ratio = self.devicePixelRatio()
        except AttributeError:
            self.pixel_ratio = 1

        #self.setFocusPolicy(StrongFocus)

        self.img_width, self.img_height = size
        self.interpolation = interpolation
        self.components = 1
        self.bits = bits
        self._roi = (0,0,0,0) #ROI w h x y
        self.texture = None
        self.camimage = None
        self.camimage_changed = False
        self.viewport = 0,0,0,0

        self.zoom = 1.0
        self.img_center = (0.0, 0.0) #image center: normalized image coordinates that are drawn in center of window

        self._mm_origin = None

        qsp = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred,
                                QtGui.QSizePolicy.Preferred
            #QtGui.QSizePolicy.MinimumExpanding,
            #QtGui.QSizePolicy.MinimumExpanding,
            #QtGui.QSizePolicy.Ignored,
            #QtGui.QSizePolicy.Ignored,

            )
        qsp.setHeightForWidth(True)
        self.setSizePolicy(qsp)

    def sizeHint(self):
        #recommended size, note: not strictly obeyed by qt
        return QtCore.QSize(self.img_width, self.img_height)

    def heightForWidth(self, w):
        h = int(round(w*self.img_height/self.img_width))
        #print 'height', h
        return h

    def resizeGL(self, w, h):
        #self.makeCurrent()
        aspect = self.img_height/self.img_width
        hi = int(round(w*aspect)) #ideal sizes
        wi = int(round(h/aspect))
        if hi > h:
            wt, ht = wi, h
        else:
            wt, ht = w, hi

        bx = int(0.5*(w-wt))
        by = int(0.5*(h-ht))
        gl.glViewport(bx, by, wt, ht)
        #gl.glFlush()
        self.viewport = bx, by, wt, ht #????

    def initializeGL(self):
        """overrides virtual function of QGLWidget, called when painting widget content is requested"""

        self.init_cl()
        self.init_gl()
        #data = self.init_data_rgb()
        #data = self.init_data_gray()
        data = self.init_data_gray_16()
        camimage = CamImage()
        camimage.data_roi = data
        self.update_texture_from_camimage(camimage)
        self.create_shared_image() #TODO: not used
        print("OpenGL initialized")

    def create_cl_context(self, gl_sharing = True):
        #global cl_context
        #if cl_context is not None:
        #    return cl_context
        #TODO: remove?????

        from pyopencl.tools import get_gl_sharing_context_properties

        #create context with GL sharing enabled
        platform = cl.get_platforms()[0]
        self.device = platform.get_devices(cl.device_type.GPU)[1]  ##### Mac fails with 0 (Iris)
        # TODO: need to find a way to get GPU device used for current OpenGL context

        if sys.platform == "darwin":
            cl_ctx = cl.Context(properties=get_gl_sharing_context_properties(),
                                devices=[self.device,])
        else:
            try:
                cl_ctx = cl.Context(properties=[(cl.context_properties.PLATFORM, platform)]
                                      + get_gl_sharing_context_properties())
            except:
                print("oops")
                cl_ctx = cl.Context(properties=[(cl.context_properties.PLATFORM, platform)]
                                      + get_gl_sharing_context_properties(),
                                      devices = [platform.get_devices()[0]])
        return cl_ctx

    def init_cl(self):
        self.cl_ctx = self.create_cl_context()
        self.cl_queue = cl.CommandQueue(self.cl_ctx)

        if False:
            #build CL kernels
            with open('imgkernel.cl') as f:
                source = f.read()
                self.cl_prog = cl.Program(self.cl_ctx, source).build()
                build_info = self.cl_prog.get_build_info(self.device, cl.program_build_info.LOG)
            if build_info:
                print('build info:')
                print(build_info)

    def create_shared_image(self):
        #create shared cl_image from texture
        self.cl_image_shared = cl.GLTexture(context = self.cl_ctx,
                                            flags = cl.mem_flags.WRITE_ONLY,
                                            texture_target = gl.GL_TEXTURE_2D,
                                            miplevel = 0,
                                            texture = self.texture,
                                            dims = 2)

    def init_texture(self, w, h, c, bits):
        """(re)init texture, no upload"""
        self.img_width, self.img_height = w, h
        self.components = c
        self.bits = bits
        self.format = {1: gl.GL_RED,
                       3: gl.GL_BGR,
                       4: gl.GL_BGRA}[self.components]

        if self.texture is not None:
            gl.glDeleteTextures([self.texture])

        ## texture
        gl.glActiveTexture(gl.GL_TEXTURE0)
        self.texture = gl.glGenTextures(1)
        gl.glBindTexture(gl.GL_TEXTURE_2D, self.texture)

        #create empty GL texture: RGBA 8bit
        #TODO: float????
        gl.glTexImage2D(gl.GL_TEXTURE_2D, 0,
                        gl.GL_RGBA, self.img_width, self.img_height, 0,
                        gl.GL_RGBA,
                        gl.GL_UNSIGNED_BYTE if bits <=8 else gl.GL_UNSIGNED_SHORT,
                        #gl.GL_UNSIGNED_BYTE,
                        None)
        #set texture parameters
        gl.glTexParameter(gl.GL_TEXTURE_2D, #texture filter for minification
                          gl.GL_TEXTURE_MIN_FILTER,
                          gl.GL_LINEAR_MIPMAP_LINEAR,
                          )

        gl.glTexParameter(gl.GL_TEXTURE_2D, #texture filter for magnification
                          gl.GL_TEXTURE_MAG_FILTER,
                          gl.GL_LINEAR if self.interpolation else gl.GL_NEAREST
                          )

        gl.glGenerateMipmap(gl.GL_TEXTURE_2D) #generate mipmaps

        gl.glBindTexture(gl.GL_TEXTURE_2D, 0)

    def init_gl(self):
        #enable multisampling
        gl.glEnable(gl.GL_MULTISAMPLE)

        #gl.glDisable(gl.GL_DEPTH_TEST)

        #print "samples", gl.glGetInteger(gl.GL_SAMPLES)

        gl.glEnable(gl.GL_BLEND)
        gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)

        #cl writes into image (shared with texture), used in shader

        #set mipmap parameters
        #gl.glTexParameter(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_BASE_LEVEL, 0)
        #gl.glTexParameter(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAX_LEVEL, 0)

        ##sampler
        #self.sampler = gl.glGenSamplers(1)
        #gl.glSamplerParameter(self.sampler, gl.GL_TEXTURE_MIN_FILTER, GL_NEAREST)
        #gl.glSamplerParameter(self.sampler, gl.GL_TEXTURE_MAG_FILTER, GL_NEAREST)

        #shader
        self.shader_color = Shader(self.vertex_shader_src,
                                   self.pixel_shader_color_src)
        self.shader_gray = Shader(self.vertex_shader_src,
                                  self.pixel_shader_gray_src)
        for s in (self.shader_color, self.shader_gray):
            s.bind()
            s.uniformi('tex', 0)
            s.unbind()

        #vertex and texture coordinates buffers
        self.vertices = np.array([-1.0,  1.0, 0.5,
                                  -1.0, -1.0, 0.5,
                                   1.0, -1.0, 0.5,
                                   1.0,  1.0, 0.5,
                                   ],
                                 dtype = np.float32)

        self.texcoords = np.array([0.0, 0.0,
                                   0.0, 1.0,
                                   1.0, 1.0,
                                   1.0, 0.0,
                                   ],
                                  dtype = np.float32)

        self.vertex_array = gl.glGenVertexArrays(1)
        gl.glBindVertexArray(self.vertex_array)

        self.vertex_buffer = gl.glGenBuffers(1)
        self.texcoords_buffer = gl.glGenBuffers(1)

        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.vertex_buffer)
        gl.glBufferData(target=gl.GL_ARRAY_BUFFER,
                        size = self.vertices.nbytes,
                        data = self.vertices.ctypes.data_as(ctypes.c_void_p),
                        usage = gl.GL_STATIC_DRAW)
        gl.glVertexAttribPointer(self.shader_color.attrib_loc('vert'), #TODO: gray too
                                 3, #vec2
                                 gl.GL_FLOAT, #type
                                 gl.GL_FALSE, #normalize
                                 4*3, #stride
                                 ctypes.c_void_p(0)
                                 ) #offset
        gl.glEnableVertexAttribArray(self.shader_color.attrib_loc('vert'))

        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.texcoords_buffer)
        gl.glBufferData(gl.GL_ARRAY_BUFFER,
                        size = self.texcoords.nbytes,
                        data = self.texcoords.ctypes.data_as(ctypes.c_void_p),
                        usage = gl.GL_STATIC_DRAW)
        gl.glVertexAttribPointer(self.shader_color.attrib_loc('texcoord'),
                                 2,
                                 gl.GL_FLOAT,
                                 gl.GL_FALSE,
                                 4*2,
                                 ctypes.c_void_p(0))
        gl.glEnableVertexAttribArray(self.shader_color.attrib_loc('texcoord'))
        gl.glBindVertexArray(0)

    def set_camimage(self, camimage):
        self.camimage = camimage
        self.camimage_changed = True

    #def set_imgdata(self, img):
    #    self.set_camimage(CamImage(img))

    def update_texture_from_camimage(self, camimg):
        #self.makeCurrent() #not necessary if called within paintGL
        camimg_shape = (camimg.w, camimg.h, camimg.c, camimg.bits)
        texture_shape = (self.img_width, self.img_height, self.components, self.bits)
        if self.texture is None  or  texture_shape != camimg_shape:
            #need (re)init
            self.init_texture(camimg.w, camimg.h, camimg.c, camimg.bits)
            self.update_texture_from_camimage(camimg)  # TODO: dangerous recursive call

            #resize viewport
            s = self.size()
            self.resizeGL(s.width(), s.height())
            #self.updateGL() #TODO????
        else: #update existing texture
            gl.glBindTexture(gl.GL_TEXTURE_2D, self.texture)
            camimg_roi = (camimg.roi_w, camimg.roi_h)+camimg.pos
            if camimg_roi == self._roi:
                #ROI unchanged, only update ROI data
                gl.glTexSubImage2D(gl.GL_TEXTURE_2D,
                                   0, #level
                                   camimg.pos[0], camimg.pos[1], camimg.roi_w, camimg.roi_h,
                                   self.format,
                                   gl.GL_UNSIGNED_BYTE if self.bits <= 8 else gl.GL_UNSIGNED_SHORT,
                                   camimg.data_roi)
            else: #update full texture and _roi cache
                self._roi = camimg_roi
                gl.glTexSubImage2D(gl.GL_TEXTURE_2D,
                                   0, #level
                                   0,0,camimg.w,camimg.h,
                                   self.format,
                                   gl.GL_UNSIGNED_BYTE if self.bits <= 8 else gl.GL_UNSIGNED_SHORT,
                                   camimg.data_fullsize)

            gl.glGenerateMipmap(gl.GL_TEXTURE_2D)
            gl.glBindTexture(gl.GL_TEXTURE_2D, 0)

    def calculate_texture_cl(self):
        # update texture from cl
        cl.enqueue_acquire_gl_objects(self.cl_queue, [self.cl_image_shared])# .wait()
        self.cl_prog.fill_texture(self.cl_queue,
                                  (self.img_width, self.img_height),
                                  #(64, 1),
                                  (8,8),
                                  self.cl_image_shared) # .wait()
        cl.enqueue_release_gl_objects(self.cl_queue, [self.cl_image_shared]).wait()

    def update_texture_cl(self):
        self.calculate_texture_cl()
        # gl.glFlush()  # TODO: needed? (before?)

        # regenerate mipmap
        gl.glBindTexture(gl.GL_TEXTURE_2D, self.texture)
        gl.glGenerateMipmap(gl.GL_TEXTURE_2D)
        gl.glBindTexture(gl.GL_TEXTURE_2D, 0)

        self.update()  # request redraw

    def paintGL(self):
        """overrides virtual function of QGLWidget, called when painting widget content is requested"""
        # print('entered paintGL')

        if self.camimage_changed:
            try:
                self.update_texture_from_camimage(self.camimage)
            except Exception as e:
                print("ignored exception in paintGL")
                exc_type, exc_value, exc_tb = sys.exc_info()
                info = str(e)
                print('\n'.join(traceback.format_exception(exc_type, exc_value, exc_tb)[2:]))

            self.camimage_changed = False

        try:
            gl.glClearColor(0.25, 0.25, 0.25, 1.0)
            gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)  # necessary for lab computers

            if self.components == 1:
                shader = self.shader_gray
            else:
                shader = self.shader_color

            shader.bind()

            #setting shader vars
            shader.uniformf('zoom', self.zoom)
            shader.uniformf('img_center', *self.img_center)

            #gl.glTexEnvi(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_REPLACE);
            gl.glActiveTexture(gl.GL_TEXTURE0)
            gl.glBindTexture(gl.GL_TEXTURE_2D, self.texture)
            #gl.glBindSampler( 0, self.sampler)
            gl.glBindVertexArray(self.vertex_array)
            gl.glDrawArrays(gl.GL_TRIANGLE_FAN, 0, 4)
            gl.glBindVertexArray(0)
            gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
            shader.unbind()
        except gl.GLError as e:
            print('caught GLError in paintGL, ignoring', e)

        # print('leaving paintGL')

    def init_data_rgb(self):
        data = np.zeros((self.img_height, self.img_width, 4), np.uint8) #rows, columns
        data[256:, 256:, 0] = 255
        data[:256, 256:, 1] = 255
        data[256] = 255
        data[:,256] = 255
        data[:100, :50:2, :3] = 255
        data[:,:,3] = 128 #alpha ?
        return data

    def init_data_gray(self):
        data = np.zeros((480, 640), np.uint8) #rows, columns
        data[:32:2, :32:2] = 255
        data[:32:2, -32:-1:2] = 128
        data[200] = 255
        data[300] = 200
        data[:,256] = 255
        return data

    def init_data_gray_16(self):
        data = np.zeros((480, 640), np.uint16) #rows, columns
        data[:32:2, :32:2] = 40000
        data[:32:2, -32:-1:2] = 40000
        data[200] = 30000
        data[300] = 30000
        data[:,256] = 30000
        return data

    def wheelEvent(self, event):
        if is_qt5:
            if event.pixelDelta().y() > 0 or event.angleDelta().y() > 0:
                self.zoom *= 1.2
            elif event.pixelDelta().y() < 0 or event.angleDelta().y() < 0:
                self.zoom /= 1.2
        else:
            if event.delta() > 0:
                self.zoom *= 1.2
            elif event.delta() < 0:
                self.zoom /= 1.2
        self.zoom = np.clip(self.zoom, (1./1.2)**10, 1.2**20)
        if abs(self.zoom - 1.) < 0.05:
            self.zoom = 1.

        self.do_update('wheel Event')
        event.ignore()

    def mousePressEvent(self, event):
        if event.button() == 1:
            self._mm_origin = event.x(), event.y()
            self._img_center = (self.img_center[0], self.img_center[1])
            event.ignore()
        else:
            print('screen pos', (event.x(), event.y()), 'img pos', self.screen_to_img(event.x(), event.y()))
            #pass

    def mouseMoveEvent(self, event):
        if self._mm_origin is not None:
            x, y = event.x(), event.y()
            viewport_width, viewport_height = self.viewport[2], self.viewport[3]
            self.img_center =  (self._img_center[0] + 2*self.pixel_ratio*(x-self._mm_origin[0])/(viewport_width*self.zoom),
                                self._img_center[1] - 2*self.pixel_ratio*(y-self._mm_origin[1])/(viewport_height*self.zoom))
            #TODO: temporarily removed
            self.do_update('mouseMoveEvent')
            event.ignore()

    def mouseReleaseEvent(self, event):
        self._mm_origin = None

    def screen_to_img(self, xs, ys):
        #convert screen coordinates to image coordinates
        #note: screen relative to widget top left
        #normalized pos. rel. viewport. top left (-1, -1), bottom right (1, 1)

        xv = (2*(xs-self.viewport[0])*self.pixel_ratio/self.viewport[2] - 1) #normalized pos rel. viewport
        yv = (2*(ys-self.viewport[1])*self.pixel_ratio/self.viewport[3] - 1) #normalized pos rel. viewport
        xi = xv/self.zoom - self.img_center[0] #pos rel image
        yi = yv/self.zoom + self.img_center[1] #pos rel image
        return xi, yi

    def do_update(self, context = 'no context given'):
        #threadname = threading.current_thread().name
        #if threadname is 'MainThread':
        #    self.update()
        #else:
        #    print 'do update not in MainThread', context
        self.update()

class SLMWidgetGL(GLImgWidget):

    def resizeGL(self, w, h):
        gl.glViewport(0, 0, self.img_width, self.img_height)
        self.viewport = (0, 0, self.img_width, self.img_height)

    def wheelEvent(self, event):
        pass

class SLMWindow(QtGui.QMainWindow):
    def __init__(self, holosize=(1920, 1080 ), secondary_screen = False):
        QtGui.QMainWindow.__init__(self)
        #window = QtGui.QWidget()
        self.slmwidget = SLMWidgetGL(None, size=holosize, interpolation=False)
        #layout = QtGui.QHBoxLayout()
        #layout.addWidget(self.imgwidget, 0, QtCore.Qt.AlignCenter)
        #window.setLayout(layout)
        #self.setCentralWidget(window)
        self.setCentralWidget(self.slmwidget)
        self.setWindowTitle('OpenGL image widget')

        if secondary_screen:
            #TODO: automatically detect SLM screen (from size?)
            self.move(2560, 0)
            #self.showMaximized()
            self.setWindowState(QtCore.Qt.WindowFullScreen)

        data = np.zeros( (holosize[1], holosize[0]), dtype=np.uint8)
        data[:500:2,:500:2] = 255
        self.slmwidget.set_camimage(CamImage(data))
        self.show()

class _GLImageEditor(Editor):
    """Editor for displaying image, uses GLImgWidget from glimgwidget"""
    scrollable = True
    #inherited attributes:
    #.value: class Image
    #.control: class GLImgWidget
    #inherited methods (to be implemented): update_editor(), rebuild_editor(), update_object() ?

    def init(self, parent):
        #self.sync_value(self.factory.xxx_name, 'trait_name', 'from')
        self.control = GLImgWidget()
        self.set_tooltip()

    def update_editor(self):
        self.control.set_camimage(self.value)
        self.control.do_update('_GLImageEditor:updateEditor')

class GLImageEditor(BasicEditorFactory):
    klass = _GLImageEditor

class GLOverlay(object):
    visible = True

    def __init__(self):
        self.initialized_GL = False

    def initGL(self):
        pass

    def paintGL(self, zoom, origin):
        raise Exception('Not implemented')

    def paint(self, zoom, origin):
        if not self.visible:
            return
        if not self.initialized_GL:
            #self.initGL()
            print('gloverlay not yet initialized!')

        self.paintGL(zoom, origin)

    def update_overlay(self, overlay_trait, img_size):
        pass

class GLOverlayPolygon(GLOverlay):
    scale = [400/640., 400/480.]
    center = [0.1, 0.]
    angle = 0.
    edge_color = [1.0, 1.0, 0.0, 1.0]

    GL_LINE = gl.GL_LINE_STRIP

    vertex_shader_src = """
    #version 150
    uniform float zoom;
    uniform vec2 origin;
    uniform vec2 center;
    uniform vec2 scale;
    uniform float angle;
    in vec3 vert;
    out vec2 coord;
    void main() {
    vec2 p = vec2( vert.x * cos(angle) - vert.y * sin(angle), vert.x * sin(angle) + vert.y*cos(angle));
    gl_Position = vec4( zoom*(scale.x*p.x - center.x + origin.x), zoom*(scale.y*p.y - center.y + origin.y), 0.5f, 1.0f);
    }
    """

    pixel_shader_src = """
    #version 150
    uniform vec4 edge_color;
    out vec4 color;
    void main() {
        //color = vec4(1.0f, 0.0f, 0.0f, 0.5f);
        color = edge_color;
    }
    """

    def initGL(self):

        self.init_vertices()

        self.shader = Shader(self.vertex_shader_src, self.pixel_shader_src)
        self.vertex_array = gl.glGenVertexArrays(1)
        gl.glBindVertexArray(self.vertex_array)
        self.vertex_buffer = gl.glGenBuffers(1)
        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.vertex_buffer)
        gl.glBufferData(target=gl.GL_ARRAY_BUFFER,
                        size = self.vertices.nbytes,
                        data = self.vertices.ctypes.data_as(ctypes.c_void_p),
                        usage = gl.GL_STATIC_DRAW)
        gl.glVertexAttribPointer(self.shader.attrib_loc('vert'),
                                 3, #vec3
                                 gl.GL_FLOAT, #type
                                 gl.GL_FALSE, #normalize
                                 4*3, #stride
                                 ctypes.c_void_p(0)
                                 )
        gl.glEnableVertexAttribArray(self.shader.attrib_loc('vert'))
        gl.glBindVertexArray(0)

        gl.glEnable(gl.GL_LINE_SMOOTH)
        #print gl.glGet(gl.GL_SMOOTH_LINE_WIDTH_RANGE)

        self.initialized_GL = True

    def paintGL(self, zoom, origin):
        shader = self.shader
        shader.bind()
        shader.uniformf('origin', *origin)
        shader.uniformf('zoom', zoom)
        shader.uniformf('angle', self.angle*np.pi/180)
        shader.uniformf('scale', *self.scale)
        shader.uniformf('center', *self.center)
        shader.uniformf('edge_color', *self.edge_color)

        gl.glBindVertexArray(self.vertex_array)
        lw = zoom if zoom < 1.0 else 1.
        gl.glLineWidth(lw) #TODO: ?????
        gl.glDrawArrays(self.GL_LINE, 0, self.n_vertices)
        gl.glBindVertexArray(0)
        shader.unbind()

class GLOverlayCircle(GLOverlayPolygon):

    def init_vertices(self):
        self.n_vertices = n = 64
        alpha = np.arange(n, dtype=np.float32)*(2*np.pi/(n-1))
        self.vertices = np.empty((n,3), dtype = np.float32)
        self.vertices[:,0] = np.cos(alpha)
        self.vertices[:,1] = np.sin(alpha)
        self.vertices[:,2] = 0.6
        self.vertices.shape = (-1,)

    def update_overlay(self, overlay_trait, img_size):
        nx, ny = img_size
        o = overlay_trait
        self.visible = o.visible
        self.angle = o.angle
        self.scale = [2*o.radius/nx, 2*o.radius/ny]
        self.center = [-((o.center[0] + 0.5)*2./nx - 1.),
                        ((o.center[1] + 0.5)*2./ny - 1.)]

class GLOverlayRectangle(GLOverlayPolygon):

    scale = [30./640, 100./480]
    center = [100/640.,
              (0.5*(200+300 + 1)/480.-0.5)*2
              ]
    edge_color = [0.0, 1.0, 0.0, 0.9]

    def init_vertices(self):
        z0 = 0.3
        self.vertices = np.array([-1.0,  1.0, z0,
                                  -1.0, -1.0, z0,
                                   1.0, -1.0, z0,
                                   1.0,  1.0, z0,
                                   -1.0,  1.0, z0,
                                   ],
                                 dtype = np.float32)
        self.n_vertices = 5

    def update_overlay(self, overlay_trait, img_size):
        nx, ny = img_size
        o = overlay_trait
        self.visible = o.visible
        self.angle = o.angle

        self.scale = [o.size[0]*1./nx, o.size[1]*1./ny]
        self.center = [-((o.center[0]+0.5)*2./nx - 1.),
                        ((o.center[1]+0.5)*2./ny - 1.)]

class GLOverlayGrid(GLOverlayRectangle):
    edge_color = [0.0, 0.7, 0.7, 0.4]
    GL_LINE = gl.GL_LINES

    def init_vertices(self):
        n = 16+1
        v = np.zeros((4*n, 3), np.float32)
        p = np.linspace(-1,1,n)
        v[0:2*n:2, 0] = p
        v[1:2*n:2, 0] = p
        v[0:2*n:2, 1] = -1
        v[1:2*n:2, 1] = 1

        v[2*n::2, 0] = -1
        v[2*n+1::2, 0] = 1
        v[2*n::2, 1] = p
        v[2*n+1::2, 1] = p

        v[:,2] = 0.45

        self.vertices = v
        self.n_vertices = v.shape[0]
        self.vertices.shape = (-1,)

class GLOverlayArrow(GLOverlayPolygon):
    scale = [0, 0]
    center = [0.1, 0.2]
    edge_color = [0.0, 0.25, 0.75, 0.75]

    def init_vertices(self):
        z0 = 0.5
        self.vertices = np.array([ 0.0, 0.0, z0,
                                   0.0, 0.1, z0,
                                   0.9, 0.1, z0,
                                   1.0, 0.0, z0,
                                   0.9,-0.1, z0,
                                   0.0,-0.1, z0,
                                   0.0, 0.0, z0,
                                   ], dtype = np.float32)
        self.n_vertices = 7

    def update_overlay(self, overlay_trait, img_size):
        nx, ny = img_size
        o = overlay_trait
        self.visible = o.visible
        self.angle = np.arctan2(o.direction[0], o.direction[1])*(180./np.pi)
        length = np.hypot(o.direction[0], o.direction[1])
        self.scale = [length*1./nx, length*1./ny]
        self.center = [-((o.center[0]+0.5)*2./nx - 1.),
                        ((o.center[1]+0.5)*2./ny - 1.)]

class GLOverlaySpots(GLOverlay):
    spots = np.zeros( (10,3), np.float32)
    spots[0] = [0.5,0.,0.]
    colors = np.zeros( (10, 4), np.float32)
    edge_color = [1.0, 0.0, 0.0, 0.5]
    spots_changed = True
    scale = (0.01, 0.01)

    vertex_shader_src = """
    #version 330
    struct vData
    {
       vec4 color;
    };
    uniform float zoom;
    uniform vec2 origin;
    in vec3 vert;
    in vec4 colors;
    out vData vertices;
    void main()
    {
       vec2 p = zoom*(vec2( vert.x, vert.y) + origin);
       gl_Position = vec4(p.x, p.y, 0.5f, 1.0f);
       vertices.color = colors; //vec4(0.f, 0.f, 1.f, .5f);
    }
    """

    geom_shader_src = """
    #version 330

    #define n 21
    #define k 2*3.141592f/(n-1)
    layout(points) in;
    layout(line_strip, max_vertices = n) out;

    struct vData
    {
       vec4 color;
    };

    uniform float zoom;
    uniform vec2 scale;
    in vData vertices[];
    out vData frag;

    void main()
    {
       int i;
       for(i = 0; i < n; i++)
       {
          gl_Position = gl_in[0].gl_Position + zoom*vec4(scale.x*cos(i*k), scale.y*sin(i*k), 0.f, 0.f);
          frag.color = vertices[0].color;
          EmitVertex();
       }
    EndPrimitive();
    }
    """

    pixel_shader_src = """
    #version 330

    struct vData
    {
       vec4 color;
    };

    in vData frag;
    uniform vec4 edge_color;
    out vec4 color;
    void main() {
        color = edge_color;
        color = frag.color;
    }
    """

    def initGL(self):

        self.init_vertices()
        self.init_colors()

        self.shader = Shader(self.vertex_shader_src, self.pixel_shader_src, self.geom_shader_src)
        self.vertex_array = gl.glGenVertexArrays(1)
        gl.glBindVertexArray(self.vertex_array)

        self.vertex_buffer = gl.glGenBuffers(1)
        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.vertex_buffer)
        gl.glBufferData(target=gl.GL_ARRAY_BUFFER,
                        size = self.vertices.nbytes,
                        data = self.vertices.ctypes.data_as(ctypes.c_void_p),
                        usage = gl.GL_STATIC_DRAW)

        gl.glVertexAttribPointer(self.shader.attrib_loc('vert'),
                                 3, #vec3
                                 gl.GL_FLOAT, #type
                                 gl.GL_FALSE, #normalize
                                 4*3, #stride
                                 ctypes.c_void_p(0)
                                 )
        gl.glEnableVertexAttribArray(self.shader.attrib_loc('vert'))

        self.colors_buffer = gl.glGenBuffers(1)
        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.colors_buffer)
        gl.glBufferData(target=gl.GL_ARRAY_BUFFER,
                        size = self.colors.nbytes,
                        data = self.colors.ctypes.data_as(ctypes.c_void_p),
                        usage = gl.GL_STATIC_DRAW)
        gl.glVertexAttribPointer(self.shader.attrib_loc('colors'),
                                 4, gl.GL_FLOAT, gl.GL_FALSE, 4*4, ctypes.c_void_p(0))
        gl.glEnableVertexAttribArray(self.shader.attrib_loc('colors'))

        gl.glBindVertexArray(0)

        #gl.glEnable(gl.GL_POINT_SMOOTH)
        self.initialized_GL = True

    def init_vertices(self):
        self.n_vertices = self.spots.shape[0]
        self.vertices = self.spots.view()
        self.vertices[:,0] = np.arange(self.n_vertices)*0.1
        self.vertices.shape = (-1,)

    def init_colors(self):
        c = self.colors
        c[0] = [1.0, 0.0, 0.0, 1.0]
        c[1] = [1.0, 1.0, 0.0, 1.0]
        c[2] = [0.0, 1.0, 0.0, 1.0]
        c[3] = [0.0, 1.0, 1.0, 1.0]
        c[4] = [0.0, 0.0, 1.0, 1.0]
        c[5:] = [1.0, 0.0, 1.0, 0.5]

        self.colors.shape = (-1,)

    def paintGL(self, zoom, origin):
        if self.spots_changed:
            #update spots
            gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.vertex_buffer)
            gl.glBufferSubData(target=gl.GL_ARRAY_BUFFER,
                               offset = 0,
                               size = 3*4*self.n_vertices,
                               data = self.vertices.ctypes.data_as(ctypes.c_void_p),
                               )

            gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.colors_buffer)
            gl.glBufferSubData(target=gl.GL_ARRAY_BUFFER,
                               offset = 0,
                               size = 4*4*self.n_vertices,
                               data = self.colors.ctypes.data_as(ctypes.c_void_p),
                               )

        shader = self.shader
        shader.bind()
        shader.uniformf('origin', *origin)
        shader.uniformf('zoom', zoom)
        shader.uniformf('scale', *self.scale)
        shader.uniformf('edge_color', *self.edge_color)

        gl.glPointSize(10*zoom)
        gl.glBindVertexArray(self.vertex_array)
        gl.glDrawArrays(gl.GL_POINTS, 0, self.n_vertices)
        gl.glBindVertexArray(0)
        shader.unbind()

    def update_overlay(self, overlay_trait, img_size):
        nx, ny = img_size
        o = overlay_trait
        self.visible = o.visible
        self.angle = o.angle

        for k, pos in enumerate(o.spots_position):
            x, y = pos
            self.spots[k, 0:2] = [((x+0.5)*2./nx - 1.), -((y+0.5)*2./ny - 1.)]
        self.n_vertices = len(o.spots_position)
        self.spots_changed = True
        self.scale = (20/nx, 20/ny)

class GLOverlayWarpArrows(GLOverlay):
    """shows arrows indicating distortions of BFP image"""
    edge_color = [0.25, 0.75, 0.0, 0.75]

    data_length = 17*17 #TODO: initialize in initGL from self.data
    data = np.zeros( (17,17, 4), np.float32) #data for each spot: idx_x, idx_y, dx, dy
    data_changed = True

    scale = (0.05, 0.05)
    grid_origin_normalized = (0., 0.) #
    grid_lattice_vector1 = (20., 0.)
    grid_lattice_vector2 = (0., 20.)

    #Vertex Shader: apply image zoom and origin to overlays
    vertex_shader_src = """
    #version 330
    uniform vec2 origin; //origin image area?
    uniform float zoom;
    uniform vec2 grid_origin_normalized;
    uniform vec2 grid_lattice_vector1;
    uniform vec2 grid_lattice_vector2;
    uniform vec2 scale;

    in vec2 idx;
    in vec2 displacement;

    out vec2 arrow; //output to geometry shader: transformed displacement in normalized coordinates

    void main() {

    vec2 g1 = grid_lattice_vector1;
    vec2 g2 = -grid_lattice_vector2;

    vec2 p = - zoom * vec2(g1.x*idx.x + g2.x*idx.y, g1.y*idx.x + g2.y*idx.y);
    vec2 o = zoom * (origin  - grid_origin_normalized);
    gl_Position = vec4(scale.x*p.x + o.x, scale.y*p.y + o.y, 0.6f, 1.0f);
    arrow = zoom*scale*vec2(displacement.x, displacement.y);
    }
    """

    geom_shader_src = """
    #version 330
    #define n 17
    #define k 2*3.141592f/(n-1)
    layout(points) in; //struct gl_in[]
    layout(line_strip, max_vertices = 19) out;

    in vec2 arrow[]; //input from vertex shader

    uniform vec2 scale;
    uniform float zoom;
    void main()
    {
       vec2 pos = vec2(gl_in[0].gl_Position.x, gl_in[0].gl_Position.y);
       vec2 head = pos + vec2(arrow[0].x,  arrow[0].y);

       gl_Position = vec4(pos.x, pos.y, 0.f, 1.f);
       EmitVertex();
       gl_Position = vec4(head.x, head.y, 0.f, 1.f);
       EmitVertex();
       EndPrimitive();

       //circle around head (spot position)
       int i;
       for(i = 0; i < n; i++)
       {
          gl_Position = vec4(head.x + 3*zoom*scale.x * cos(i*k),
                             head.y + 3*zoom*scale.y * sin(i*k),
                             0.f, 1.f);
          EmitVertex();
       }
       EndPrimitive();
    }
    """

    pixel_shader_src = """
    #version 330
    uniform vec4 edge_color;
    out vec4 color;
    void main() {
       color = edge_color;
    }
    """

    def initGL(self):
        #init vertices
        #self.n_arrows = 17*17; #TODO: take from self.arrow_data
        #TODO: replace by meshgrid
        idx_x, idx_y = np.meshgrid(np.arange(17)-8, np.arange(17)-8)
        self.data[...,0] = idx_x
        self.data[...,1] = idx_y

        self.data_flat = self.data.view()
        self.data_flat.shape = (-1,) #nötig?

        #compile shaders
        self.shader = Shader(self.vertex_shader_src, self.pixel_shader_src, self.geom_shader_src)

        #init vertex buffer objects
        self.data_gl_buffer = gl.glGenBuffers(1)
        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.data_gl_buffer)
        gl.glBufferData(target = gl.GL_ARRAY_BUFFER,
                        size = self.data_flat.nbytes,
                        data = self.data_flat.ctypes.data_as(ctypes.c_void_p), #TODO: also self.data possible?
                        usage = gl.GL_DYNAMIC_DRAW) #TODO: static ?

        #init vertex array and vertex attributes
        self.data_gl_array = gl.glGenVertexArrays(1)
        gl.glBindVertexArray(self.data_gl_array)

        #data: for each point: index (idx_x, idx_y), displacement dx, dy
        gl.glEnableVertexAttribArray(self.shader.attrib_loc('idx'))
        gl.glVertexAttribPointer(self.shader.attrib_loc('idx'),
                                 2, #vec2
                                 gl.GL_FLOAT, #type
                                 gl.GL_FALSE, #normalize
                                 4*4, #stride in bytes
                                 ctypes.c_void_p(0) #offset in bytes
                                 )
        gl.glEnableVertexAttribArray(self.shader.attrib_loc('displacement'))
        gl.glVertexAttribPointer(self.shader.attrib_loc('displacement'),
                                 2, #vec
                                 gl.GL_FLOAT, #type
                                 gl.GL_FALSE, #normalize
                                 4*4, #stride
                                 ctypes.c_void_p(2*4), #offset in bytes
                                 )

        gl.glBindVertexArray(0)
        self.initialized_GL = True

    def paintGL(self, zoom, origin):
        if self.data_changed:
            #update arrow data
            gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.data_gl_buffer)
            gl.glBufferSubData(target=gl.GL_ARRAY_BUFFER,
                               offset=0,
                               size=self.data_flat.nbytes, #4*4*self.data_length, #number of arrows, 4 floats per arrow, TODO: init from data
                               data = self.data_flat.ctypes.data_as(ctypes.c_void_p), #TODO: self.data ?
                               )
            shader = self.shader
            shader.bind()

            #vertex shader
            shader.uniformf('origin', *origin)
            shader.uniformf('zoom', zoom)
            shader.uniformf('grid_origin_normalized', *self.grid_origin_normalized) #center arrow ensemble in normalized coordinates
            shader.uniformf('grid_lattice_vector1', *self.grid_lattice_vector1)
            shader.uniformf('grid_lattice_vector2', *self.grid_lattice_vector2)
            shader.uniformf('scale', *self.scale) #scaling factors from pixel to normalized coordinates

            #geometry shader

            #pixel shader
            shader.uniformf('edge_color', *self.edge_color)

            gl.glBindVertexArray(self.data_gl_array)
            gl.glDrawArrays(gl.GL_POINTS, 0, self.data_flat.shape[0])
            gl.glBindVertexArray(0)
            shader.unbind()

    def update_overlay(self, overlay_trait, img_size):
        #take over data from overlay trait model
        nx, ny = img_size
        o = overlay_trait

        self.visible = o.visible
        self.grid_origin_normalized = [-((o.grid_origin[0]+0.5)*2./nx - 1.), #origin grid in normalized coordinates
                                        ((o.grid_origin[1]+0.5)*2./ny - 1.)]
        self.grid_lattice_vector1 = (-o.grid_lattice_vector1[0], o.grid_lattice_vector1[1]);
        self.grid_lattice_vector2 = (-o.grid_lattice_vector2[0], o.grid_lattice_vector2[1]);

        D = o.displacements #.view()
        self.data[:,:,2] =  -D[:,:,0]
        self.data[:,:,3] =  D[:,:,1]

        self.scale = (2./nx, 2./ny) #scaling pixel to normalized coordinates (-1...1)
        self.data_changed = True

class GLImgWithOverlaysWidget(GLImgWidget):

    def __init__(self, *args, **kwargs):
        GLImgWidget.__init__(self, *args, **kwargs)
        self.gloverlays = dict()

    #override
    def init_gl(self):
        GLImgWidget.init_gl(self)
        for o in self.gloverlays.values():
            o.initGL()

    #override
    def paintGL(self):
        GLImgWidget.paintGL(self)
        for o in self.gloverlays.values():
            try:
                o.paint(zoom=self.zoom, origin=self.img_center)
            except gl.GLError as e:
                print('caught GLError in paintGL of overlay, ignoring')

class OverlayTrait(HasTraits):
    idx = Int(0)
    angle = Float(0.) #Range(-45., 45., 0)
    visible = Bool(True)

    def handle_mouse(self, mx, my, event_type):
        #event_type: 'left_click', 'left_drag', 'drag', ...
        #mx, my: mouse coordinates, in image coordinates (image pixels relative top left)
        pass

class OverlayTraitCircle(OverlayTrait):
    center = Tuple(Range(0, 2000, 500), Range(0, 1200, 500))#Int(300), Int(400))
    radius = Range(0, 1000, 100)
    trait_view = View(Item('center'), #editor = TupleEditor(editors = [RangeEditor(), RangeEditor()])),
                      Item('radius'),
                      )

class OverlayTraitRectangle(OverlayTrait):
    def __init__(self, **kwargs):
        OverlayTrait.__init__(self, **kwargs)
        self._mouse_interactive = False, (0,0)

    size = Tuple(Range(0, 2000, 100), Range(0, 2000, 100))
    center = Tuple(Int, Int)
    trait_view = View(
        Item('visible'),
        Item('center', editor = TupleEditor(labels = ['x', 'y'])),
        Item('size', editor = TupleEditor(labels = ['w', 'h'])),
        Item('angle', editor = RangeEditor(low=-90, high=90)),
        )
    _mouse_interactive = Python

    def handle_mouse(self, mx, my, event_type):
        #called by filter function, return True if event is accepted (skip event)
        if event_type is 'left_click':
            posx, posy = self.center
            if (abs(posx-mx) + abs(posy-my)) < 20:
                #print "hit rectangle center"
                self._mouse_interactive = (True, (posx-mx, posy-my))
                return True
        elif event_type is 'drag':
            active,(dx, dy)  = self._mouse_interactive
            if active:
                self.center = (int(mx+dx), int(my+dy))
                return True
        elif event_type is 'release':
            active, (dx, dy) = self._mouse_interactive
            if active:
                self._mouse_interactive = (False, (0,0))
                return True
        return False

class OverlayTraitGrid(OverlayTrait):
    extend = Range(0,2000,500)
    size = Property(Tuple(Float, Float), depends_on = 'extend')
    center = Tuple(Float(100), Float(100))
    trait_view = View(
        Item('visible'),
        Item('angle', editor = RangeEditor(low=-45., high=45.)),
        Item('center', editor = TupleEditor(labels = ['x', 'y'])),
        #Item('size', editor = TupleEditor(labels = ['w', 'h'])),
        Item('extend'),
        )

    def _get_size(self):
        return (self.extend, self.extend)

class OverlayTraitSpots(OverlayTrait):

    def __init__(self, **kwargs):
        OverlayTrait.__init__(self, **kwargs)
        self._spot_interactive = False, 0, (0,0)

    spots_position = List(
        Tuple(
            Float, #Range(0.,1000., 300.),
            Float, #Range(0.,1000., 300.),
            ),
        value = [(100., 0),
                 (200., 10),
                 (300., 20),
                 (400., 30),
        ],
        )

    _spot_interactive = Python#( default_value = (False,0,(0,0)))

    trait_view = View(
        Item('spots_position',),#editor = TupleEditor(labels = ['x', 'y'])),
        Item('visible'),
        )

    def handle_mouse(self, mx, my, event_type):
        #called by filter function, return True if event is accepted (skip event)
        if event_type is 'left_click':
            for k, (posx, posy) in enumerate(self.spots_position):
                if (abs(posx-mx) + abs(posy-my)) < 10:
                    #print "hit spot #", k
                    self._spot_interactive = (True, k, (posx-mx, posy-my))
                    return True
        elif event_type is 'drag':
            active,k,(dx, dy)  = self._spot_interactive
            if active:
                self.spots_position[k] = (mx+dx, my+dy)
                return True
        elif event_type is 'release':
            active, k, (dx, dy) = self._spot_interactive
            if active:
                self._spot_interactive = (False, 0, (0,0))
                return True
        return False

class OverlayTraitArrow(OverlayTrait):
    center = Tuple(Float(100), Float(100))
    direction = Tuple(Float(5.), Float(5.), Float(5.))
    trait_view = View(
        Item('visible'),
        Item('center',
             editor = TupleEditor(
                 labels = ['x', 'y'],
                 editors = [RangeEditor(low=0, high=2000),
                            RangeEditor(low=0, high=2000)],
                 )
                 ),
        Item('direction',
             editor = TupleEditor(editors = [RangeEditor(low=0, high=50, mode='slider'),
                                             RangeEditor(low=0, high=50, mode='slider'),
                                             RangeEditor(low=0, high=50, mode='slider'),],),),)

class OverlayTraitWarpArrows(OverlayTrait):
    grid_origin = Tuple(Float(100), Float(100))
    grid_lattice_vector1 = Tuple(Float(20), Float(20))
    grid_lattice_vector2 = Tuple(Float(20), Float(20))

    displacements = Array(dtype=np.float32, shape = (17, 17, 2))

    def _displacements_default(self):
        return (np.random.random((17, 17, 2)) - 0.5)*30

    trait_view = View(
        Item('visible'),
        Item('grid_origin'),
        Item('grid_lattice_vector1'),
        Item('grid_lattice_vector2'),
        )

class MyEventFilter(QtCore.QObject):
    def __init__(self, callback=None):
        QtCore.QObject.__init__(self)
        self.callback = callback

    def eventFilter(self, obj, event):
        r = self.callback(obj, event)
        return bool(r)

class _GLImageWithOverlaysEditor(Editor):
    scrollable = True

    overlays = List( Instance( OverlayTrait, () ))
    # inherited trait 'value = Image'

    event_filter = Python

    def init(self, parent):
        self.control = GLImgWithOverlaysWidget()
        for k, o in enumerate(self.overlays):
                self._init_overlayGL(o, idx=k)

        self.set_tooltip()

        self.event_filter = MyEventFilter(callback=self.handle_mouse_event) #need to keep reference
        self.control.installEventFilter(self.event_filter) #TODO: install event filter for interactivity

        #TODO:
        #unsync_value !?
        #def dispose(self):
        # print "cleanup editor"
        #self.sync_value(self.factory.overlays,
        #                'overlays',
        #                is_list=True) ###
        # Editor.dispose(self)

        self.on_trait_change(self.update_editor_overlays, 'overlays[]', dispatch='ui', deferred=True)
        self.on_trait_change(self.update_editor_overlay_item, 'overlays:+', dispatch='ui', deferred=True)
        self.sync_value(self.factory.overlays, 'overlays', 'from', is_list = True) #after on_trait_change!

    evt_dict = {
        QtCore.QEvent.MouseButtonPress: 'left_click',
        QtCore.QEvent.MouseMove: 'drag',
        QtCore.QEvent.MouseButtonRelease: 'release',
        }

    def handle_mouse_event(self, obj, event):
        #print "event", event.type()
        #MouseMove, MouseButtonRelease, MouseButtonDblClick, Wheel, #TouchBegin, TouchUpdate, TouchEnd

        msg = self.evt_dict.get(event.type())
        if msg is not None:

            pos = event.pos()
            xs,ys  = pos.x(), pos.y()
            x, y = self.control.screen_to_img(xs, ys)
            xp, yp = 0.5*(x+1)*self.value.w, 0.5*(y+1)*self.value.h #pixel coordinates of texture
            #print "coordinates", xp, yp

            for o in self.overlays:
                if o.visible:
                    try:
                        r = o.handle_mouse(xp, yp, msg)
                    except Exception as e:
                        print("ignoring exception in mouse event handler", e)
                        r = False

                    if r:
                        return True

        return False

    def update_editor(self):
        #print 'update editor'
        self.control.set_camimage(self.value)
        self.control.do_update('update_editor')

    #note: dispatched in ui thread
    def update_editor_overlays(self, obj, name, old, new):
        if self.control is not None:
            for k, o in enumerate(self.overlays):
                self._init_overlayGL(o, idx=k)

            self.control.do_update('update_editor_overlays') #TODO: dangerous (not in ui thread?)
        else:
            print("ERROR, control not yet initialized")

    #NOTE: dispatched in ui thread
    def update_editor_overlay_item(self, obj, name, old, new):
        if self.control is not None:
            self._modify_overlay(obj)
            #TODO: removed temporarily: leave update of control to image updates
            self.control.do_update('update_editor_overlay_item')
        else:
            print('warning in update_editor_overlay_item: control is None (not yet initialized?)', self, obj, name)

    def _init_overlayGL(self, overlay, idx):
        #print "init_overlay", idx, overlay.__class__.__name__
        overlay.idx = idx
        if isinstance(overlay, OverlayTraitCircle):
            glovl = GLOverlayCircle()
        elif isinstance(overlay, OverlayTraitRectangle):
            glovl = GLOverlayRectangle()
        elif isinstance(overlay, OverlayTraitGrid):
            glovl = GLOverlayGrid()
        elif isinstance(overlay, OverlayTraitSpots):
            glovl = GLOverlaySpots()
        elif isinstance(overlay, OverlayTraitArrow):
            glovl = GLOverlayArrow()
        elif isinstance(overlay, OverlayTraitWarpArrows):
            glovl = GLOverlayWarpArrows()
        else:
            raise Exception('unknown overlay class')
        self.control.gloverlays[idx] = glovl
        self._modify_overlay(overlay)

    def _modify_overlay(self, overlay):
        "update control GLoverlay according to overlay trait" #called when overlay trait has been changed
        try:
            nx, ny = float(self.value.w), float(self.value.h) #TODO: self.value could be None!
        except AttributeError:
            nx, ny = 640, 480
            print('Exception caught in glimgwidget/_modify_overlay')
        glovl = self.control.gloverlays[overlay.idx]
        glovl.update_overlay(overlay_trait=overlay, img_size=(nx,ny)) #TODO: dangerous (not in ui thread?), possibly not, changes only trait attributes

class GLImageWithOverlaysEditor(BasicEditorFactory):
    klass = _GLImageWithOverlaysEditor
    overlays = Str #trait name of overlays

###############
# Demos / Tests
###############

def qApp():
    app = QtCore.QCoreApplication.instance()
    if app is None:
        print('no qt app found, create one')
        app = QtGui.QApplication([])
    else:
        print('found qt app instance')  # likely, import traitsui initializes qApp

    return app

def start_event_loop():
    from IPython.lib.guisupport import start_event_loop_qt4, is_event_loop_running_qt4
    if not is_event_loop_running_qt4():
        print('qt4 event loop not running')
        print('starting event loop via IPython.lib.guisupport.start_event_loop_qt4')
        start_event_loop_qt4(QtCore.QCoreApplication.instance())
    else:
        print('running qt4 event loop detected')

class DemoWindow(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        window = QtGui.QWidget()
        self.glimgwidget = GLImgWidget(parent=window)
        layout = QtGui.QHBoxLayout()
        layout.addWidget(self.glimgwidget, 1, QtCore.Qt.AlignCenter)
        window.setLayout(layout)
        self.setWindowTitle('GLImgWidget demo')
        self.setCentralWidget(window)

def test_demo_window():
    window = DemoWindow()
    window.show()
    return window

def test_cl_texture():
    window = DemoWindow()
    window.show()
    g = window.glimgwidget
    g.update_texture_cl()
    return window, g

class DemoOverlayWindow(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        window = QtGui.QWidget()
        self.imgwidget = GLImgWithOverlaysWidget(None)
        self.imgwidget.gloverlays[0] = GLOverlayCircle()
        self.imgwidget.gloverlays[1] = GLOverlayRectangle()
        self.imgwidget.gloverlays[2] = GLOverlayWarpArrows()

        layout = QtGui.QHBoxLayout()
        layout.addWidget(self.imgwidget, 1, QtCore.Qt.AlignCenter)
        window.setLayout(layout)
        self.setWindowTitle('OpenGL image widget')
        self.setCentralWidget(window)

def test_demo_overlay_window():
    window = DemoOverlayWindow()
    window.show()
    return window

def test_SLM_window():
    app = qApp()

    desktop = app.desktop()
    print("desktop screen count", desktop.screenCount())
    print("desktop is virtual desktop", desktop.isVirtualDesktop())
    print("screen geometry default screen", desktop.screenGeometry(screen=-1))
    print("primary screen index", desktop.primaryScreen())
    for k in range(desktop.screenCount()):
        print("screen geometry screen #%d: %s" % (k, desktop.screenGeometry(screen=k)))

    window = SLMWindow()

    print("window created")
    window.show()
    return window

class TraitsTestImage(HasTraits):
    image = Instance(CamImage, CamImage())

    traits_view = View(
        Item('image',
             editor = GLImageEditor(),
             ),
        resizable=True,
    )

def test_traits_image():
    t = TraitsTestImage()
    e = t.edit_traits()
    return t, e

class TraitsTestImageOverlays(HasTraits):
    image = Instance(CamImage)
    overlays = List(Instance(OverlayTrait), [OverlayTraitCircle(),
                                             OverlayTraitRectangle(),
                                             OverlayTraitGrid(),
                                             OverlayTraitSpots(),
                                             OverlayTraitArrow(),
                                             OverlayTraitWarpArrows(),
                                             ])

    def _image_default(self):
        data = np.zeros((480, 640,), np.uint8) #rows, columns
        #data[::2, ::2] = 213
        #data[::4, ::4] = 50
        #data[:32:2, -32:-1:2] = 128
        data[200] = 255
        data[300] = 200
        data[:,256] = 255
        camimg = CamImage()
        camimg.data_roi = data
        return camimg

    traits_view = View(
        Item('image', editor = GLImageWithOverlaysEditor(overlays = 'overlays'),
             springy = True),
        Item('overlays',
             style = 'custom',
             editor = ListEditor(use_notebook = True),
             ),
        resizable=True,
             )

def test_traits_image_overlays():
    t = TraitsTestImageOverlays()
    e = t.edit_traits()
    return t, e

if __name__ == '__main__':

    #w = test_demo_window() #keeping reference important!
    #w = test_demo_overlay_window()  # keeping reference important!
    #w = test_SLM_window()

    #w, g = test_cl_texture()

    ## Test updating texture via cl kernel
    #w = test_demo_window()
    #g = w.glimgwidget
    #g.update_texture_cl()

    # Traits test
    #t, e = test_traits_image()
    t, e = test_traits_image_overlays()

    ## old stuff
    #glot = e._editors[0].control.gloverlays[5]
    #o = t.overlays[-1]

    start_event_loop()


from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import collections
import numpy as np
from tensorflow.contrib.optimizer_v2 import optimizer_v2
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import ops
from tensorflow.python.framework import tensor_shape
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import control_flow_ops
from tensorflow.python.ops import linalg_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import state_ops

class GGTOptimizer(optimizer_v2.OptimizerV2):
  """Optimizer that implements the GGT algorithm.

  GGT has an advantage over sgd and adam on large models with poor conditioning,
  for example language models and CNNs,
  see [[ABCHSZZ 2018]](https://arxiv.org/pdf/1806.02958.pdf).
  """

  def __init__(self,
               learning_rate=0.001,
               beta1=0.9,
               use_locking=False,
               name="GGT",
               window=10,
               eps=1e-4,
               svd_eps=1e-6,
               sigma_eps=1e-2):
    """Construct a new GGT optimizer.

    Initialization:

    ```
    t <- 0 (Initialize timestep)
    grad_buffer <- 0 (Initialize buffer for keeping past gradients)
    flat_grad <- 0 (Initialize flattened gradient that contains gradients of all
                    variables)
    m_0 <- 0 (Initialize 1st moment vector)
    ```

    Suppose all variables and their gradients are concatenated into vectors
    `flat_vars` and `flat_grad`. The update rule for `flat_vars`
    uses an optimization described at the beginning of section 2 of the paper:

    ```
    t <- t + 1

    m_t <- beta1 * m_{t-1} + (1 - beta1) * flat_grad
    grad_buffer[(t-1) % window, :] <- m_t

    M <- grad_buffer^T / sqrt(min(t, window))
    U, sigma, _ <- SVD(M^TM + I * svd_eps)

    sigma_sqrt_inv <- (sqrt(sigma) + sigma_eps)^(-3)
    sigma_sqrt_min <- min(sqrt(sigma))

    if sigma_sqrt_min > eps:
      new_step <- M U diag(sigma_sqrt_inv) U^T M^T m_t +
                  (m_t - M U diag(1/sigma) U^T M^T m_t) / sigma_sqrt_min
    else:
      new_step <- M U diag(sigma_sqrt_inv) U^T M^T m_t

    flat_vars <- flat_vars - learning_rate * new_step
    ```

    GGT provides the power of full-matrix adaptive regularization at a cost not
    much larger than SGD. As a result it is suited for large models where the
    gradient covariance matrix has a poor condition number that slows down first
    order methods.
    GGT uses the preconditioner from full-matrix AdaGrad, with gradient history
    attenuated exponentially as in Adam, and truncated to a window parameter.
    It has provable guarantees even for non-convex optimization that is never
    significantly worse than SGD and in some cases better.

    Args:
      learning_rate: A float hyperparameter. The learning rate.
      beta1: A float hyperparameter. The exponential decay rate for the 1st
        moment estimates.
      use_locking: If True use locks for update operations.
      name: Optional name for the operations created when applying gradients.
        Defaults to "GGT".
      window: An integer hyperparameter. The number of first moments to keep in
        computing the adaptive preconditioner.
      eps: A float hyperparameter. Used to truncate small eigenvalues of the
        gradient covariance matrix.
      svd_eps: A float hyperparameter. Used to stabilize SVD.
      sigma_eps: A float hyperparameter. Used to regularize matrix inversion.
    """
    super(GGTOptimizer, self).__init__(use_locking, name)
    self._set_hyper("lr", learning_rate)
    self._set_hyper("beta1", beta1)
    self._set_hyper("window", window)
    self._set_hyper("eps", eps)
    self._set_hyper("svd_eps", svd_eps)
    self._set_hyper("sigma_eps", sigma_eps)

    self.index_dict = {}
    self.shape_dict = {}

  def _create_vars(self, var_list, state):
    # Construct ordered dictionary for variable dimensions, sorted by name.
    shape_dict = {}
    for v in var_list:
      shape_dict[v.name] = tensor_shape.dimension_value(np.prod(v.get_shape()))
    self.shape_dict = collections.OrderedDict(
        sorted(shape_dict.items(), key=lambda t: t[0]))

    # Assign each variable its location in flat_grad. The locations are based on
    # the order of sorted names.
    idx = 0
    for v_name, v_dim in self.shape_dict.items():
      self.index_dict[v_name] = idx
      idx += v_dim

    state.create_non_slot(
        initial_value=math_ops.cast(0., dtype=var_list[0].dtype.base_dtype),
        name="global_step")

    # Buffer for keeping past gradients.
    window = state.get_hyper("window")
    grad_buffer_init = array_ops.zeros(
        [window, idx], dtype=var_list[0].dtype.base_dtype)
    state.create_non_slot(initial_value=grad_buffer_init, name="grad_buffer")

    state.create_non_slot(
        initial_value=array_ops.zeros(
            (idx,), dtype=var_list[0].dtype.base_dtype),
        name="moment1")

    # Flattened gradient that contains gradients for all variables in the model.
    state.create_non_slot(
        initial_value=array_ops.zeros(
            (idx,), dtype=var_list[0].dtype.base_dtype),
        name="flat_grad")

  def _get_global_step(self, state=None):
    if state is None:
      state = self._get_per_graph_state()
    return state.get_non_slot("global_step")

  def _get_moment1(self, state=None):
    if state is None:
      state = self._get_per_graph_state()
    return state.get_non_slot("moment1")

  def _get_grad_buffer(self, state=None):
    if state is None:
      state = self._get_per_graph_state()
    return state.get_non_slot("grad_buffer")

  def _get_flat_grad(self, state=None):
    if state is None:
      state = self._get_per_graph_state()
    return state.get_non_slot("flat_grad")

  def _apply_sparse(self, grad, var):
    raise NotImplementedError("Sparse gradient updates are not supported.")

  def _prepare(self, state):
    self._variables = []

  def _apply_dense(self, grad, var, state):
    self._variables.append(var)
    dim = self.shape_dict[var.name]
    start_index = self.index_dict[var.name]
    end_index = start_index + dim

    # Update flat_gradient at the index associated with the variable.
    flat_grad = self._get_flat_grad(state)
    new_flat_grad = array_ops.reshape(grad, [-1])
    flat_grad_updated = state_ops.scatter_update(
        flat_grad, math_ops.range(start_index, end_index), new_flat_grad)

    return flat_grad_updated

  def _resource_apply_dense(self, grad, var, state):
    self._variables.append(var)
    dim = self.shape_dict[var.name]
    start_index = self.index_dict[var.name]
    end_index = start_index + dim

    # Update flat_gradient at the index associated with the variable.
    flat_grad = self._get_flat_grad(state)
    new_flat_grad = array_ops.reshape(grad, [-1])
    flat_grad_updated = state_ops.scatter_update(
        flat_grad, math_ops.range(start_index, end_index), new_flat_grad)

    return flat_grad_updated

  def _finish(self, state):
    var_dtype = self._variables[0].dtype.base_dtype
    # Update global step.
    global_step = self._get_global_step(state)
    update_global_step = state_ops.assign_add(global_step, 1.)

    # Update the first moment estimate.
    beta1 = state.get_hyper("beta1", dtype=var_dtype)
    moment1 = self._get_moment1(state)
    flat_grad = self._get_flat_grad(state)
    # moment1_t := beta1 * moment1_{t-1} + (1 - beta1) * flat_grad_t
    update_moment1 = moment1.assign(beta1 * moment1 + (1. - beta1) * flat_grad)

    # Update the gradient buffer.
    window = state.get_hyper("window")
    grad_buffer = self._get_grad_buffer(state)
    next_grad_index = math_ops.floormod(
        math_ops.cast(update_global_step - 1., dtypes.int32), window)
    # grad_buffer[(t-1) % window] := moment1_t
    update_grad_buffer = state_ops.scatter_update(grad_buffer, next_grad_index,
                                                  update_moment1)

    # Compute the update step.
    eps = state.get_hyper("eps", dtype=var_dtype)
    svd_eps = state.get_hyper("svd_eps", dtype=var_dtype)
    sigma_eps = state.get_hyper("sigma_eps", dtype=var_dtype)
    lr = state.get_hyper("lr", dtype=var_dtype)
    denom = math_ops.sqrt(
        math_ops.minimum(
            ops.convert_to_tensor(update_global_step),
            ops.convert_to_tensor(math_ops.cast(window, dtype=var_dtype))))
    moment1_2d = array_ops.expand_dims(update_moment1, -1)

    # m = grad_buffer^T / sqrt(min(t, window))
    # m has shape [model dimension, window], where model dimension is the sum
    # of the dimensions of the flattened variables.
    m = array_ops.transpose(math_ops.divide(update_grad_buffer, denom))

    # sigma, u, _ = SVD(m^Tm + I * svd_eps)
    mm = math_ops.matmul(m, m, transpose_a=True)
    damping = math_ops.cast(linalg_ops.eye(window), dtype=var_dtype) * svd_eps
    sigma, u, _ = linalg_ops.svd(mm + damping)
    sigma_sqrt = math_ops.sqrt(sigma)
    sigma_sqrt_min = math_ops.reduce_min(sigma_sqrt)

    # sigma_sqrt_inv = 1 / (\sqrt{sigma} + sigma_eps) ^ 3
    # We add sigma_eps to alleviate numerical instability.
    # Note that (m^Tm)^(-3/2) = u diag(sigma_sqrt_inv) u^T.
    sigma_sqrt_inv = math_ops.divide(
        math_ops.cast(1.0, dtype=var_dtype),
        math_ops.pow(sigma_sqrt + sigma_eps, 3))

    # In full matrix AdaGrad, the update step computes (mm^T)^(-1/2)g, where the
    # inversion of a model dimension by model dimension matrix is needed. To
    # speed up this computation we calculate the following instead:
    # m(m^Tm)^(-3/2)m^T moment1 = m u diag(sigma_sqrt_inv) u^T m^T moment1.
    new_step = array_ops.expand_dims(
        array_ops.zeros(flat_grad.get_shape(), dtype=var_dtype), -1)
    head = math_ops.matmul(
        m,
        math_ops.matmul(
            u,
            math_ops.matmul(
                array_ops.diag(sigma_sqrt_inv),
                math_ops.matmul(
                    u,
                    math_ops.matmul(m, moment1_2d, transpose_a=True),
                    transpose_a=True))))

    # When inverting (mm^t)^(1/2), we also add epsilon * I regularization for
    # degenerate cases. We expand ((mm^t)^(1/2) + epsilon * I)^(-1) using
    # Woodbury's identity.
    # For full derivation please see paper at
    # https://arxiv.org/pdf/1806.02958.pdf
    tail = moment1_2d - math_ops.matmul(
        m,
        math_ops.matmul(
            u,
            math_ops.matmul(
                array_ops.diag(
                    math_ops.divide(math_ops.cast(1.0, dtype=var_dtype),
                                    sigma)),
                math_ops.matmul(
                    u,
                    math_ops.matmul(m, moment1_2d, transpose_a=True),
                    transpose_a=True))))
    scaled_tail = math_ops.divide(tail, sigma_sqrt_min)

    update_new_step = control_flow_ops.cond(
        sigma_sqrt_min > eps, lambda: math_ops.add(head, scaled_tail),
        lambda: math_ops.add(new_step, head))

    # Update each variable.
    update_step = []
    for var in self._variables:
      dim = self.shape_dict[var.name]
      start_index = self.index_dict[var.name]
      end_index = start_index + dim
      var_update_correct_shape = array_ops.reshape(
          update_new_step[start_index:end_index], var.get_shape())
      var_updated = state_ops.assign_sub(var, lr * var_update_correct_shape)
      update_step.append(var_updated)

    return control_flow_ops.group(update_step)


from flask import Blueprint, request
from flask_login import login_required, current_user
from app.models import User, Transaction, db
from app.forms import PayRequestForm
from .auth_routes import validation_errors_to_error_messages

transaction_routes = Blueprint('transactions', __name__)

@transaction_routes.route('')
@login_required
def transactions():
    """
    Query for all transactions of the current user and its friends, and returns them in a list of transaction dictionaries
    """
    user_transactions = current_user.to_dict_luxury()['transactions']
    user_transactions_ids = [tran['id'] for tran in user_transactions]

    fds_from_ids = filter(lambda friend: friend.is_confirmed, current_user.friends_from)
    fds_to_ids = filter(lambda friend: friend.is_confirmed, current_user.friends_to)
    fds_from = [User.query.get(fds.to_user_id).to_dict_luxury() for fds in fds_from_ids]
    fds_to = [User.query.get(fds.from_user_id).to_dict_luxury() for fds in fds_to_ids]
    friends = [*fds_from, *fds_to]

    friends_transactions = []
    friend_trans_dups = []

    for f in friends:
        friend_trans = f['transactions']
        for t in friend_trans:
            if t['id'] not in user_transactions_ids and t['id'] not in friend_trans_dups:
                friends_transactions.append(t)
                friend_trans_dups.append(t['id'])

    return {'UserTransactions': user_transactions, "FriendsTransactions": friends_transactions}

@transaction_routes.route('/<int:id>')
@login_required
def transaction(id):
    """
    Query for a transaction by id and returns that transaction in a dictionary
    """
    transaction = Transaction.query.get(id)

    # get all confirmed friend ids.
    fds_from_ids = [
        friendship.to_user_id for friendship in current_user.friends_from if friendship.is_confirmed]
    fds_to_ids = [
        friendship.from_user_id for friendship in current_user.friends_to if friendship.is_confirmed]

    friends = [*fds_from_ids, *fds_to_ids]

    # print('--------------------------------------------------')
    # print(current_user.id, 'currend_user.id')
    # print(transaction.from_user_id, 'transaction.from_user_id')
    # print(transaction.to_user_id, 'transaction.to_user_id')

    if current_user.id != transaction.from_user_id and current_user.id != transaction.to_user_id \
        and transaction.from_user_id not in friends and transaction.to_user_id not in friends:
            # print('-------------------------returning unauthorized-------------------------')
            return {'errors': 'Unauthorized'}, 401

    if not transaction:
        return {'errors': 'Transaction is not found.'}, 404

    return transaction.to_dict_luxury()

@transaction_routes.route('', methods=['POST'])
@login_required
def create_transaction():
    """
    Create a transaction and returns the newly created transaction in a dictionary
    """
    form = PayRequestForm()

    form['csrf_token'].data = request.cookies['csrf_token']
    if form.validate_on_submit():
        if form.data['to_user_id'] == current_user.id:
            return {'error': 'You cannot transfer fund to yourself.'}, 401

        # user_from = User.query.with_for_update(of=User).filter_by(id=current_user.id).first()
        # user_to = User.query.with_for_update(of=User).filter_by(id=form.data['to_user_id']).first()
        user_from = db.session.query(User).populate_existing().with_for_update(of=User.balance).filter_by(id=current_user.id).first()
        user_to = db.session.query(User).populate_existing().with_for_update(of=User.balance).filter_by(id=form.data['to_user_id']).first()

        if user_from.balance < form.data['amount']:
            return {'error': 'insufficient fund.'}

        user_from.balance -= form.data['amount']
        user_to.balance += form.data['amount']

        transaction = Transaction(
            amount=form.data['amount'],
            note=form.data['note'],
            user_from=user_from,
            user_to=user_to
        )

        db.session.add(transaction)
        db.session.commit()

        return transaction.to_dict_fancy()

    return {'errors': validation_errors_to_error_messages(form.errors)}, 401

# !!! for development only, to remove !!!
@transaction_routes.route('/<int:transaction_id>', methods=['DELETE'])
@login_required
def delete_transaction(transaction_id):
    t = Transaction.query.get(transaction_id)
    db.session.delete(t)
    db.session.commit()

    return 'deleted'