from PyQt5 import QtWidgets, uic
from PyQt5.QtWidgets import QTableWidgetItem, QMessageBox
from PyQt5.QtGui import QPen, QColor, QImage, QPixmap, QPainter, QTransform, QPolygonF
from PyQt5.QtCore import Qt, QTime, QCoreApplication, QEventLoop, QPointF
import copy

red = Qt.red
blue = Qt.blue
black = Qt.black
now = None


class Window(QtWidgets.QMainWindow):
    def __init__(self):
        QtWidgets.QWidget.__init__(self)
        uic.loadUi("window.ui", self)
        self.scene = Scene(0, 0, 561, 581)
        self.scene.win = self
        self.view.setScene(self.scene)
        self.poly.clicked.connect(lambda : set_pol(self))
        self.erase.clicked.connect(lambda: clean_all(self))
        self.paint.clicked.connect(lambda: clipping(self))
        self.rect.clicked.connect(lambda: set_rect(self))
        self.ect.clicked.connect(lambda: add_bars(self))
        self.lock.clicked.connect(lambda: lock(self))
        self.clip = []
        self.pol = []
        self.point_now_clip = None
        self.point_now_pol = None
        self.point_lock_pol = None
        self.point_lock_clip = None
        self.input_pol = False
        self.input_clip = False
        self.pen = QPen(black)


class Scene(QtWidgets.QGraphicsScene):
    def mousePressEvent(self, event):
        add_point(event.scenePos())

    def mouseMoveEvent(self, event):
        global w
        x = event.scenePos().x()
        y = event.scenePos().y()
        w.x.setText("{0}".format(x))
        w.y.setText("{0}".format(y))


def sign(x):
    if not x:
        return 0
    else:
        return x / abs(x)


def set_pol(win):
    if win.input_pol:
        win.input_pol = False
        win.rect.setDisabled(False)
        win.erase.setDisabled(False)
        win.paint.setDisabled(False)
        win.ect.setDisabled(False)
    else:
        win.input_pol = True
        win.rect.setDisabled(True)
        win.erase.setDisabled(True)
        win.paint.setDisabled(True)
        win.ect.setDisabled(True)


def set_rect(win):
    if win.input_clip:
        win.input_clip = False
        win.poly.setDisabled(False)
        win.erase.setDisabled(False)
        win.paint.setDisabled(False)
        win.ect.setDisabled(False)
    else:
        win.input_clip = True
        win.poly.setDisabled(True)
        win.erase.setDisabled(True)
        win.paint.setDisabled(True)
        win.ect.setDisabled(True)


def add_point(point):
    global w
    if w.input_clip:
        w.pen.setColor(black)
        if w.point_now_clip is None:
            w.point_now_clip = point
            w.point_lock_clip = point
            add_row(w.table_rect)
            i = w.table_rect.rowCount() - 1
            item_x = QTableWidgetItem("{0}".format(point.x()))
            item_y = QTableWidgetItem("{0}".format(point.y()))
            w.table_rect.setItem(i, 0, item_x)
            w.table_rect.setItem(i, 1, item_y)
        else:
            w.clip.append(point)
            w.point_now_clip = point
            add_row(w.table_rect)
            i = w.table_rect.rowCount() - 1
            item_x = QTableWidgetItem("{0}".format(point.x()))
            item_y = QTableWidgetItem("{0}".format(point.y()))
            w.table_rect.setItem(i, 0, item_x)
            w.table_rect.setItem(i, 1, item_y)
            item_x = w.table_rect.item(i-1, 0)
            item_y = w.table_rect.item(i-1, 1)
            w.scene.addLine(point.x(), point.y(), float(item_x.text()), float(item_y.text()), w.pen)

    if w.input_pol:
        w.pen.setColor(blue)
        if w.point_now_pol is None:
            w.point_now_pol = point
            w.point_lock_pol = point
            add_row(w.table_pol)
            i = w.table_pol.rowCount() - 1
            item_x = QTableWidgetItem("{0}".format(point.x()))
            item_y = QTableWidgetItem("{0}".format(point.y()))
            w.table_pol.setItem(i, 0, item_x)
            w.table_pol.setItem(i, 1, item_y)
        else:
            w.pol.append(point)
            w.point_now_pol = point
            add_row(w.table_pol)
            i = w.table_pol.rowCount() - 1
            item_x = QTableWidgetItem("{0}".format(point.x()))
            item_y = QTableWidgetItem("{0}".format(point.y()))
            w.table_pol.setItem(i, 0, item_x)
            w.table_pol.setItem(i, 1, item_y)
            item_x = w.table_pol.item(i-1, 0)
            item_y = w.table_pol.item(i-1, 1)
            w.scene.addLine(point.x(), point.y(), float(item_x.text()), float(item_y.text()), w.pen)


def lock(win):
    if w.input_pol:
        win.pol.append(win.point_lock_pol)
        win.scene.addLine(win.point_now_pol.x(), win.point_now_pol.y(), win.point_lock_pol.x(), win.point_lock_pol.y(), w.pen)
        win.point_now_pol = None

    if w.input_clip:
        win.clip.append(win.point_lock_clip)
        win.scene.addLine(win.point_now_clip.x(), win.point_now_clip.y(), win.point_lock_clip.x(), win.point_lock_clip.y(), w.pen)
        win.point_now_clip = None


