/*
 * Copyright (c) 2016 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package jme3test.app;

import java.util.*;

import com.jme3.util.clone.*;

/**
 *
 *
 *  @author    Paul Speed
 */
public class TestCloner {
    
    public static void main( String... args ) {
        
        System.out.println("Clone test:");
        
        Cloner cloner = new Cloner();
        
        RegularObject ro = new RegularObject(42);
        System.out.println("Regular Object:" + ro);
        RegularObject roCloneLegacy = ro.clone();
        System.out.println("Regular Object Clone:" + roCloneLegacy);
        RegularObject roClone = cloner.clone(ro);
        System.out.println("cloner: Regular Object Clone:" + roClone);
 
        System.out.println("------------------------------------");
        System.out.println();
        
        cloner = new Cloner();       
        RegularSubclass rsc = new RegularSubclass(69, "test");
        System.out.println("Regular subclass:" + rsc);
        RegularSubclass rscCloneLegacy = (RegularSubclass)rsc.clone();
        System.out.println("Regular subclass Clone:" + rscCloneLegacy);
        RegularSubclass rscClone = cloner.clone(rsc);
        System.out.println("cloner: Regular subclass Clone:" + rscClone);
        
        System.out.println("------------------------------------");
        System.out.println();
 
        cloner = new Cloner();       
        Parent parent = new Parent("Foo", 34);
        System.out.println("Parent:" + parent);
        Parent parentCloneLegacy = parent.clone();
        System.out.println("Parent Clone:" + parentCloneLegacy);
        Parent parentClone = cloner.clone(parent);
        System.out.println("cloner: Parent Clone:" + parentClone);
         
        System.out.println("------------------------------------");
        System.out.println();
        
        cloner = new Cloner();
        GraphNode root = new GraphNode("root");
        GraphNode child1 = root.addLink("child1");
        GraphNode child2 = root.addLink("child2");        
        GraphNode shared = child1.addLink("shared");
        child2.addLink(shared);
        
        // Add a circular reference to get fancy
        shared.addLink(root);
        
        System.out.println("Simple graph:");
        root.dump("  ");
        
        GraphNode rootClone = cloner.clone(root);
        System.out.println("clone:");  
        rootClone.dump("  ");
        
        System.out.println("original:");
        root.dump("  ");
 
        GraphNode reclone = Cloner.deepClone(root);
        System.out.println("reclone:");  
        reclone.dump("  ");
        
        System.out.println("------------------------------------");
        System.out.println();
        cloner = new Cloner();
        
        ArrayHolder arrays = new ArrayHolder(5, 3, 7, 3, 7, 2, 1, 4);
        System.out.println("Array holder:" + arrays);       
        ArrayHolder arraysClone = cloner.clone(arrays);
        System.out.println("Array holder clone:" + arraysClone);       
 
           
        
    }
    
    public static class RegularObject implements Cloneable {
        protected int i;
        
        public RegularObject( int i ) {
            this.i = i;
        }
 
        public RegularObject clone() {
            try {
                return (RegularObject)super.clone();
            } catch( CloneNotSupportedException e ) {
                throw new RuntimeException(e);
            }
        }
        
        public String toString() {
            return getClass().getSimpleName() + "@" + System.identityHashCode(this) 
                    + "[i=" + i + "]";
        }
    }
    
    public static class RegularSubclass extends RegularObject {
        protected String name;
        
        public RegularSubclass( int i, String name ) {
            super(i);
            this.name = name;
        }
               
        public String toString() {
            return getClass().getSimpleName() + "@" + System.identityHashCode(this) 
                    + "[i=" + i + ", name=" + name + "]";
        }
    }
    
    public static class Parent implements Cloneable, JmeCloneable {
        
        private RegularObject ro;
        private RegularSubclass rsc;
        
        public Parent( String name, int age ) {
            this.ro = new RegularObject(age);
            this.rsc = new RegularSubclass(age, name);
        }
        
        public Parent clone() {
            try {
                return (Parent)super.clone();
            } catch( CloneNotSupportedException e ) {
                throw new RuntimeException(e);
            }
        }
        
        public Parent jmeClone() {
            // Ok to delegate to clone() in this case because no deep
            // cloning is done there.
            return clone();
        }
 
        public void cloneFields( Cloner cloner, Object original ) {
            this.ro = cloner.clone(ro);
            this.rsc = cloner.clone(rsc);
        } 
        
        public String toString() {
            return getClass().getSimpleName() + "@" + System.identityHashCode(this)
                    + "[ro=" + ro + ", rsc=" + rsc + "]";
        }
    }
    
    public static class GraphNode implements Cloneable, JmeCloneable {
 
