#use sin cos to get better gradients (than nulayer)
#migth habe better gradients? (seems that way but not sure yet)

#should rename it, but who cares
#now also able to export the given matrix
from tensorflow.keras.layers import Layer
from tensorflow.keras import backend as K
from tensorflow import keras
import tensorflow as tf

import numpy as np



class ulayer(Layer):
    def __init__(self,siz,dex1,dex2, **kwargs):
        self.siz = siz
        self.dex1 = dex1
        self.dex2 = dex2
        super(ulayer, self).__init__(**kwargs)

    def build(self, input_shape):
        # Create a trainable weight variable for this layer.
        self.kernel = self.add_weight(name='kernel',
                                      shape=(1,),
                                      initializer=keras.initializers.RandomUniform(-0.5, 0.5),
                                      trainable=True)
        super(ulayer, self).build(input_shape)  # Be sure to call this at the end

    def numpify(self):
        mat=np.eye(self.siz)
        val=self.weights[0].numpy()[0]
        sin,cos=np.sin(val),np.cos(val)
        mat[self.dex1,self.dex2]=sin
        mat[self.dex2,self.dex1]=-sin
        mat[self.dex1,self.dex1]=cos
        mat[self.dex2,self.dex2]=cos
        return mat



    def call(self, x):
        kernel=self.kernel
        sin=K.sin(kernel)
        cos=K.cos(kernel)
        tan=sin/cos#that should diverge?
        rows=[tf.expand_dims(x[:,i],1) for i in range(self.siz)]
        #instead of ((1,a),(-a,1)), I want this to be
        #((1,a),(-a,1))/sqrt(1+a**2)
        #and with trigonometry, I can get the same result by
        #a=sin(kernel)?
        #multiply to make 1->cos(x) (aka *cos(x))
        #so a actually tan(kernel)
        z1=rows[self.dex2]*tan
        z2=rows[self.dex1]*tan
        rows[self.dex1]+=z1
        rows[self.dex2]-=z2
        rows[self.dex1]*=cos
        rows[self.dex2]*=cos
        rows=K.concatenate(rows,axis=1)
        return rows


        mat=tf.eye(self.siz)
        tf.assign(mat[self.dex1,self.dex2],self.kernel)
        #mat[self.dex2,self.dex1]=-self.kernel
        return K.dot(x, mat)

    def compute_output_shape(self, input_shape):
        return input_shape


