/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package desenho;

import controlador.Editor;
import controlador.Diagrama;
import controlador.inspector.InspectorProperty;
import desenho.formas.Forma;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 *
 * @author Rick
 */
public class FormaElementar extends Elementar {

    private static final long serialVersionUID = -1032848572397943975L;

    // <editor-fold defaultstate="collapsed" desc="Criação">
    /**
     * Futuro
     */
    public FormaElementar() {
    }

    public FormaElementar(FormaElementar pai) {
        super(pai);
        ID = pai.getMaster().getElementarID();
    }

    public FormaElementar(Diagrama master) {
        ID = master.getElementarID();
//        ancorasCode.add(Ancorador.CODE_ANCORAR);
//        ancorasCode.add(Ancorador.CODE_DEL);
        InitElementar(master);
    }

    protected void InitializeSubItens(Diagrama criador) {
        SetFontAndBackColorFromModelo();
        this.subItens = new ArrayList<>();
    }

    private void InitElementar(Diagrama master) {
        setMaster(master);
        if (getMaster() != null) {
            getMaster().Add(this);
            InitializeSubItens(master);
        }
        this.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR));
    }
    // </editor-fold>
    // <editor-fold defaultstate="collapsed" desc="Campos">
    private List<Elementar> subItens = null;
    //private boolean nulo = false;
    //private boolean atualizando = false;
    private boolean selecionado = false;
    protected boolean PontosIsHide = false;
    protected boolean Selecionavel = true;
    protected int ID = -1;
    private boolean raiseMuda = true;

    /**
     * Poderá ser utilizado para dizer quais objetos podem ou não ser ajustados por comandos na iterface - alinhamento.
     */
    protected boolean AceitaAjusteAutmatico = true;

    public boolean isRaiseMuda() {
        return raiseMuda;
    }

    public void setRaiseMuda(boolean raiseMuda) {
        this.raiseMuda = raiseMuda;
    }

    protected void setID(int ID) {
        this.ID = ID;
    }

    public int getID() {
        return ID;
    }

    public boolean isSelecionavel() {
        return Selecionavel;
    }

    public void setSelecionavel(boolean Selecionavel) {
        this.Selecionavel = Selecionavel;
    }

    public boolean isPontosIsHide() {
        return PontosIsHide;
    }

    protected void setPontosIsHide(boolean PontosIsHide) {
        this.PontosIsHide = PontosIsHide;
    }

//    public boolean isAtualizando() {
//        return atualizando;
//    }
//    public void setAtualizando(boolean atualizando) {
//        this.atualizando = atualizando;
//    }
//    public boolean isNulo() {
//        return nulo;
//    }
//    public void setNulo(boolean nulo) {
//        this.nulo = nulo;
//    }
    public boolean isSelecionado() {
        return selecionado;
    }

    public void setSelecionado(boolean selecionado) {
        this.selecionado = selecionado;
    }

    public void setSubItens(ArrayList<Elementar> subItens) {
        this.subItens = subItens;
    }

    public List<Elementar> getSubItens() {
        return subItens;
    }

    /**
     * Define se o objeto ficará fixo no diagrama
     */
    private boolean ancorado = false;

    public boolean isAncorado() {
        return ancorado;
    }

    public void setAncorado(boolean ancorado) {
        if (this.ancorado == ancorado) {
            return;
        }
        this.ancorado = ancorado;
        //InvalidateArea();
    }
    // </editor-fold>

    /**
     * Roda por meio de um comando disparado pelo inspector.
     *
     * @param Tag
     */
    public void DoAnyThing(int Tag) {

    }

    /**
     * Quais cores o objeto usa. Serve para o objeto legenda capturar as cores e mostrar.
     *
     * @param cores
     */
    public void PoluleColors(ArrayList<Color> cores) {
        if (cores.indexOf(getForeColor()) == -1) {
            cores.add(getForeColor());
        }
        if (cores.indexOf(getBackColor()) == -1) {
            cores.add(getBackColor());
        }
    }

    /**
     * Em ordem de criação --> os sub componetes (independente de ser tratado como subitem.
     *
     * @param i: index
     * @return
     */
    public FormaElementar getSub(int i) {
        return null;
    }

    /**
     * Caso mova-se o mouse sobre o objeto
     *
     * @param b: está sobre?
     */
    public void setOverMe(boolean b) {

    }

    // Versao 3.2
    /**
     * No Tree de navegação, quando seleciona o subitem de um artefato.
     * @param index 
     */
    public void DoSubItemSel(int index) {
        
    }

    public enum nomeComandos {

        cmdNothing, cmdLoadImg, cmdDlgLegenda, cmdCallDrawerEditor, cmdExcluirSubItem,
        cmdAdicionarSubItem, cmdDoAnyThing, cmdFonte
    }

    /**
     * Distância entre a borda de um FomraElementar.
     */
    public final int distSelecao = 2;

    public void HidePontos(boolean esconde) {
        setPontosIsHide(esconde);
    }

    public void DoPontoCor(boolean verde) {
    }

    public boolean IntersectPath(Rectangle recsel) {
        return recsel.intersects(getBounds());
    }

    public void RemoveSubItem(Elementar si) {
        si.Destroy();
        subItens.remove(si);
    }

    @Override
    public Elementar IsMeOrMine(Point p) {
        Elementar res;
        for (Elementar el : subItens) {
            res = el.IsMeOrMine(p);
            if (res != null) {
                return res;
            }
        }
        return super.IsMeOrMine(p);
    }

    @Override
    public Elementar IsMeOrMine(Point p, Elementar nor) {
        Elementar res;
        for (Elementar el : subItens) {
            res = el.IsMeOrMine(p, nor);
            if (res != null) {
                return res;
            }
        }
        return super.IsMeOrMine(p, nor);
    }

    @Override
    public Elementar IsMeOrMineBase(Point p, Elementar nor) {
        Elementar res;
        for (Elementar el : subItens) {
            res = el.IsMeOrMineBase(p, nor);
            if (res != null && res instanceof Forma) {
                return res;
            }
        }
        return super.IsMeOrMineBase(p, nor);
    }

    @Override
    public void DoPaint(Graphics2D g) {
        super.DoPaint(g);
        if (isVisible()) {
            PinteSelecao(g);
            //paintAncora(g);
            for (int i = subItens.size() - 1; i > -1; i--) {
                if (subItens.get(i).CanPaint()) {
                    subItens.get(i).DoPaint(g);
                }
            }
        }
    }

