package Matrix2P;

import javax.swing.*;
import javax.imageio.ImageIO;
import java.awt.event.ActionListener;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.concurrent.locks.ReentrantLock;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Collections;
import java.io.File;

/**
 * Author: Max Buegler - max@sarstech.com
 * Date: Oct 17, 2009
 * Time: 1:38:27 PM
 */
public class ThreatPoint extends JFrame implements GameAlgorithm{
    private final Color[] colors=new Color[]{Color.GREEN,Color.BLUE,Color.RED};
    private BufferedImage buffer;
    private ReentrantLock lock;
    private double tx,ty;
    private Polygon area;
    private GameMatrix m;
    private ArrayList<Point> plausibleArea;

    public ThreatPoint(){
        super("Wega threat point (Right click to save)");
        super.addMouseListener(new Mouse(this));
        this.lock=new ReentrantLock();

    }


    public void settings(ActionListener updater) {
        final JFrame settingsFrame=new JFrame("Dominated Actions settings");
        settingsFrame.setLayout(new GridBagLayout());
        GridBagConstraints cons=new GridBagConstraints();
        cons.weightx=0.5;
        cons.weighty=0.5;
        cons.gridx=0;
        cons.gridy=0;
        cons.gridheight=1;
        cons.gridwidth=1;
        cons.fill=GridBagConstraints.HORIZONTAL;
        JPanel aboutPanel=new JPanel();
        aboutPanel.setBorder(BorderFactory.createCompoundBorder(
                        BorderFactory.createTitledBorder("About"),
                        BorderFactory.createEmptyBorder(5,5,5,5)));
            aboutPanel.setLayout(new GridLayout(1,1));
            JTextArea about=new JTextArea();
            about.setEditable(false);
            about.setCursor(null);
            about.setOpaque(false);
            about.setFocusable(false);
            about.setLineWrap(true);
            about.setText("Finds the threat point of the game and draws a payoff diagram");
            aboutPanel.add(about);
        settingsFrame.add(aboutPanel,cons);

        settingsFrame.setSize(300,200);
        settingsFrame.setVisible(true);
    }

    public void run(GameMatrix m, ActionListener finish) {
        double p1t=Double.MAX_VALUE;
        this.m=m;
        int p2a=-1;
        for (int x=0;x<m.getActionsP2();x++){
            double max=-Double.MAX_VALUE;
            for (int y=0;y<m.getActionsP1();y++){
                if (m.getP1()[y][x]>max)max=m.getP1()[y][x];
            }
            if (max<p1t){
                p1t=max;
                p2a=x;
            }
        }
        double p2t=Double.MAX_VALUE;
        int p1a=-1;
        for (int x=0;x<m.getActionsP1();x++){
            double max=-Double.MAX_VALUE;
            for (int y=0;y<m.getActionsP2();y++){
                if (m.getP2()[x][y]>max)max=m.getP2()[x][y];
            }
            if (max<p2t){
                p2t=max;
                p1a=x;
            }
        }
        if ((int)p1t==p1t&&(int)p2t==p2t)System.out.println("Threat point: ("+(int)p1t+","+(int)p2t+")");
        else System.out.println("Threat point: ("+p1t+","+p2t+")");
        System.out.println("Player 1 action: "+(p1a+1));
        System.out.println("Player 2 action: "+(p2a+1));
        area=new Polygon(m);
        tx=p1t;
        ty=p2t;
        plausibleArea=findPlausiblePayoffArea();
        setSize(400,400);
        if (isVisible())
            repaint();
        else 
            setVisible(true);
        finish.actionPerformed(null);
    }