        private String name;       
        private List<GraphNode> links = new ArrayList<>();
        
        public GraphNode( String name ) {
            this.name = name;
        }
        
        public void dump( String indent ) {
            dump(indent, new HashSet<GraphNode>());        
        }
        
        private void dump( String indent, Set<GraphNode> visited ) {
            if( visited.contains(this) ) {
                // already been here
                System.out.println(indent + this + " ** circular.");
                return;
            } 
            System.out.println(indent + this);
            visited.add(this);
            for( GraphNode n : links ) {
                n.dump(indent + "    ", visited);
            }
            visited.remove(this);
        }
        
        public GraphNode addLink( String name ) {
            GraphNode node = new GraphNode(name);
            links.add(node);
            return node;
        }
        
        public GraphNode addLink( GraphNode node ) {
            links.add(node);
            return node;
        }
        
        public List<GraphNode> getLinks() {
            return links;
        }
        
        public GraphNode jmeClone() {
            try {
                return (GraphNode)super.clone();
            } catch( CloneNotSupportedException e ) {
                throw new RuntimeException(e);
            }
        }
 
        public void cloneFields( Cloner cloner, Object original ) {
            this.links = cloner.clone(links);
        } 
        
        public String toString() {
            return getClass().getSimpleName() + "@" + System.identityHashCode(this)
                    + "[name=" + name + "]";
        }
    }
    
    public static class ArrayHolder implements JmeCloneable {
    
        private int[] intArray;
        private int[][] intArray2D;
        private Object[] objects;
        private RegularObject[] regularObjects;
        private String[] strings;
 
        public ArrayHolder( int... values ) {
            this.intArray = values;
            this.intArray2D = new int[values.length][2];
            for( int i = 0; i < values.length; i++ ) {
                intArray2D[i][0] = values[i] + 1;
                intArray2D[i][1] = values[i] * 2;
            }
            this.objects = new Object[values.length];
            this.regularObjects = new RegularObject[values.length];
            this.strings = new String[values.length];
            for( int i = 0; i < values.length; i++ ) {
                objects[i] = values[i];
                regularObjects[i] = new RegularObject(values[i]);
                strings[i] = String.valueOf(values[i]);   
            }
        }
        
        public ArrayHolder jmeClone() {
            try {
                return (ArrayHolder)super.clone();
            } catch( CloneNotSupportedException e ) {
                throw new RuntimeException(e);
            }
        }
 
        public void cloneFields( Cloner cloner, Object original ) {
            intArray = cloner.clone(intArray);
            intArray2D = cloner.clone(intArray2D);
            
            // Boxed types are not cloneable so this will fail
            //objects = cloner.clone(objects);
            
            regularObjects = cloner.clone(regularObjects);
            
            // Strings are also not cloneable
            //strings = cloner.clone(strings);
        }
        
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("intArray=" + intArray);
            for( int i = 0; i < intArray.length; i++ ) {
                if( i == 0 ) {
                    sb.append("[");
                } else {
                    sb.append(", ");
                }
                sb.append(intArray[i]);
            }
            sb.append("], ");
            
            sb.append("intArray2D=" + intArray2D);
            for( int i = 0; i < intArray2D.length; i++ ) {
                if( i == 0 ) {
                    sb.append("[");
                } else {
                    sb.append(", ");
                }
                sb.append("intArray2D[" + i + "]=" + intArray2D[i]);
                for( int j = 0; j < 2; j++ ) {
                    if( j == 0 ) {
                        sb.append("[");
                    } else {
                        sb.append(", ");
                    }
                    sb.append(intArray2D[i][j]);                    
                }
                sb.append("], ");
            }
            sb.append("], ");
            
            sb.append("objectArray=" + objects);
            for( int i = 0; i < objects.length; i++ ) {
                if( i == 0 ) {
                    sb.append("[");
                } else {
                    sb.append(", ");
                }
                sb.append(objects[i]);
            }
            sb.append("], ");
            
            sb.append("objectArray=" + regularObjects);
            for( int i = 0; i < regularObjects.length; i++ ) {
                if( i == 0 ) {
                    sb.append("[");
                } else {
                    sb.append(", ");
                }
                sb.append(regularObjects[i]);
            }
            sb.append("], ");
            
            sb.append("stringArray=" + strings);
            for( int i = 0; i < strings.length; i++ ) {
                if( i == 0 ) {
                    sb.append("[");
                } else {
                    sb.append(", ");
                }
                sb.append(strings[i]);
            }
            sb.append("]");
            
            return getClass().getSimpleName() + "@" + System.identityHashCode(this)
                    + "[" + sb + "]";
        }       
    }
}