//    public void paintAncora(Graphics2D g) {
//    }
    /**
     * Rearranja o componente de acordao com a posição dos pontos que o circunda quando selecionado.
     */
    public void reSetBounds() {
    }

    //public void Recalcule() {
    //}
    /**
     * Rearranja o componente de acordo com a posição dos pontos que o circunda quando selecionado.
     *
     * @param posicao Em qual das oito posições possíveis (0-7) está o ponto movimentado.
     * @param xleft Qual o novo Left do Ponto
     * @param ytop Qual o novo Top do Ponto
     */
    public void reSetBounds(int posicao, int xleft, int ytop) {
    }

    public void PinteSelecao(Graphics2D g) {
    }

    public void DoRaizeReenquadreReposicione() {
    }

    // <editor-fold defaultstate="collapsed" desc="Mouse">
    transient Point down = new Point(0, 0);
    private transient Point inidown = new Point(0, 0);

    protected Point getIniDown() {
        return inidown;
    }

    transient boolean isMouseDown = false;

    @Override
    public void mousePressed(MouseEvent e) {
        super.mousePressed(e);
        isMouseDown = true && !isAncorado();
        down = new Point(e.getX(), e.getY());
        inidown = new Point(e.getX(), e.getY());
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        isMouseDown = false;
        DoRaizeReenquadreReposicione();
        if (isPontosIsHide()) {
            getMaster().HidePontosOnSelecao(false);
        }
        Point enddown = new Point(e.getX(), e.getY());
        if (!enddown.equals(inidown)) {
            DoMuda();
        }
        super.mouseReleased(e);
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        if (!isPontosIsHide()) {
            getMaster().HidePontosOnSelecao(true);
        }
        super.mouseDragged(e);
        int X = e.getX();
        int Y = e.getY();
        if (isMouseDown) {
            int movX = X - down.x;
            int movY = Y - down.y;
            if ((movX != 0) || (movY != 0)) {
                DoRaiseMove(movX, movY);
                down.setLocation(e.getPoint());
            }
        }
    }

    @Override
    public void mouseDblClicked(MouseEvent e) {
        super.mouseDblClicked(e);
        ProcessaDblClick(e);
    }
    // </editor-fold>

    @Override
    public void Reposicione() {
        super.Reposicione();
    }

    public void DoRaiseMove(int movX, int movY) {
        DoMove(movX, movY);
    }

    /**
     * Métdo responsável por receber cliques de subItens
     *
     * @param sender disparador do médoto
     * @param dbl o clique é duplo
     * @param e Mouse.
     */
    public void ReciveClick(Elementar sender, boolean dbl, MouseEvent e) {
        //////fazer linha quadrada.
    }

    /**
     * Métdo responsável por lançar o MouseDblClick sem a necessidade de usar o método principal. Padrão próprio: evitar reescrever ou mexer nos métodos principais.
     *
     * @param dbl o clique é duplo
     * @param e Mouse.
     */
    protected void ProcessaDblClick(MouseEvent e) {
    }

    //<editor-fold defaultstate="collapsed" desc="Tratamento da propriedade">
    /**
     * Carrega as propriedades para o Inspector, veja GenerateFullProperty (FormaElementar)
     *
     * @return
     */
    public ArrayList<InspectorProperty> GenerateProperty() {
        ArrayList<InspectorProperty> res = new ArrayList<>();

        if (getMaster().getEditor().isMostrarIDs()) {
            res.add(InspectorProperty.PropertyFactoryApenasLeituraTexto("id", Integer.toString(ID)));
        }
        res.add(InspectorProperty.PropertyFactorySeparador("dimensoes", true));

        res.add(InspectorProperty.PropertyFactoryNumero("left", "setLeft", getLeft()));

        res.add(InspectorProperty.PropertyFactoryNumero("top", "setTop", getTop()));

        res.add(InspectorProperty.PropertyFactoryNumero("width", "setWidth", getWidth()));

        res.add(InspectorProperty.PropertyFactoryNumero("height", "setHeight", getHeight()));

        return res;
    }

    /**
     * Propriedadaes adicionadas em tempo de execução! Não sei se será implementado!
     */
    protected ArrayList<InspectorProperty> propriedadesAdicinais = null;

    /**
     * Método principal para carregar as propriedades de um objeto para o Inspector (FormaElementar)
     *
     * @return
     */
    public ArrayList<InspectorProperty> GenerateFullProperty() {
        ArrayList<InspectorProperty> res = GenerateProperty();
        if (propriedadesAdicinais != null) {
            res.addAll(propriedadesAdicinais);
        }
        return CompleteGenerateProperty(res);
    }

    /**
     * É executado após o método GenerateProperty, veja em GenerateFullProperty (FormaElementar)
     *
     * @param GP
     * @return
     */
    public ArrayList<InspectorProperty> CompleteGenerateProperty(ArrayList<InspectorProperty> GP) {
        return GP;
    }
    //</editor-fold>

    @Override
    public void DoMuda() {
        if (!isRaiseMuda()) {
            return;
        }
        super.DoMuda();
        if (getMaster() != null) {
            getMaster().DoMuda(this);
        } else if (getCriador() != null) {
            getCriador().DoMuda();
        }
    }

    /**
     * Este objeto pode ser carregdo no InfoDiagrama_LoadFromXML ?
     *
     * @return
     */
    public boolean getIsLoadedFromXML() {
        return true;
    }

    /**
     * Variavel de apoio para salvar em XML a condição de isDisablePainted(). Necessária porque, de outra forma, as cores persistidas seriam apenas a cor configurada para a condição de DisablePainted quando true.
     */
    private boolean snDisablePainted = false; 
            
    /**
     * Para que possa ser utilizado por softwares de terceiros e de diferentes linguagens, preferi este modelo ao de persistir o objeto em XLM usando o writer do próprio java.
     *
     * @param doc
     * @param root
     */
    public final void ToXlm(Document doc, Element root) {
        snDisablePainted = isDisablePainted();
        setDisablePainted(false); //# agora as cores serão salvas da meneira correta!
        Element me = doc.createElement(Editor.getClassTexto(this));
        ToXmlAtributos(doc, me);
        ToXmlValores(doc, me);
        root.appendChild(me);
        setDisablePainted(snDisablePainted);
    }

    /**
     * Serializa o ID dos objetos que estiverem ligados.
     *
     * @param doc
     * @param me
     */
    protected void SerializeListener(Document doc, Element me) {
        Element lst = doc.createElement("Listener");
        if (getListeners() != null) {
            for (ElementarListener el : getListeners()) {
                if (el instanceof FormaElementar) {
                    FormaElementar fe = (FormaElementar) el;
                    lst.appendChild(util.XMLGenerate.ValorRefFormElementar(doc, Editor.getClassTexto(fe), fe));
                }
            }
        }
        me.appendChild(lst);
    }

    /**
     * Refaz as ligações. Deve ser chamado no método CommitXML. Quando a ligação entre dois objetos passa por um objeto SuperLinha, não é necessário serializar as ligações. Os métodos
     * SerializeListener e UnSerializeListener devem ser usados para persistir as ligações entre dois objetos. Ex: FormaArea e os artefatos capturados.
     *
     * @param me
     * @param mapa
     */
    protected void UnSerializeListener(Element me, HashMap<Element, FormaElementar> mapa) {
        Element lig = util.XMLGenerate.FindByNodeName(me, "Listener");
        NodeList nodeLst = lig.getChildNodes();
        for (int s = 0; s < nodeLst.getLength(); s++) {
            Node fstNode = nodeLst.item(s);
            if (fstNode.getNodeType() == Node.ELEMENT_NODE) {
                Element fstElmnt = (Element) fstNode;
                String xid = fstElmnt.getAttribute("ID");
                FormaElementar resA = util.XMLGenerate.FindWhoHasID(xid, mapa);
                if (resA == null) {
                    continue;
                }
                PerformLigacao(resA, true);
            }
        }
    }

    /**
     * Dado um objeto, este método gera XLM para sua propriedades a partir de campos determinados
     *
     * @param doc
     * @param me
     */
    protected void ToXmlValores(Document doc, Element me) {
        me.appendChild(util.XMLGenerate.ValorRect(doc, "Bounds", getBounds()));
        me.appendChild(util.XMLGenerate.ValorBoolean(doc, "DisablePainted", snDisablePainted));
    }

    /**
     * Serializa os atributos do objeto.
     *
     * @param doc
     * @param me
     */
    protected void ToXmlAtributos(Document doc, Element me) {
        me.setAttribute("ID", Integer.toString(ID));
    }

    /**
     * Carrega um elemento a partir de um código XML.
     *
     * @param me
     * @param colando
     * @return
     */
    public boolean LoadFromXML(Element me, boolean colando) {
        String aID = me.getAttribute("ID");
        if (!colando) {
            ID = Integer.valueOf(aID);
        }
        Rectangle bounds = util.XMLGenerate.getValorRectFrom(me, "Bounds");
        if (bounds != null) {
            /**
             * Referencia igual em Ligacao.LoadFromXML
             */
            SetBounds(bounds);
        }
        boolean x = util.XMLGenerate.getValorBooleanFrom(me, "DisablePainted");
        SetDisablePainted(x);
        return true;
    }

    /**
     * É executado após o carregamento do XML (quer colando, quer carregando de uma arquivo).
     *
     * @param me
     * @param mapa
     * @return
     */
    public boolean CommitXML(Element me, HashMap<Element, FormaElementar> mapa) {
        reSetBounds();
        BringToFront();
        return true;
    }

    /**
     * Dado um objeto que não pode ser apagado por conta de uma dependência, o método AskToDelete pede para que a dependência seja desfeita (caso em que importará o retorno do método: true = a
     * dependência foi desfeita), ou que o objeto providencie a sua auto-exclusão, caso em que o retorno seria irrelevante
     *
     * @return
     */
    public boolean AskToDelete() {
        return false;
    }

    /**
     * Para ser executado pelo Inspector.
     *
     * @param idx: tag ou índice do componente a ser removido.
     */
    public void ExcluirSubItem(int idx) {

    }

    /**
     * Para ser executado pelo Inspector.
     *
     * @param idx: posição onde o subitem será incluído.
     */
    public void AdicionarSubItem(int idx) {

    }

    //<editor-fold defaultstate="collapsed" desc="Ancorador">