    public void paint(Graphics g2){
        if (lock.isLocked())return;
        lock.lock();
        try{
            if (buffer==null||buffer.getWidth()!=getWidth()||buffer.getHeight()!=getHeight()){
                buffer=new BufferedImage(getWidth(),getHeight(),BufferedImage.TYPE_BYTE_INDEXED);
            }
            Graphics2D g=(Graphics2D)buffer.getGraphics();
            int h=getHeight()-100;
            int w=getWidth()-80;
            g.setColor(Color.WHITE);
            g.fillRect(0,0,getWidth(),getHeight());
            h-=10;
            FontMetrics fM=g.getFontMetrics();
            int fh=fM.getHeight();
            int fx=(int)(getWidth()-fM.getStringBounds("Threat point",g).getWidth())/2;
            g.setColor(Color.BLACK);
            g.drawString("Threat point",fx,40);

            g.drawRect(40,50,w,h);
            fx=(int)(20-fM.getStringBounds("p2",g).getWidth())/2;
            if (fx<0)fx=0;
            int fy=50+(h+fh)/2;
            g.drawString("p2",fx,fy);
            fx=(int)(w-fM.getStringBounds("p1",g).getWidth())/2;
            g.drawString("p1",40+fx,50+h+2*fh);
            double cw=((double)w)/(1.1*(area.getMaxx()-area.getMinx()));
            double ch=((double)h)/(1.1*(area.getMaxy()-area.getMiny()));
            double step=1;
            if (area.getMaxx()-area.getMinx()>300)step=(area.getMaxx()-area.getMinx())/30;
            else if (area.getMaxx()-area.getMinx()>100)step=10;
            else if (area.getMaxx()-area.getMinx()>15)step=2.5;
            else if (area.getMaxx()-area.getMinx()<=1)step=0.05;
            else if (area.getMaxx()-area.getMinx()<=4)step=0.25;
            else if (area.getMaxx()-area.getMinx()<=15)step=0.5;
            double c=0;
            boolean label=true;
            while(c*cw<=w){
                int xp=(int)(40+c*cw);
                g.drawLine(xp,50+h-5,xp,50+h+3);
                if (label||(c+step)*cw>w){
                    if (!((c+2*step)*cw>w&&(c+step)*cw<w)){
                        double v=area.getMinx()+c;
                        v=Math.round(v*100)/100.0;
                        String l=v+"";
                        if ((int)v==v)l=(int)v+"";
                        if (l.length()-(((int)v)+"").length()>3)
                            l=l.substring(0,(((int)v)+"").length()+3);
                        int tx=(int)(xp-(fM.getStringBounds(l,g).getWidth()/2));
                        if (c>0)g.drawString(l,tx,52+h+fh);
                    }
                    label=false;
                }
                else label=true;
                c+=step;
            }
            step=1;
            if (area.getMaxy()-area.getMiny()>300)step=(area.getMaxy()-area.getMiny())/20;
            else if (area.getMaxy()-area.getMiny()>100)step=10;
            else if (area.getMaxy()-area.getMiny()>15)step=2.5;
            else if (area.getMaxy()-area.getMiny()<=1)step=0.05;
            else if (area.getMaxy()-area.getMiny()<=4)step=0.25;
            else if (area.getMaxy()-area.getMiny()<=15)step=0.5;


            c=0;
            label=true;
            while(c*ch<=h){
                int yp=(int)(50+h-c*ch);
                g.drawLine(40-3,yp,40+5,yp);
                if (label||(c+step)*ch>h){
                    double v=area.getMiny()+c;
                    v=Math.round(v*100)/100.0;
                    String l=v+"";
                    if ((int)v==v)l=(int)v+"";
                    if (l.length()-(((int)v)+"").length()>3)
                        l=l.substring(0,(((int)v)+"").length()+3);
                    int tx=(int)(35-fM.getStringBounds(l,g).getWidth());
                    if (tx<0)tx=0;
                    g.drawString(l,tx,yp+fh/2);
                    label=false;
                }
                else label=true;
                c+=step;
            }
            if (plausibleArea!=null&&plausibleArea.size()>2){
                int[] xpoints=new int[plausibleArea.size()],ypoints=new int[plausibleArea.size()];
                for (int x=0;x<plausibleArea.size();x++){
                    xpoints[x]=(int)(40+plausibleArea.get(x).getX()*cw);
                    ypoints[x]=(int)(50+h-plausibleArea.get(x).getY()*ch);
                }
                g.setColor(Color.LIGHT_GRAY);
                g.fillPolygon(xpoints,ypoints,plausibleArea.size());
            }
            g.setColor(colors[0]);
            g.setStroke(new BasicStroke(2));
            for (int x=0;x<area.size();x++){
                int next=x+1;
                if (next>=area.size())next=0;
                int x1=(int)(40+(area.get(x).getX()-area.getMinx())*cw);
                int x2=(int)(40+(area.get(next).getX()-area.getMinx())*cw);
                int y1=(int)(50+h-(area.get(x).getY()-area.getMiny())*ch);
                int y2=(int)(50+h-(area.get(next).getY()-area.getMiny())*ch);
                g.drawLine(x1,y1,x2,y2);
            }
            g.setColor(colors[1]);
            for (int x=0;x<m.getActionsP1();x++){
                for (int y=0;y<m.getActionsP2();y++){
                    g.fillOval((int)(40+(m.getP1()[x][y]-area.getMinx())*cw-5),(int)(50+h-(m.getP2()[x][y]-area.getMiny())*ch-5),10,10);
                    if ((int)m.getP1()[x][y]==m.getP1()[x][y]&&
                            (int)m.getP2()[x][y]==m.getP2()[x][y])
                        g.drawString("("+(int)m.getP1()[x][y]+","+(int)m.getP2()[x][y]+")",(int)(45+(m.getP1()[x][y]-area.getMinx())*cw-5),(int)(50+h-(m.getP2()[x][y]-area.getMiny())*ch-5));
                    else g.drawString("("+m.getP1()[x][y]+","+m.getP2()[x][y]+")",(int)(45+(m.getP1()[x][y]-area.getMinx())*cw-5),(int)(50+h-(m.getP2()[x][y]-area.getMiny())*ch-5));
                }
            }
            g.setColor(colors[2]);
            g.fillOval((int)(40+(tx-area.getMinx())*cw-5),(int)(50+h-(ty-area.getMiny())*ch-5),10,10);
            g.drawLine((int)(40+(tx-area.getMinx())*cw),50,(int)(40+(tx-area.getMinx())*cw),50+h);
            g.drawLine(40,(int)(50+h-(ty-area.getMiny())*ch),40+w,(int)(50+h-(ty-area.getMiny())*ch));
            if ((int)tx==tx&&(int)ty==ty)
                g.drawString("("+(int)tx+","+(int)ty+")",(int)(45+(tx-area.getMinx())*cw-5),(int)(50+h-(ty-area.getMiny())*ch-5));
            else g.drawString("("+tx+","+ty+")",(int)(45+(tx-area.getMinx())*cw-5),(int)(50+h-(ty-area.getMiny())*ch-5));


            g2.drawImage(buffer,0,0,null);
            setVisible(true);
        }
        catch(Exception ex){}
        lock.unlock();
    }

