/*
 * Copyright 2005 JBoss Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.drools.guvnor.client.rulefloweditor;

import org.cobogw.gwt.user.client.ui.RoundedPanel;
import org.drools.guvnor.client.rulefloweditor.RuleFlowBaseNode.Corners;
import org.drools.guvnor.client.rulefloweditor.TransferNode.Type;

import pl.balon.gwt.diagrams.client.connection.Connection;

import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;

public class RuleFlowNodeFactory {

    public static RuleFlowBaseNode createNode(TransferNode tn) {

        RuleFlowBaseNode n;

        if ( tn.getType() == Type.START ) {

            n = new StartNode();

        } else if ( tn.getType() == Type.HUMANTASK ) {

            n = createHumanTask( (HumanTaskTransferNode) tn );

        } else if ( tn.getType() == Type.JOIN ) {

            n = new JoinNode();

        } else if ( tn.getType() == Type.SUB_PROCESS ) {

            n = new SubProcessNode();

        } else if ( tn.getType() == Type.MILESTONE ) {

            n = new MileStoneNode();

        } else if ( tn.getType() == Type.TIMER ) {

            n = new TimerNode();

        } else if ( tn.getType() == Type.ACTION_NODE ) {

            n = new ActionNode();

        } else if ( tn.getType() == Type.WORK_ITEM ) {

            n = createWorkItemNode( (WorkItemTransferNode) tn );

        } else if ( tn.getType() == Type.RULESET ) {

            n = new RuleSetNode();

        } else if ( tn.getType() == Type.SPLIT ) {

            n = createSplit( (SplitTransferNode) tn );

        } else if ( tn.getType() == Type.FOR_EACH ) {

            n = createElementContainerNode( new ForEachNode(),
                                            (ElementContainerTransferNode) tn );

        } else if ( tn.getType() == Type.FAULT ) {

            n = new FaultNode();

        } else if ( tn.getType() == Type.EVENT ) {

            n = new EventNode();

        } else if ( tn.getType() == Type.COMPOSITE ) {

            n = createElementContainerNode( new CompositeNode(),
                                            (ElementContainerTransferNode) tn );

        } else if ( tn.getType() == Type.END ) {

            n = new EndNode();

        } else {

            throw new IllegalArgumentException( "Unkown node type " + tn );

        }

        fillRuleFlowBaseNode( n,
                              tn );

        return n;
    }

    private static RuleFlowBaseNode createElementContainerNode(ElementContainerNode node,
                                                               ElementContainerTransferNode tn) {

        for ( TransferNode subNode : tn.getContentModel().getNodes() ) {
            RuleFlowBaseNode baseNode = createNode( subNode );

            node.getNodes().put( baseNode.getId(),
                                 baseNode );
        }

        for ( TransferConnection c : tn.getContentModel().getConnections() ) {
            try {
                node.getConnections().add( RuleFlowConnectionFactory.createConnection( c,
                                                                                       node.getNodes() ) );
            } catch ( RuntimeException e ) {
                // TODO: handle exception
            }
        }

        return node;
    }

    private static RuleFlowBaseNode createWorkItemNode(WorkItemTransferNode node) {

        WorkItemNode workItemNode = null;

        if ( node.getWorkName().equals( WorkItemNode.EMAIL ) ) {

            workItemNode = new EmailNode();

        } else if ( node.getWorkName().equals( WorkItemNode.LOG ) ) {

            workItemNode = new LogNode();

        } else {

            workItemNode = new DefaultWorkItemNode();

        }

        workItemNode.setAttributes( node.getParameters() );

        return workItemNode;
    }

    private static RuleFlowBaseNode createSplit(SplitTransferNode tn) {

        SplitNode splitNode = new SplitNode();

        splitNode.type = tn.getSplitType();

        splitNode.constraints = tn.getConstraints();

        return splitNode;
    }

    private static RuleFlowBaseNode createHumanTask(HumanTaskTransferNode node) {
        HumanTaskNode n = new HumanTaskNode();

        n.setAttributes( node.getParameters() );

        return n;
    }

    private static void fillRuleFlowBaseNode(RuleFlowBaseNode node,
                                             TransferNode tn) {
        fillIdAndCoordinates( node,
                              tn );

        Widget panel;
        if ( node.getImagePath() == null ) {
            panel = createContentWithoutImage( tn.getName(),
                                               node,
                                               tn.getWidth() );
        } else {
            panel = createContentWithImage( tn.getName(),
                                            node,
                                            tn.getWidth() );
        }

        if ( node.getCorners() == Corners.ROUNDED ) {
            int cornerHeight = LayoutCalculator.calculateCornerHeight( tn.getWidth() );
            panel.setHeight( LayoutCalculator.calculateNodeHeight( tn.getHeight(),
                                                                   cornerHeight ) + "px" );
            RuleFlowRoundedPanel rp = new RuleFlowRoundedPanel( RoundedPanel.ALL,
                                                                RuleFlowBaseNode.CORNER_HEIGHT );

            rp.setTopAndBottomRowColor( "Black" );
            rp.setCornerStyleName( node.getStyle() );

            rp.add( panel );
            node.add( rp );

        } else if ( node.getCorners() == Corners.ROUND ) {
            int cornerHeight = LayoutCalculator.calculateRoundCornerHeight( tn.getWidth() );
            panel.setHeight( "1px" );

            RuleFlowRoundPanel rp = new RuleFlowRoundPanel( cornerHeight );

            rp.setTopAndBottomRowColor( "Black" );
            rp.setCornerStyleName( node.getStyle() );

            rp.add( panel );
            node.add( rp );

        } else {
            panel.setHeight( tn.getHeight() + "px" );

            if ( node instanceof ElementContainerNode ) {

                // Add nodes that are in for each node 
                AbsolutePanel ap = new AbsolutePanel();

                ElementContainerNode fen = (ElementContainerNode) node;

                for ( RuleFlowBaseNode baseNode : fen.getNodes().values() ) {
                    ap.add( baseNode,
                            baseNode.getX(),
                            baseNode.getY() );
                }

                // Add connections that are in for each node
                for ( Connection c : fen.getConnections() ) {
                    c.appendTo( ap );
                }

                ap.add( panel );

                node.add( ap );

            } else {
                node.add( panel );
            }
        }
    }

    private static Widget createContentWithoutImage(String name,
                                                    RuleFlowBaseNode node,
                                                    int width) {
        Label label = new Label( name );

        label.setStyleName( node.getStyle() );

        label.setWidth( width + "px" );

        return label;
    }

    private static Widget createContentWithImage(String name,
                                                 RuleFlowBaseNode node,
                                                 int width) {
        Label label = new Label( name );

        Image image = new Image();
        image.setStyleName( RuleFlowBaseNode.IMAGE_STYLE );
        image.setResource( node.getImagePath() );

        HorizontalPanel panel = new HorizontalPanel();

        panel.setStyleName( node.getStyle() );

        panel.add( image );
        panel.add( label );
        panel.setWidth( width + "px" );

        return panel;
    }

    protected static void fillIdAndCoordinates(RuleFlowBaseNode node,
                                               TransferNode tn) {

        node.addClickHandler( node );

        node.setId( tn.getId() );
        node.setX( tn.getX() );
        node.setY( tn.getY() );
    }

    protected static class LayoutCalculator {
        /**
         * Subtracts the border height from nodes height.
         * 
         * @return node height
         */
        static int calculateNodeHeight(int height,
                                       int cornerHeight) {
            int newHeight = 0;

            if ( height < (2 * cornerHeight) ) {
                newHeight = height;
            } else {
                newHeight = height - (2 * cornerHeight);
            }

            return newHeight;
        }

        /**
         * Calculates the corner height. If the width is too short, adding the
         * corner divs go over the node edges.
         * 
         * @param width
         * @return The fixed corner height
         */
        static int calculateCornerHeight(int width) {
            int newWidth = 1;

            if ( width < RuleFlowBaseNode.CORNER_HEIGHT ) {
                newWidth = width - 4;
            } else if ( width <= 20 ) {
                newWidth = 6;
            } else {
                newWidth = RuleFlowBaseNode.CORNER_HEIGHT;
            }

            if ( newWidth <= 0 ) {
                return 1;
            } else {
                return newWidth;
            }
        }

        static int calculateRoundCornerHeight(int width) {
            if ( width < 44 ) {
                return width / 2 - 1;
            } else {
                return 16;
            }
        }
    }
}
