/* Copyright or (C) or Copr. GET / ENST, Telecom-Paris, Ludovic Apvrille, Andrea Enrici
 * 
 * ludovic.apvrille AT telecom-paristech.fr
 * andrea.enrici AT telecom-paritech.fr
 * 
 * This software is a computer program whose purpose is to allow the
 * edition of TURTLE analysis, design and deployment diagrams, to
 * allow the generation of RT-LOTOS or Java code from this diagram,
 * and at last to allow the analysis of formal validation traces
 * obtained from external tools, e.g. RTL from LAAS-CNRS and CADP
 * from INRIA Rhone-Alpes.
 * 
 * This software is governed by the CeCILL  license under French law and
 * abiding by the rules of distribution of free software.  You can  use,
 * modify and/ or redistribute the software under the terms of the CeCILL
 * license as circulated by CEA, CNRS and INRIA at the following URL
 * "http://www.cecill.info".
 * 
 * As a counterpart to the access to the source code and  rights to copy,
 * modify and redistribute granted by the license, users are provided only
 * with a limited warranty  and the software's author,  the holder of the
 * economic rights,  and the successive licensors  have only  limited
 * liability.
 * 
 * In this respect, the user's attention is drawn to the risks associated
 * with loading,  using,  modifying and/or developing or reproducing the
 * software by the user in light of its specific status of free software,
 * that may mean  that it is complicated to manipulate,  and  that  also
 * therefore means  that it is reserved for developers  and  experienced
 * professionals having in-depth computer knowledge. Users are therefore
 * encouraged to load and test the software's suitability as regards their
 * requirements in conditions enabling the security of their systems and/or
 * data to be ensured and,  more generally, to use and operate it in the
 * same conditions as regards security.
 * 
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL license and that you accept its terms.
 */




package ui.sd;

import myutil.GraphicLib;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import ui.*;
import ui.util.IconManager;
import ui.window.JDialogSDInstance;

import javax.swing.*;
import java.awt.*;



/**
   * Class SDInstance
   * Fixed duration operator. To be used in sequence diagrams
   * Creation: 04/10/2004
   * @version 1.1 10/06/2014
   * @author Ludovic APVRILLE, Andrea ENRICI
 */
public class SDInstance extends TGCWithInternalComponent implements SwallowTGComponent {
    //private int lineLength = 5;
    //private int textX, textY;
    private int spacePt = 10;
    private int wText = 10, hText = 15;
    private int increaseSlice = 250;
    private boolean isActor;
    private static int heightActor = 30;
    private static int widthActor = 16;


    public SDInstance(int _x, int _y, int _minX, int _maxX, int _minY, int _maxY, boolean _pos, TGComponent _father, TDiagramPanel _tdp)  {
        super(_x, _y, _minX, _maxX, _minY, _maxY, _pos, _father, _tdp);

        width = 10;
        height = 500;
        //textX = 0;
        //textY = 2;
        minWidth = 10;
        maxWidth = 10;
        minHeight = 250;
        maxHeight = 1500;


        makeTGConnectingPoints();
        //addTGConnectingPointsComment();

        nbInternalTGComponent = 0;

        moveable = true;
        editable = true;
        removable = true;
        userResizable = true;

        value = "Instance name";
        name = "instance";
        isActor = false;

        myImageIcon = IconManager.imgic500;
    }

    
    public void internalDrawing(Graphics g) {

        if( !tdp.isScaled() ) {
            wText  = g.getFontMetrics().stringWidth(name);
            hText = g.getFontMetrics().getHeight();
        }
        g.drawString( name, x - (wText / 2) + width/2, y - 3 );
        g.drawLine(x - (wText / 2) + width/2, y-2, x + (wText / 2) + width/2, y-2);
        g.drawLine(x+(width/2), y, x+(width/2), y +height);

        if( isActor ) {
            int xtmp = x + (width-widthActor) / 2;
            int ytmp = y-hText;
            // Head
            g.drawOval(xtmp+(widthActor/4)-1, ytmp-heightActor, 2+widthActor/2, 2+widthActor/2);
            //Body
            g.drawLine(xtmp+widthActor/2, ytmp-heightActor/3, xtmp+widthActor/2, ytmp-(2*heightActor)/3);
            //Arms
            g.drawLine(xtmp, ytmp-(heightActor/2) - 2, xtmp+widthActor, ytmp-(heightActor/2) - 2);
            //Left leg
            g.drawLine(xtmp+widthActor, ytmp, xtmp+widthActor/2, ytmp-heightActor/3);
            //right leg
            g.drawLine(xtmp, ytmp, xtmp+widthActor/2, ytmp-heightActor/3);
        }
    }

    
    public TGComponent isOnOnlyMe(int _x, int _y) {
        if (GraphicLib.isInRectangle(_x, _y, x, y, width, height)) {
            return this;
        }

        if (GraphicLib.isInRectangle(_x, _y, x + (width/2) - (wText/2) , y-hText, wText, hText)) {
            return this;
        }

        if (isActor) {
            if (GraphicLib.isInRectangle(_x, _y, x + (width-widthActor) / 2, y-heightActor-hText, widthActor, heightActor)) {
                return this;
            }
        }
        return null;
    }