    private ArrayList<Point> findPlausiblePayoffArea(){
        if (area.size()<3)return new ArrayList<Point>();
        double xe=-1;
        double ye=-1;
        boolean foundx=false,foundy=false;
        for (int x=0;x<area.size();x++){
            if (area.get(x).getX()>tx&&area.get(x).getY()==ty){
                xe=area.get(x).getX();
                foundx=true;
            }
            if (area.get(x).getY()>ty&&area.get(x).getX()==tx){
                ye=area.get(x).getY();
                foundy=true;
            }
        }
        if (!foundx||!foundy){
            for (int x=0;x<area.size();x++){
                int lastx=x-1;
                if (lastx<0)lastx=area.size()-1;
                double dx=area.get(x).getX()-area.get(lastx).getX();
                double dy=area.get(x).getY()-area.get(lastx).getY();
                if (!foundx){
                    if (dy!=0){
                        double ix=(ty-area.get(lastx).getY())*(dx/dy)+area.get(lastx).getX();
                        if (isbetween(ix,area.get(x).getX(),area.get(lastx).getX())&&ix>tx&&ix<=area.getMaxx()){
                            xe=ix;
                            foundx=true;
                        }
                    }
                }
                if (!foundy){
                    if (dx!=0){
                        double iy=(tx-area.get(lastx).getX())*(dy/dx)+area.get(lastx).getY();
                        if (isbetween(iy,area.get(x).getY(),area.get(lastx).getY())&&iy>ty&&iy<=area.getMaxy()){
                            ye=iy;
                            foundy=true;
                        }
                    }
                }
            }
        }
        if (!foundx||!foundy){
            for (int x=0;x<area.size();x++){
                int lastx=x-1;
                if (lastx<0)lastx=area.size()-1;
                double dx=area.get(x).getX()-area.get(lastx).getX();
                double dy=area.get(x).getY()-area.get(lastx).getY();
                if (!foundx){
                    if (dy!=0){
                        double ix=(ty-area.get(lastx).getY())*(dx/dy)+area.get(lastx).getX();
                        if (isbetween(ix,area.get(x).getX(),area.get(lastx).getX())&&ix==tx&&ix<=area.getMaxx()){
                            xe=ix;
                            foundx=true;
                        }
                    }
                }
                if (!foundy){
                    if (dx!=0){
                        double iy=(tx-area.get(lastx).getX())*(dy/dx)+area.get(lastx).getY();
                        if (isbetween(iy,area.get(x).getY(),area.get(lastx).getY())&&iy==ty&&iy<=area.getMaxy()){
                            ye=iy;
                            foundy=true;
                        }
                    }
                }
            }
        }
        ArrayList<Point> plausibleArea=new ArrayList<Point>();
        if (foundx&&foundy){
            plausibleArea.add(new Point(xe,ty));
            int shift=-1;
            for (int x=0;x<area.size()&&shift<0;x++)
                if (area.get(x).getX()<tx||area.get(x).getY()<ty)shift=x;

            for (int x=0;x<area.size();x++){
                int rx=x+shift;
                if (rx>area.size()-1)rx-=area.size();
                if (area.get(rx).getX()>tx&&area.get(rx).getY()>ty)plausibleArea.add(area.get(rx));
            }
            plausibleArea.add(new Point(tx,ye));
            plausibleArea.add(new Point(tx,ty));
        }
        return plausibleArea;
    }