//    /**
//     * Quais botões âncoras (botões que ficam ao lado do artefato selecionado no diagrama) deverão ser mostrados.
//     */
//    private final ArrayList<Integer> ancorasCode = new ArrayList<>();

    /**
     * Quais botões âncoras (botões que ficam ao lado do artefato selecionado no diagrama) deverão ser mostrados.
     *
     * @return
     */
    public ArrayList<Integer> getAncorasCode() {
        Integer[] ancorasCode = new Integer[] {Ancorador.CODE_ANCORAR, Ancorador.CODE_DEL};
        return new ArrayList<>(Arrays.asList(ancorasCode));
    }

    /**
     * *
     * É chamado quando clica-se em um dos botões ancorados no artefato selecionado
     *
     * @param cod
     */
    public void runAncorasCode(int cod) {
        switch (cod) {
            case Ancorador.CODE_ANCORAR:
                setAncorado(!isAncorado());
                InvalidateArea();
                getMaster().PerformInspector();
                break;
            case Ancorador.CODE_DEL:
                getMaster().ClearSelect();
                getMaster().setSelecionado(this);
                getMaster().deleteSelecao();
                break;
        }
    }

    /**
     * *
     * É chamado quando um artefato é selecionado no diagrama
     *
     * @param c - diz o que desenhar para cada um dos botões de ancoragem.
     * @return o caminho para desenho da imagem: getMaster().getEditor().getControler().ImagemDeDiagrama.get( RETURN ).getImage()
     */
    public String WhatDrawOnAcorador(Integer c) {
        String res = "diagrama.ancordor.0.img";
        switch (c) {
            case Ancorador.CODE_ANCORAR:
                res = isAncorado() ? "diagrama.ancordor.0.0.img" : "diagrama.ancordor.0.img";
                break;
            case Ancorador.CODE_DEL:
                res = isAncorado() ? "diagrama.ancordor.1.img" : "diagrama.ancordor.1.img";
                break;
        }
        return res;
    }
    //</editor-fold>
    
}