    public int getMyCurrentMinX() {
        return Math.min(x + (width/2) - (wText/2), x);

    }

    public int getMyCurrentMaxX() {
        return Math.max(x + (width/2) + (wText/2), x + width);
    }

    public int getMyCurrentMinY() {
        return Math.min(y-hText-heightActor, y);
    }

    public String getInstanceName() {
        return getValue();
    }

    public int getType() {
        return TGComponentManager.SD_INSTANCE;
    }

    private void makeTGConnectingPoints() {

        nbConnectingPoint = ((height - (2 * spacePt)) / spacePt) + 1;
        connectingPoint = new TGConnectingPoint[nbConnectingPoint];

        int yh = spacePt;

        for(int i=0; i<nbConnectingPoint; i ++, yh+=spacePt) {
            connectingPoint[i] = new TGConnectingPointMessageSD(this, (width/2), yh, true, true);
        }

    }

    public boolean editOndoubleClick(JFrame frame) {
        String oldValue = name;

        JDialogSDInstance jdsdi = new JDialogSDInstance(frame, name, isActor, "Instance attributes");
        jdsdi.setSize(300, 250);
        GraphicLib.centerOnParent(jdsdi);
        jdsdi.setVisible( true ); // blocked until dialog has been closed


        String text = getName() + ": ";
        if (hasFather()) {
            text = getTopLevelName() + " / " + text;
        }

        if (jdsdi.hasBeenUpdated()) {
            isActor = jdsdi.isAnActor();
            String s = jdsdi.getInstanceName();

            if (s != null) {
                s = s.trim();
            }

            if((s != null) && (s.length() > 0) && (!s.equals(oldValue))) {
                if(!TAttribute.isAValidId(s, false, false)) {
                    JOptionPane.showMessageDialog( frame,
                                                   "Could not change the name of the instance: the new name is not a valid name",
                                                   "Error", JOptionPane.INFORMATION_MESSAGE );
                    return false;
                }
                setName(s);
                return true;
            }
        }
        return false;
    }

    public boolean acceptSwallowedTGComponent(TGComponent tgc) {
        if ((tgc instanceof SDAbsoluteTimeConstraint) || (tgc instanceof SDRelativeTimeConstraint) || (tgc instanceof SDTimeInterval)){
            return true;
        }

        if ((tgc instanceof SDActionState) || (tgc instanceof SDCoregion)|| (tgc instanceof SDGuard)) {
            return true;
        }

        if (tgc instanceof SDTimerSetting) {
            return true;
        }

        if (tgc instanceof SDTimerExpiration) {
            return true;
        }

        return tgc instanceof SDTimerCancellation;

    }

