package shapeUtil;


import java.awt.*;
import java.awt.datatransfer.Transferable;
import java.awt.geom.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.Border.*;
import java.awt.dnd.*;
import java.io.File.*;
import java.util.Date;
import shape.*;
import util.*;
import menu.*;
 
public class ComponentLibPanel extends JPanel implements MouseListener, 
        MouseMotionListener, DragGestureListener, DropTargetListener{
    ComponentLibDialog dialog;
    final Dimension sizeOfComponent=new Dimension(24,24);
    final int xGap=12, yGap=12;
    final int xNum=8;
    int yNum=0;
    ShapeContainer[] components=null;
    Dimension panelSize=null;
    Rectangle2D[] componentRects=null;
    int selectedComponentIndex=-1;
    int gapIndex=-1;
    MultiLineToolTip mToolTip=new MultiLineToolTip(500, 60000);
    public static int debug=0;

    public ComponentLibPanel(ComponentLibDialog dialog){
        this.dialog=dialog;
        this.addMouseListener(this);
        this.addMouseMotionListener(this);
        this.addMouseListener(dialog.action);
        this.mToolTip.setComponent(this);
        Font font = new Font(Font.DIALOG, Font.BOLD, 12);
        mToolTip.setFont(font);
        String text="Drag a component from this panel and :"
            +"\n1. drop the component on the same panel to reorder the component."
            +"\n2. drop the component on the canvas to create a new shape.";
        this.setToolTipText(text);
        DragSource dragSource = DragSource.getDefaultDragSource();
        dragSource.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_COPY, this);
        DropTarget dropTarget = new DropTarget(this, this);
    }
    
    public JToolTip createToolTip() {
        this.mToolTip.setComponent(this);
        return this.mToolTip;
   }
    
    public Dimension getSizeOfComponent(){
        return this.sizeOfComponent;
    }
    
    public int getSelectedComponentByIndex(){
        //System.out.println("++ ComponentLibPanel.getSelectedComponent="+this.selectedComponentIndex);
        return this.selectedComponentIndex;
    }

    public void setSelectedComponentByIndex(int selectedComponentIndex){
        //System.out.println("++ ComponentLibPanel.getSelectedComponent="+this.selectedComponentIndex);
        this.selectedComponentIndex=selectedComponentIndex;
    }

    public Dimension getSizeOfScrollPane(){
        ContainerManager manager=this.dialog.getComponentManager().getContainerManager();
        int numOfComponents=manager.size();
        int width=this.xNum*(int)this.sizeOfComponent.getWidth()+(this.xNum+1)*this.xGap;
        this.yNum=(int)(numOfComponents/xNum);
        if(numOfComponents>this.xNum*this.yNum) this.yNum++;
        int height=this.yNum*(int)this.sizeOfComponent.getHeight()+(this.yNum+1)*this.yGap;
        return new Dimension(width+16, height+16);
    }

    public void paint(Graphics g) {
        ContainerManager manager=this.dialog.getComponentManager().getContainerManager();
        int numOfComponents=manager.size();
        setComponentPanel(numOfComponents);
        this.components=manager.getContainers();
        if(debug>0) System.out.println("++ paint numOfComponents="+numOfComponents+
                ", selectedComponentIndex="+this.getSelectedComponentByIndex());
        Graphics2D g2=(Graphics2D)g;
        //if(this.panelSize==null) return;
        Rectangle2D panelRect=new Rectangle2D.Double( 0, 0, panelSize.getWidth(),
                panelSize.getHeight());
        g2.setColor(Color.WHITE);
        g2.fill(panelRect);
        g2.setClip(panelRect);
        for(int i=0;i<numOfComponents;i++){
            Stroke currentStroke=g2.getStroke();
            g2.setColor(Color.LIGHT_GRAY);
            Rectangle2D enlargedRect=this.getEnlargedRectangle(this.componentRects[i], 4d, 4d);
            BasicStroke narrowStroke=new BasicStroke(0.5f, BasicStroke.CAP_SQUARE,
                    BasicStroke.JOIN_MITER, 10.0f);
            g2.setStroke(narrowStroke);
            g2.draw(enlargedRect);
            if(i==this.selectedComponentIndex){
                g2.setColor(Color.DARK_GRAY);
                BasicStroke wideStroke=new BasicStroke(2f, BasicStroke.CAP_SQUARE,
                    BasicStroke.JOIN_MITER, 10.0f);
                g2.setStroke(wideStroke);
                g2.draw(enlargedRect);
            }
            if(this.gapIndex>=0&&i==this.gapIndex){
                g2.setColor(Color.DARK_GRAY);
                BasicStroke wideStroke=new BasicStroke(2f, BasicStroke.CAP_SQUARE,
                    BasicStroke.JOIN_MITER, 10.0f);
                g2.setStroke(wideStroke);
                double lineX=this.componentRects[i].getX();
                double lineY=this.componentRects[i].getY();
                double lineH=this.componentRects[i].getHeight();
                Line2D line=new Line2D.Double(lineX-xGap/2, lineY, lineX-xGap/2, lineY+lineH);
                g2.draw(line);
            }
            if(this.gapIndex>=0&&(i+1)==this.gapIndex){
                g2.setColor(Color.DARK_GRAY);
                BasicStroke wideStroke=new BasicStroke(2f, BasicStroke.CAP_SQUARE,
                    BasicStroke.JOIN_MITER, 10.0f);
                g2.setStroke(wideStroke);
                double lineX=this.componentRects[i].getX();
                double lineY=this.componentRects[i].getY();
                double lineW=this.componentRects[i].getWidth();
                double lineH=this.componentRects[i].getHeight();
                Line2D line=new Line2D.Double(lineX+lineW+xGap/2, lineY, lineX+lineW+xGap/2, lineY+lineH);
                g2.draw(line);
            }
            g2.setStroke(currentStroke);
          //---------------------------------------------//
            drawComponent(g, i, this.componentRects[i]);
          //---------------------------------------------//
        }
    }

    private void setComponentPanel(int numOfComponents){
        this.yNum=numOfComponents/this.xNum;
        int residue=numOfComponents-this.yNum*this.xNum;
        if(residue>0||numOfComponents==0) this.yNum++;
        int w=this.xNum*(int)this.sizeOfComponent.getWidth()+(this.xNum+1)*this.xGap;
        int h=this.yNum*(int)this.sizeOfComponent.getHeight()+(this.yNum+1)*this.yGap;
        this.panelSize=new Dimension(w, h);
        this.setPreferredSize(this.panelSize);

        this.componentRects=new Rectangle2D[numOfComponents];
        double X=xGap;
        double Y=yGap;
        double width=this.sizeOfComponent.getWidth();
        double height=this.sizeOfComponent.getHeight();
        for(int i=0;i<numOfComponents;i++){
            this.componentRects[i]=new Rectangle2D.Double(X, Y, width, height);
            X+=width+xGap;
            if((i+1)-(int)((i+1)/this.xNum)*this.xNum==0){
                Y+=height+yGap;
                X=xGap;
            }
        }
        this.revalidate();
        if(debug>0) System.out.println(" -- ComponentLibPanel.setPreferredSize&revalidate");
    }
    
    public Rectangle2D getEnlargedRectangle(Rectangle2D box, double wEx, double hEx) {
        if (box == null) {
            return null;
        }
        double X = box.getX();
        double Y = box.getY();
        double Width = box.getWidth();
        double Height = box.getHeight();
        return new Rectangle2D.Double(X - wEx, Y - hEx, Width + 2.5d * wEx,
                Height + 2.5d * hEx);
    }
    
    private void drawComponent(Graphics g, int i, Rectangle2D rect){
        Graphics2D g2=(Graphics2D)g;
        Shape currentClip=g2.getClip();
        g2.setClip(rect);
        ShapeContainer container=this.components[i];
        Rectangle2D box=container.getBoundingBox();
        Color currentColor=g2.getColor();
        AffineTransform  currentTransform=g2.getTransform();
        double rectCenterX=rect.getX()+rect.getWidth()*0.5;
        double rectCenterY=rect.getY()+rect.getHeight()*0.5;
        double moveX=rectCenterX-box.getWidth()*0.5;
        double moveY=rectCenterY-box.getHeight()*0.5;
        g2.translate(moveX, moveY);
       //---------------------------// 
        container.drawShape(g);
       //---------------------------//   
        g2.translate(-moveX, -moveY);
        g2.setTransform(currentTransform);
        g2.setColor(currentColor);
        g2.setClip(currentClip);
    }
    
   //DragGestureListener methods 
    public void dragGestureRecognized(DragGestureEvent e) {
        int index=this.getSelectedComponentByIndex();
        ContainerManager manager=this.dialog.getComponentManager().getContainerManager();
        ShapeContainer container=manager.getContainer(index);
        TransferableComponent transfer=new TransferableComponent((ShapeContainer)container.clone());
        e.startDrag(DragSource.DefaultCopyDrop, transfer);
    }
    
  //DragTargetListener methods 
    public void drop(DropTargetDropEvent e) {
        if(debug>=0) System.out.println("ComponentLibPanel.drop called");
        if (e.isDataFlavorSupported(TransferableComponent.componentFlavor)) {
            e.acceptDrop(DnDConstants.ACTION_COPY);
        }else{
            e.rejectDrop();
            return;
        }
        Transferable transferable = e.getTransferable(); // Holds the dropped data
        ShapeContainer container;
        try {
            container=(ShapeContainer)transferable.getTransferData(TransferableComponent.componentFlavor);
        } catch (Exception ex) {
            e.dropComplete(false);
            return;
        }
       // 
        Point p = e.getLocation(); 
        Dimension dim=this.getPreferredSize();
        if(p.x>=0&&p.x<=dim.width&&p.y>=0&&p.y<=dim.height){
            if(debug>0) System.out.println("** Warning drop point is inside ComponentLibPanel "
                    + ", drop p="+p);
            MouseEvent event=new MouseEvent(this, MouseEvent.MOUSE_RELEASED, (new Date()).getTime(), 
                    MouseEvent.BUTTON1_MASK, (int)p.getX(), (int)p.getY(), 1, false);
            this.mouseReleased(event);
            e.dropComplete(false);
            return;
        }
        e.dropComplete(true);
        //this.removeDropTarget();
    }
    
    public void dragEnter(DropTargetDragEvent e) {}
    public void dragExit(DropTargetEvent e) {}
    public void dropActionChanged(DropTargetDragEvent e) {}
    public void dragOver(DropTargetDragEvent e) {
        if(debug>=0) System.out.println("** dragOver pos="+e.getLocation());
        Point p=e.getLocation();
        MouseEvent event=new MouseEvent(this, MouseEvent.MOUSE_RELEASED, (new Date()).getTime(), 
               MouseEvent.BUTTON1_MASK, (int)p.getX(), (int)p.getY(), 1, false);
        this.mouseDragged(event);
    }
    
   //MouseListener, MouseMotionListener methods
    public void mousePressed(MouseEvent e){
        //System.out.println("** mousePressed");
        double X = e.getX();
        double Y = e.getY();
        Point2D startPoint=new Point2D.Double(X,Y);
        int index=this.ptInside(startPoint);
        if(index>=0) this.selectedComponentIndex=index;
        else this.selectedComponentIndex=-1;
        this.dialog.showMessage(" ", Color.BLACK);
        this.repaint();
    }

    public void mouseDragged(MouseEvent e) {
        //System.out.println("** mouseDragged");
        double X = e.getX();
        double Y = e.getY();
        Point2D currentPoint=new Point2D.Double(X,Y);
        this.gapIndex=this.getNearestGap(currentPoint);
        this.repaint();
    } //mouseDragged

    public void mouseReleased(MouseEvent e) {
        if(this.selectedComponentIndex>=0&&this.gapIndex>=0){
            this.moveComponent(this.selectedComponentIndex, this.gapIndex);
            ComponentManager componentManager=this.dialog.getComponentManager();
            componentManager.writeComponentList();
            if(this.gapIndex<this.selectedComponentIndex) {
                this.selectedComponentIndex=this.gapIndex;
            }else {
                this.selectedComponentIndex=this.gapIndex-1;
            }
        }
        this.gapIndex=-1;
        this.repaint();
    } //mouseReleased

    public void mouseClicked(MouseEvent e) {}
    public void mouseEntered(MouseEvent e) {}
    public void mouseExited(MouseEvent e) {}
    public void mouseMoved(MouseEvent e) {}
    public int ptInside(Point2D point){
        int index=-1;
        for(int i=0;i<componentRects.length;i++){
            if(componentRects[i].contains(point)) index=i;
        }
        return index;
    }

    public int getNearestGap(Point2D point){
        int index=-1;
        int side=-1;
        double distMin=1e+5;
        double X=point.getX();
        double Y=point.getY();
        for(int i=0;i<componentRects.length;i++){
            double x=componentRects[i].getX();
            double y=componentRects[i].getY();
            double w=componentRects[i].getWidth();
            double h=componentRects[i].getHeight();
            if(Y<y||Y>y+h) continue;
            double dist1=Math.abs(X-x);
            double dist2=Math.abs(X-x-w);
            if(dist1<distMin){
                distMin=dist1;
                index=i;
                side=0;
            }
            if(dist2<distMin){
                distMin=dist2;
                index=i;
                side=1;
            }
        }
        int gapIndex=-1;
        if(index>=0&&side==0)  gapIndex=index;
        if(index>=0&&side==1)  gapIndex=index+1;
        if(debug>0) System.out.println("  -- nearest gap="+gapIndex+", X,Y="+X+","+Y);
        return gapIndex;
    }

    private void moveComponent(int selectedComponentIndex, int gapIndex){
        ComponentManager componentManager=this.dialog.getComponentManager();
        componentManager.moveComponent(selectedComponentIndex, gapIndex);
    }

} 