package client.components.imagePanel;

import client.components.imagePanel.listeners.ImageControlsListener;
import client.components.listeners.DrawingListener;
import client.persistence.*;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

public class ScalableImage extends JPanel {

    private BufferedImage image;

    private int w_originX;
    private int w_originY;
    private double scale;

    private boolean dragging;
    private int w_dragStartX;
    private int w_dragStartY;
    private int w_dragStartOriginX;
    private int w_dragStartOriginY;

    private ImageTable imageTable;

    private double MAX_SCROLL = 1.0f;
    private double MIN_SCROLL = 0.2f;

    private ImageState imageState;


    public ScalableImage(ImageState imageState) {
        this.imageState = imageState;

        this.setBackground(Color.DARK_GRAY);

        initDrag();

        this.addMouseListener(mouseAdapter);
        this.addMouseMotionListener(mouseAdapter);
        this.addMouseWheelListener(mouseAdapter);

        setupView();

        this.imageState.addListener(imageStateListener);
        this.imageState.addNewProjectListener(newProjectListener);

    }

    private void setupView() {
        image = imageState.getImage();

        imageTable = new ImageTable(imageState);

        this.setOrigin(imageState.getSettings().getImageOriginX(),
                imageState.getSettings().getImageOriginY());

        this.setScale(imageState.getSettings().getImageScaleLevel());

        this.repaint();
    }

    public void refreshImage() {
        imageTable.setDeactivated(true);
        setupView();
    }

    boolean redrawHack = false;

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2 = (Graphics2D)g;

        g2.translate(this.getWidth() / 2, this.getHeight() / 2);
        g2.scale(scale, scale);
        g2.translate(-w_originX, -w_originY);
        g2.drawImage(image, 0, 0, null);

        imageTable.paint(g2);

        if(!redrawHack) {
            redrawHack = true;
            this.repaint();
        }
    }

    public void invertImage(BufferedImage b) {
        for (int x = 0; x < b.getWidth(); x++) {
            for (int y = 0; y < b.getHeight(); y++) {
                int rgba = b.getRGB(x, y);
                Color col = new Color(rgba, true);
                col = new Color(255 - col.getRed(),
                        255 - col.getGreen(),
                        255 - col.getBlue());
                b.setRGB(x, y, col.getRGB());
            }
        }

        boolean current = imageState.getSettings().isImageInverted();
        imageState.getSettings().setImageInverted(!current);

        redrawHack = false;
        this.repaint();
    }

    public void setValue(Cell cell, String value) {
        imageTable.setCurrentCell(cell.getField(), cell.getRecord());
        this.repaint();
    }

    public void setCurrentCell(Cell cell) {
        imageTable.setCurrentCell(cell.getField(), cell.getRecord());
        this.repaint();
    }

    private void initDrag() {
        dragging = false;
        w_dragStartX = 0;
        w_dragStartY = 0;
        w_dragStartOriginX = 0;
        w_dragStartOriginY = 0;
    }

    public void setScale(double newScale) {
        scale = newScale;
        this.repaint();
    }

    public void setOrigin(int w_newOriginX, int w_newOriginY) {
        w_originX = w_newOriginX;
        w_originY = w_newOriginY;
        this.repaint();
    }

    private MouseAdapter mouseAdapter = new MouseAdapter() {

        @Override
        public void mousePressed(MouseEvent e) {
            if(!imageState.isHasImage()) return;

            int d_X = e.getX();
            int d_Y = e.getY();

            AffineTransform transform = new AffineTransform();
            transform.translate(getWidth()/2, getHeight()/2);
            transform.scale(scale, scale);
            transform.translate(-w_originX, -w_originY);

            Point2D d_Pt = new Point2D.Double(d_X, d_Y);
            Point2D w_Pt = new Point2D.Double();
            try {
                transform.inverseTransform(d_Pt, w_Pt);
            } catch (NoninvertibleTransformException ex) {
                return;
            }
            int w_X = (int)w_Pt.getX();
            int w_Y = (int)w_Pt.getY();

            imageTable.contains(w_X, w_Y);

            dragging = true;
            w_dragStartX = w_X;
            w_dragStartY = w_Y;
            w_dragStartOriginX = w_originX;
            w_dragStartOriginY = w_originY;

            repaint();
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            if (dragging) {
                int d_X = e.getX();
                int d_Y = e.getY();

                AffineTransform transform = new AffineTransform();
                transform.translate(getWidth()/2, getHeight()/2);
                transform.scale(scale, scale);
                transform.translate(-w_dragStartOriginX, -w_dragStartOriginY);

                Point2D d_Pt = new Point2D.Double(d_X, d_Y);
                Point2D w_Pt = new Point2D.Double();
                try {
                    transform.inverseTransform(d_Pt, w_Pt);
                } catch (NoninvertibleTransformException ex) {
                    return;
                }
                int w_X = (int)w_Pt.getX();
                int w_Y = (int)w_Pt.getY();

                int w_deltaX = w_X - w_dragStartX;
                int w_deltaY = w_Y - w_dragStartY;

                w_originX = w_dragStartOriginX - w_deltaX;
                w_originY = w_dragStartOriginY - w_deltaY;

                repaint();
            }
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            initDrag();

            imageState.getSettings().setImageOriginX(w_originX);
            imageState.getSettings().setImageOriginY(w_originY);
        }

        @Override
        public void mouseWheelMoved(MouseWheelEvent e) {
            int rotation = e.getWheelRotation();
            if((rotation > 0)  && scale > MIN_SCROLL ) {
                setScale(scale - 0.02f);
            } else if (scale < MAX_SCROLL) {
                setScale(scale + 0.02f);
            }

            imageState.getSettings().setImageScaleLevel(scale);
        }
    };

    private ImageStateListener imageStateListener = new ImageStateListener() {
        @Override
        public void valueChanged(Cell cell, String newValue) {
            return;
        }

        @Override
        public void selectedCellChanged(Cell newSelectedCell) {
            imageTable.setCurrentCell(newSelectedCell.getField(), newSelectedCell.getRecord());

            repaint();
        }
    };

    private NewProjectListener newProjectListener = new NewProjectListener() {
        @Override
        public void hasNewProject() {
            refreshImage();
        }
    };

    private ImageControlsListener imageControlsListener = new ImageControlsListener() {
        @Override
        public void onScrollIncrease() {
            if (scale < MAX_SCROLL) {
                setScale(scale + 0.02f);
            }
        }

        @Override
        public void onScrollDecrease() {
            if(scale > MIN_SCROLL ) {
                setScale(scale - 0.02f);
            }
        }

        @Override
        public void onInvertImage() {
            invertImage(image);
        }

        @Override
        public void onToggleHighlights() {
            if(imageTable.isHighlightsEnabled()) {
                imageTable.enableHighlights(false);
            } else {
                imageTable.enableHighlights(true);
            }
            repaint();
        }
    };

    public ImageControlsListener getImageControlsListener() {
        return imageControlsListener;
    }

}