    public boolean addSwallowedTGComponent(TGComponent tgc, int x, int y) {
        if (!acceptSwallowedTGComponent(tgc)) {
            return false;
        }


        //System.out.println("Add swallow component");
        // Choose its position
        int realY = Math.max(y, getY() + spacePt);
        realY = Math.min(realY, getY() + height + spacePt);
        int realX = tgc.getX();


        // Make it an internal component
        // It's one of my son
        tgc.setFather(this);
        tgc.setDrawingZone(true);

        // Set its coordinates
        if ((tgc instanceof SDAbsoluteTimeConstraint) || (tgc instanceof SDRelativeTimeConstraint) || (tgc instanceof SDTimeInterval)){
            realX = getX() + (width/2) - tgc.getWidth();
            //tgc.setCdRectangle((width/2) - tgc.getWidth(), (width/2), spacePt, height-spacePt);
            //tgc.setCdRectangle(0, -50, 0, 50);
            tgc.setCd(realX, realY);
        }

        if ((tgc instanceof SDActionState) || (tgc instanceof SDCoregion)|| (tgc instanceof SDGuard)) {
            realX = getX()+(width/2);
            //tgc.setCdRectangle((width/2), (width/2), spacePt, height-spacePt-tgc.getHeight());
            tgc.setCd(realX, realY);
        }

        if (tgc instanceof SDTimerSetting) {
            realX = getX()+(width/2) + ((SDTimerSetting)tgc).getLineLength() - tgc.getWidth()/2;
            //tgc.setCdRectangle((width/2) + ((SDTimerSetting)tgc).getLineLength() - tgc.getWidth()/2, (width/2) + ((SDTimerSetting)tgc).getLineLength() - tgc.getWidth()/2, spacePt - tgc.getHeight()/2, height-spacePt-tgc.getHeight() / 2);
            tgc.setCd(realX, realY);
        }

        if (tgc instanceof SDTimerExpiration) {
            realX = getX()+(width/2) + ((SDTimerExpiration)tgc).getLineLength() - tgc.getWidth()/2;
            //tgc.setCdRectangle((width/2) + ((SDTimerExpiration)tgc).getLineLength() - tgc.getWidth()/2, (width/2) + ((SDTimerExpiration)tgc).getLineLength() - tgc.getWidth()/2, spacePt - tgc.getHeight()/2, height-spacePt-tgc.getHeight() / 2);
            tgc.setCd(realX, realY);
        }

        if (tgc instanceof SDTimerCancellation) {
            realX = getX()+(width/2) + ((SDTimerCancellation)tgc).getLineLength() - tgc.getWidth()/2;
            //tgc.setCdRectangle((width/2) + ((SDTimerCancellation)tgc).getLineLength() - tgc.getWidth()/2, (width/2) + ((SDTimerCancellation)tgc).getLineLength() - tgc.getWidth()/2, spacePt - tgc.getHeight()/2, height-spacePt-tgc.getHeight() / 2);
            tgc.setCd(realX, realY);
        }

        setCDRectangleOfSwallowed(tgc);

        // coregions -> in the middle !

        // else unknown

        //add it
        addInternalComponent(tgc, 0);

        return true;
    }

    public void removeSwallowedTGComponent(TGComponent tgc) {
        removeInternalComponent(tgc);
    }

    public boolean isInCoregion(int yy) {
        TGComponent tgc;
        for(int i=0; i<nbInternalTGComponent; i++) {
            tgc = tgcomponent[i];
            if (tgc instanceof SDCoregion) {
                //System.out.println("Coregion found from " + tgc.getY() + " to " + (tgc.getY() + tgc.getHeight()));
                if (tgc.isOnMe(tgc.getX(), yy) != null) {
                    return true;
                }
            }
        }
        return false;
    }

    public boolean isInSameCoregion(int y1, int y2) {
        //System.out.println("Is in same coregion y1=" + y1 + " y2=" + y2);
        int y11 = Math.min(y1, y2);
        int y22 = Math.max(y1, y2) +1;
        for(int i=y11; i<y22; i++) {
            if (!isInCoregion(i)) {
                //System.out.println("No!");
                return false;
            }
        }
        //System.out.println("YES !");
        return true;
    }

    public boolean inSameCoregion(TGComponent tgc1, TGComponent tgc2) {
        TGComponent tgctmp;
        if (tgc2.getY() < tgc1.getY()) {
            tgctmp = tgc1;
            tgc1 = tgc2;
            tgc2 = tgctmp;
        }

        // each y between the two components should be in a coregion
        for(int i=tgc1.getY(); i<tgc2.getY()+1; i++) {
            if (!isInCoregion(i)) {
                return false;
            }
        }

        return true;

    }

