import pyqtgraph as pg
import numpy as np

import sys
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QApplication, QHBoxLayout, QLabel, QSizePolicy, QSlider, QSpacerItem, \
    QVBoxLayout, QWidget



class Slider(QWidget):
    def __init__(self, minimum, maximum, parent=None):
        super(Slider, self).__init__(parent=parent)
        self.verticalLayout = QVBoxLayout(self)
        self.label = QLabel(self)
        self.verticalLayout.addWidget(self.label)
        self.horizontalLayout = QHBoxLayout()
        spacerItem = QSpacerItem(0, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
        self.horizontalLayout.addItem(spacerItem)
        self.slider = QSlider(self)
        self.slider.setOrientation(Qt.Orientation.Vertical)
        self.horizontalLayout.addWidget(self.slider)
        spacerItem1 = QSpacerItem(0, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
        self.horizontalLayout.addItem(spacerItem1)
        self.verticalLayout.addLayout(self.horizontalLayout)
        self.resize(self.sizeHint())

        self.minimum = minimum
        self.maximum = maximum
        self.slider.valueChanged.connect(self._setLabelValue)
        self.x = None
        self._setLabelValue(self.slider.value())

    def setValue(self, value : float):
        self.slider.setValue(100 * (value - self.minimum) / (self.maximum - self.minimum))
        # self.slider.setValue(value)
        self._setLabelValue(self.slider.value())


    def _setLabelValue(self, value):
        self.x = self.minimum + (float(value) / (self.slider.maximum() - self.slider.minimum())) * (
        self.maximum - self.minimum)
        self.label.setText("{0:.4g}".format(self.x))


class Widget(QWidget):
    def __init__(self, parent=None):
        super(Widget, self).__init__(parent=parent)
        self.horizontalLayout = QHBoxLayout(self)
        self.w1 = Slider(0, 1)
        self.w1.setValue(.4141414141414141414141414141)
        # self.w1.setValue(50)
        self.horizontalLayout.addWidget(self.w1)

        self.w2 = Slider(-1, 1)
        self.horizontalLayout.addWidget(self.w2)

        self.w3 = Slider(-10, 10)
        self.horizontalLayout.addWidget(self.w3)

        self.w4 = Slider(-10, 10)
        self.horizontalLayout.addWidget(self.w4)

        self.win = pg.GraphicsWindow(title="Basic plotting examples")
        self.horizontalLayout.addWidget(self.win)
        self.p6 = self.win.addPlot(title="My Plot")
        self.p6.setAspectLocked()
        self.horn_upper = self.p6.plot(pen='r')
        self.horn_lower = self.p6.plot(pen='r')
        self.bounce_path = self.p6.plot(pen='w')
        self.update_plot()

        self.w1.slider.valueChanged.connect(self.update_plot)
        self.w2.slider.valueChanged.connect(self.update_plot)
        self.w3.slider.valueChanged.connect(self.update_plot)
        self.w4.slider.valueChanged.connect(self.update_plot)

    def update_plot(self):

        k = self.w1.x
        def ii(x):
            return np.floor(2**x + .5)
        def qq(x):
            return ii(x)*x
        def horn(x):
            return -k *qq(x-np.log2(ii(x)))/ii(x) + 1
            return np.exp(-k*x)
        def horn_bot(x):
            return -horn(x)

        #create numpy arrays
        #make the numbers large to show that the range shows data from 10000 to all the way 0
        xs = np.linspace(-.5,10, 10000)
        horn_upper = horn(xs)
        horn_lower = -horn_upper

        vel = np.array([1,0])
        bounce_points = [np.array([xs[0], 1])]
        def ray(t: float):
            return bounce_points[-1] + t*vel

        epsilon = 1e-10

        # simulate bounces
        for bounce_i in range(100):
            bot_t = 0
            top_t = .1
            t = bot_t
            horn_func = horn if vel[1] >= 0 else horn_bot

            p = ray(t)
            initial_side = p[1] < horn_func(p[0])
            t = top_t
            # expand search forwards
            for i in range(64):
                p = ray(top_t)
                side = p[1] < horn_func(p[0])
                if p[0] < -.5:
                    break
                if side == initial_side:
                    (bot_t, top_t) = (top_t, top_t+1.1*(top_t - bot_t))
                else:
                    break
            if side == initial_side:
                # print(f'never found a crossover point at bounce_i={bounce_i}')
                # bounce_points.append(p)
                break
            # bisect
            for i in range(64):
                t = (bot_t + top_t) / 2
                p = ray(t)
                side = p[1] < horn_func(p[0])
                if side == initial_side:
                    bot_t = t
                else:
                    top_t = t
                if bot_t == top_t:
                    break
            dx = 2*epsilon
            dy = horn_func(p[0] + epsilon) - horn_func(p[0] - epsilon)
            n = np.array([-dy, dx])
            n /= np.sqrt(n.dot(n))  # normal
            # reflect
            vel = vel - 2*(vel.dot(n))*n
            bounce_points.append(p)
            # (bot_t, top_t)

        bounce_xs = [p[0] for p in bounce_points]
        bounce_ys = [p[1] for p in bounce_points]

        self.horn_upper.setData(x=xs, y=horn_upper)
        self.horn_lower.setData(x=xs, y=horn_lower)
        self.bounce_path.setData(x=bounce_xs, y=bounce_ys)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = Widget()
    w.show()
    sys.exit(app.exec())

# if __name__ == '__main__':
#     pg.exec()