    private boolean isbetween(double x, double s, double e){
        if (s==e)return x==s;
        if (s<e&&x>=s&&x<=e)return true;
        return x<=s&&x>=e;
    }

    public void stop() {
        //To change body of implemented methods use File | Settings | File Templates.
    }

    public String getDescriptor() {
        return "Find threat point";  //To change body of implemented methods use File | Settings | File Templates.
    }
    class Mouse implements MouseListener {
        private JFrame frame;
        Mouse(JFrame frame) {
            this.frame = frame;
        }
        public void mouseClicked(MouseEvent e) {
            if (e.getButton()==MouseEvent.BUTTON3){
                lock.lock();
                try{
                    final JFileChooser fc = new JFileChooser();
                    fc.setName("Save Chart");
                    int retVal = fc.showSaveDialog(frame);
                    if (retVal==JFileChooser.APPROVE_OPTION){
                        File f=fc.getSelectedFile();
                        if (f.exists()){
                            retVal=JOptionPane.showConfirmDialog(frame,"Overwrite "+f.getName()+"?");
                            if (retVal!=JOptionPane.OK_OPTION)return;
                        }
                        int i=f.getName().lastIndexOf('.');
                        if (i>0){
                            String format=f.getName().substring(i+1);
                            ImageIO.write(buffer,format,f);
                        }
                        else{
                            ImageIO.write(buffer,"bmp",f);
                        }
                    }
                }
                catch(Exception ex){
                    JOptionPane.showMessageDialog(frame,"Could not save Image.");
                }
                lock.unlock();
                repaint();
            }
        }
        public void mousePressed(MouseEvent e){}
        public void mouseReleased(MouseEvent e){}
        public void mouseEntered(MouseEvent e){}
        public void mouseExited(MouseEvent e){}
    }
    class Point{
        private double x,y;
        Point(double x, double y){this.x = x;this.y = y;}
        public double getX(){return x;}
        public double getY(){return y;}
        public double getAngleTo(double px, double py){
            if (px==x&&py==y)return -1;
            double dx=x-px;
            double dy=y-py;
            if (dx==0){
                if (dy<0)return Math.PI;
                else return 0;
            }
            double out=Math.atan(dy/dx)+Math.PI/2;
            if (dx>0)out+=Math.PI;
            return out;
        }
        public boolean equals(Object e){
            if (!(e instanceof Point))return false;
            Point p=(Point)e;
            return p.x==x&&p.y==y;
        }

    }
    class PointAngleComparator implements Comparator<Point>{
        private double cx,cy;
        PointAngleComparator(double cx, double cy){this.cx = cx;this.cy = cy;}
        public int compare(Point o1, Point o2){
            return (int)(36000*(o1.getAngleTo(cx,cy)-o2.getAngleTo(cx,cy)));

        }
    }
    class Polygon extends ArrayList<Point>{
        private double minx,miny,maxx,maxy;