    // previous in the sense of with y the closer and before
    public TGComponent getPreviousTGComponent(TGComponent tgcToAnalyse) {
        int close = Integer.MAX_VALUE;
        TGComponent tgc;
        TGComponent tgcfound = null;
        int diff;

        for(int i=0; i<nbInternalTGComponent; i++) {
            tgc = tgcomponent[i];
            if (tgc != tgcToAnalyse) {
                diff = tgcToAnalyse.getY() - tgc.getY();
                if ((diff > 0) && (diff < close)) {
                    if (!inSameCoregion(tgcToAnalyse, tgc)) {
                        close = diff;
                        tgcfound = tgc;
                    }

                }
            }
        }

        return tgcfound;
    }

    public TGComponent getTGComponentActionCloserTo(TGComponent tgc) {
        /* action : message send, message receive, other ? */
        /* timers ? */
        /* now: only message! */

        return ((SequenceDiagramPanel)tdp).messageActionCloserTo(tgc, this);

    }

    /*public void addActionToPopupMenu(JPopupMenu componentMenu, ActionListener menuAL, int x, int y) {
        componentMenu.addSeparator();

        JMenuItem decrease = new JMenuItem("Decrease size");
        decrease.addActionListener(menuAL);
        componentMenu.add(decrease);
        decrease.setEnabled(canDecreaseSize());
        JMenuItem increase = new JMenuItem("Increase size");
        increase.addActionListener(menuAL);
        componentMenu.add(increase);
    }

    public boolean eventOnPopup(ActionEvent e) {
        if ((e.getActionCommand().compareTo("Decrease size")) == 0) {
            decreaseSize();
        } else {
            increaseSize();
        }
        return true;
	}*/

    public void updateMinMaxSize() {
	((SequenceDiagramPanel)tdp).updateAllInstanceMinMaxSize();
    }

    public void setMinHeight(int _min) {
	minHeight = _min;
    }

    public int getMinHeightSize() {
	int msize = 250;
        int i;

        for(i=0; i<connectingPoint.length ; i++) {
            if (!(connectingPoint[i].isFree())) {
                msize = Math.max(msize, connectingPoint[i].getY() - y +spacePt );
            }
        }

        for(i=0; i<nbInternalTGComponent ; i++) {
            msize = Math.max(msize, tgcomponent[i].getY() + tgcomponent[i].getHeight()- y + spacePt);
        }

	return msize;
    }

    public boolean canDecreaseSize() {
	//TraceManager.addDev("Can decrease my size? " + getValue());
	
        if (height <= increaseSlice) {
            return false;
        }

        int newNbConnectingPoint = (((height-increaseSlice) - (2 * spacePt)) / spacePt) + 1;
        int i;

        for(i=newNbConnectingPoint; i<connectingPoint.length ; i++) {
            if (!connectingPoint[i].isFree()) {
                //System.out.println("Cannot reduce size because of a connecting point");
                return false;
            }
        }

        //SwallowedComponents
        for(i=0; i<nbInternalTGComponent ; i++) {
            //System.out.println("tgcomponent =" + tgc + "
            if ((tgcomponent[i].getY() + tgcomponent[i].getHeight()) > (getY() + getHeight() - increaseSlice)) {
                //System.out.println("Cannot reduce size because of a swallowed component");
                return false;
            }
        }

	//TraceManager.addDev("Can decrease my size! " + getValue());

        return true;
    }

    public void setUserResize(int desired_x, int desired_y, int desired_width, int desired_height) {
        //System.out.println("newx = " + desired_x + " newy = " + desired_y + " minWidth = " + minWidth);
        setCd(desired_x, desired_y);
        actionOnUserResize(desired_width, desired_height);
	((SequenceDiagramPanel)tdp).instanceHasBeenResized(this,  desired_width, desired_height);
    }