def add_row(win_table):
    win_table.insertRow(win_table.rowCount())


def clean_all(win):
    win.scene.clear()
    win.table_rect.clear()
    win.table_pol.clear()
    win.clip = []
    win.pol = []
    win.point_now_clip = None
    win.point_now_pol = None
    win.point_lock_clip = None
    win.point_lock_pol = None
    r = win.table_rect.rowCount()
    for i in range(r, -1, -1):
        win.table_rect.removeRow(i)

    r = win.table_pol.rowCount()
    for i in range(r, -1, -1):
        win.table_pol.removeRow(i)


def isConvex(edges):
    flag = 1

    # начальные вершины
    vo = edges[0]  # iая вершина
    vi = edges[1]  # i+1 вершина
    vn = edges[2]  # i+2 вершина и все остальные

    # векторное произведение двух векторов
    x1 = vi.x() - vo.x()
    y1 = vi.y() - vo.y()

    x2 = vn.x() - vi.x()
    y2 = vn.y() - vi.y()

    # определяем знак ординаты
    r = x1 * y2 - x2 * y1
    prev = sign(r)

    for i in range(2, len(edges) - 1):
        if not flag:
            break
        vo = edges[i - 1]
        vi = edges[i]
        vn = edges[i + 1]

        # векторное произведение двух векторов
        x1 = vi.x() - vo.x()
        y1 = vi.y() - vo.y()

        x2 = vn.x() - vi.x()
        y2 = vn.y() - vi.y()

        r = x1 * y2 - x2 * y1
        curr = sign(r)

        # если знак предыдущей координаты не совпадает, то возможно многоугольник невыпуклый
        if curr != prev:
            flag = 0
        prev = curr

    # не забываем проверить последнюю с первой вершины
    vo = edges[len(edges) - 1]
    vi = edges[0]
    vn = edges[1]

    # векторное произведение двух векторов
    x1 = vi.x() - vo.x()
    y1 = vi.y() - vo.y()

    x2 = vn.x() - vi.x()
    y2 = vn.y() - vi.y()

    r = x1 * y2 - x2 * y1
    curr = sign(r)
    if curr != prev:
        flag = 0

    return flag * curr


def is_intersection(ed1, ed2, norm):
    vis1 = is_visiable(ed1[0], ed2[0], ed2[1], norm)
    vis2 = is_visiable(ed1[1], ed2[0], ed2[1], norm)
    if (vis1 and not vis2) or (not vis1 and vis2):
        # ищем пересечение

        p1 = ed1[0]
        p2 = ed1[1]

        q1 = ed2[0]
        q2 = ed2[1]

        delta = (p2.x() - p1.x()) * (q1.y() - q2.y()) - (q1.x() - q2.x()) * (p2.y() - p1.y())
        delta_t = (q1.x() - p1.x()) * (q1.y() - q2.y()) - (q1.x() - q2.x()) * (q1.y() - p1.y())

        if abs(delta) <= 1e-6:
            return p2

        t = delta_t / delta

        I = QPointF()
        I.setX(ed1[0].x() + (ed1[1].x() - ed1[0].x()) * t)
        I.setY(ed1[0].y() + (ed1[1].y() - ed1[0].y()) * t)
        return I
    else:
        return False


def is_visiable(point, peak1, peak2, norm):
    v = vector([point, peak1], [peak2, peak1])
    if norm * v < 0:
        return True
    else:
        return False


def vector(v1, v2):
    x1 = v1[0].x() - v1[1].x()
    y1 = v1[0].y() - v1[1].y()

    x2 = v2[0].x() - v2[1].x()
    y2 = v2[0].y() - v2[1].y()

    return x1 * y2 - x2 * y1



def clipping(win):
    if len(win.clip) <= 1:
        QMessageBox.warning(win, "Ошибка!", "Отсекатель не задан!")

    if len(win.pol) <= 1:
        QMessageBox.warning(win, "Ошибка!", "Многоугольник не задан!")

    if len(win.pol) > 1 and len(win.clip) > 1:
        norm = isConvex(win.clip)
        if not norm:
            QMessageBox.warning(win, "Ошибка!", "Отсекатель не выпуклый!Операция не может быть проведена!")
        else:
            p = sutherland_hodgman(win.clip, win.pol, norm)
            if p:
                win.pen.setWidth(2)
                win.pen.setColor(red)
                win.scene.addPolygon(p, win.pen)
                win.pen.setWidth(1)


def sutherland_hodgman(clip, pol, norm):
    # дублируем начальную вершину отсекателя в конец
    clip.append(clip[0])

    s = None
    f = None
    # цикл по вершинам отсекателя
    for i in range(len(clip) - 1):
        new = []  # новый массив вершин
        for j in range(len(pol)):    # цикл по вершинам многоугольника
            if j == 0:
                f = pol[j]
            else:
                t = is_intersection([s, pol[j]], [clip[i], clip[i + 1]], norm)
                if t:
                    new.append(t)

            s = pol[j]
            if is_visiable(s,  clip[i], clip[i + 1], norm):
                    new.append(s)

        if len(new) != 0:
            t = is_intersection([s, f], [clip[i], clip[i + 1]], norm)
            if t:
                new.append(t)

        pol = copy.deepcopy(new)

    if len(pol) == 0:
        return False
    else:
        return QPolygonF(pol)


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = Window()
    w.show()
    sys.exit(app.exec_())