        public Polygon(){
            super();
        }
        public Polygon(GameMatrix m){
            super();
            double sx=0,sy=0;
            int c=0;
            for (int x=0;x<m.getActionsP1();x++){
                for (int y=0;y<m.getActionsP2();y++){
                    Point np=new Point(m.getP1()[x][y],m.getP2()[x][y]);
                    if (!contains(np)){
                        add(np);
                        if (m.getP1()[x][y]<minx)minx=m.getP1()[x][y];
                        if (m.getP1()[x][y]>maxx)maxx=m.getP1()[x][y];
                        if (m.getP2()[x][y]<miny)miny=m.getP2()[x][y];
                        if (m.getP2()[x][y]>maxy)maxy=m.getP2()[x][y];
                        sx+=m.getP1()[x][y];
                        sy+=m.getP2()[x][y];
                        c++;
                    }
                }
            }

            double cx=sx/c;
            double cy=sy/c;
            Collections.sort(this,new PointAngleComparator(cx,cy));
            convexize();
        }
        public Polygon removePoint(Point p){
            Polygon out=new Polygon();
            for (int x=0;x<size();x++){
                if (!get(x).equals(p)){
                    out.add(get(x));
                }
            }

            return out;
        }
        public void convexize(){
            if (size()<4)return;
            boolean improvement=true;
            while(improvement&&size()>3){
                improvement=false;
                double maxarea=getArea();
                int maxpoint=-1;
                for (int x=0;x<size();x++){
                    double a2=removePoint(get(x)).getArea();
                    if (a2>maxarea){
                        maxarea=a2;
                        maxpoint=x;
                    }
                }
                if (maxpoint>=0){
                    remove(maxpoint);
                    improvement=true;
                }
            }
        }
        public double getArea(){
            double out=0;
            for (int x=0;x<size();x++){
                int next=x+1;
                if (next>=size())next=0;
                out+=get(x).getX()*get(next).getY()-get(next).getX()*get(x).getY();
            }
            return Math.abs(out/2);
        }

        public double getMinx() {
            return minx;
        }

        public double getMiny() {
            return miny;
        }

        public double getMaxx() {
            return maxx;
        }

        public double getMaxy() {
            return maxy;
        }
    }
}