    public void decreaseSize() {
        //System.out.println("Decrease size");
        //Check whether it is possible or not (swallowed components and tgconnecting points used
        if (!canDecreaseSize()) {
            return;
        }
        // new nb of connectingPoints

        // If ok, do the modification
        height = height - increaseSlice;
        hasBeenResized();
    }

    public void increaseSize() {
        //System.out.println("Increase size");
        height = height + increaseSlice;
        hasBeenResized();
    }

    public void hasBeenResized(){
        int i;

        TGConnectingPoint [] connectingPointTmp = connectingPoint;
        makeTGConnectingPoints();
        for(i=0; i<Math.min(connectingPointTmp.length, connectingPoint.length) ; i++) {
            connectingPoint[i] = connectingPointTmp[i];
        }

        // Increase tdp if necessary?

        // Reposition each swallowed component
        for(i=0; i<nbInternalTGComponent ; i++) {
            setCDRectangleOfSwallowed(tgcomponent[i]);
        }
    }

    private void setCDRectangleOfSwallowed(TGComponent tgc) {
        if ((tgc instanceof SDAbsoluteTimeConstraint) || (tgc instanceof SDRelativeTimeConstraint)){
            tgc.setCdRectangle((width/2) - tgc.getWidth(), (width/2), spacePt, height-spacePt);
        }

        if ((tgc instanceof SDActionState) || (tgc instanceof SDGuard) || (tgc instanceof SDCoregion) || (tgc instanceof SDTimeInterval)) {
            tgc.setCdRectangle((width/2), (width/2), spacePt, height-spacePt-tgc.getHeight());
        }

        if (tgc instanceof SDTimerSetting) {
            tgc.setCdRectangle((width/2) + ((SDTimerSetting)tgc).getLineLength() - tgc.getWidth()/2, (width/2) + ((SDTimerSetting)tgc).getLineLength() - tgc.getWidth()/2, spacePt - tgc.getHeight()/2, height-spacePt-tgc.getHeight() / 2);
        }

        if (tgc instanceof SDTimerExpiration) {
            tgc.setCdRectangle((width/2) + ((SDTimerExpiration)tgc).getLineLength() - tgc.getWidth()/2, (width/2) + ((SDTimerExpiration)tgc).getLineLength() - tgc.getWidth()/2, spacePt - tgc.getHeight()/2, height-spacePt-tgc.getHeight() / 2);
        }

        if (tgc instanceof SDTimerCancellation) {
            tgc.setCdRectangle((width/2) + ((SDTimerCancellation)tgc).getLineLength() - tgc.getWidth()/2, (width/2) + ((SDTimerCancellation)tgc).getLineLength() - tgc.getWidth()/2, spacePt - tgc.getHeight()/2, height-spacePt-tgc.getHeight() / 2);
        }
    }

    protected String translateExtraParam() {
        StringBuffer sb = new StringBuffer("<extraparam>\n");
        sb.append("<Actor data=\"");
        sb.append(""+isActor);
        sb.append("\" />\n");
        sb.append("</extraparam>\n");
        return new String(sb);
    }

    @Override
    public void loadExtraParam(NodeList nl, int decX, int decY, int decId) throws MalformedModelingException{
        //System.out.println("*** load extra synchro ***");
        try {
            NodeList nli;
            Node n1, n2;
            Element elt;

            for(int i=0; i<nl.getLength(); i++) {
                n1 = nl.item(i);
                //System.out.println(n1);
                if (n1.getNodeType() == Node.ELEMENT_NODE) {
                    nli = n1.getChildNodes();

                    // Issue #17 copy-paste error on j index
                    for(int j=0; j<nli.getLength(); j++) {
                        n2 = nli.item(j);
                        //System.out.println(n2);
                        if (n2.getNodeType() == Node.ELEMENT_NODE) {
                            elt = (Element) n2;
                            if (elt.getTagName().equals("Actor")) {
                                if (elt.getAttribute("data").compareTo("true") == 0) {
                                    isActor = true;
                                }
                            }
                        }
                    }
                }
            }

        } catch (Exception e) {
            throw new MalformedModelingException();
        }
    }

    public void setActor(boolean b) {
        isActor = b;
    }

}