// Copyright 2008-2012 severally by the contributors
//
// 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 net.sf.practicalxml.converter.bean;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

import org.w3c.dom.Element;

import junit.framework.Assert;

import net.sf.practicalxml.DomUtil;
import net.sf.practicalxml.converter.AbstractConversionTestCase;
import net.sf.practicalxml.converter.ConversionConstants;
import net.sf.practicalxml.converter.internal.ConversionUtils;


/**
 *  Provides common support code (primary test bean classes) for the converter
 *  testcases. Note that the bean classes are public, so that they can be
 *  instrospected.
 */
public abstract class AbstractBeanConverterTestCase
extends AbstractConversionTestCase
{
    protected AbstractBeanConverterTestCase(String name)
    {
        super(name);
    }


//----------------------------------------------------------------------------
//  Primitive Test Data -- single tests will run through an array of values
//----------------------------------------------------------------------------

    protected static class PrimitiveValue
    {
        private Class<?> _klass;
        private Object _value;
        private String _xsdType;
        private String _xsdText;

        public PrimitiveValue(Object value, String xsdType, String xsdText)
        {
            _klass = value.getClass();
            _value = value;
            _xsdType = xsdType;
            _xsdText = xsdText;
        }

        public Class<?> getKlass()      { return _klass; }
        public Object getValue()        { return _value; }
        public String getXsdType()      { return _xsdType; }
        public String getXsdText()      { return _xsdText; }
        public String getDefaultText()  { return String.valueOf(getValue()); }
    }


    protected static PrimitiveValue[] PRIMITIVE_VALUES = new PrimitiveValue[]
    {
        new PrimitiveValue("testing 123",                   "xsd:string",   "testing 123"),
        new PrimitiveValue(Character.valueOf('A'),          "xsd:string",   "A"),
        new PrimitiveValue(Boolean.TRUE,                    "xsd:boolean",  "true"),
        new PrimitiveValue(Boolean.FALSE,                   "xsd:boolean",  "false"),
        new PrimitiveValue(Byte.valueOf((byte)123),         "xsd:byte",     "123"),
        new PrimitiveValue(Short.valueOf((short)4567),      "xsd:short",    "4567"),
        new PrimitiveValue(Integer.valueOf(12345678),       "xsd:int",      "12345678"),
        new PrimitiveValue(Long.valueOf(12345678901234L),   "xsd:long",     "12345678901234"),
        new PrimitiveValue(Float.valueOf((float)1234),      "xsd:decimal",  "1234.0"),
        new PrimitiveValue(Double.valueOf(1234567890.5),    "xsd:decimal",  "1234567890.5"),
        new PrimitiveValue(new BigInteger("123456789012345"),                   "xsd:decimal", "123456789012345"),
        new PrimitiveValue(new BigDecimal("123456789012345.123456789012345"),   "xsd:decimal", "123456789012345.123456789012345"),
    };


//----------------------------------------------------------------------------
//  Non-primitive test data
//----------------------------------------------------------------------------

    // note that we override toString() to return something different from name()
    public enum MyEnum
    {
        FOO, BAR, BAZ;

        @Override
        public String toString()
        {
            return name().toLowerCase();
        }
    }


    public static class SimpleBean
    {
        private String _sval;
        private int _ival;
        private BigDecimal _dval;
        private boolean _bval;

        public SimpleBean()
        {
            // nothign to see here
        }

        public SimpleBean(String sval, int ival, BigDecimal dval, boolean bval)
        {
            _sval = sval;
            _ival = ival;
            _dval = dval;
            _bval = bval;
        }

        public String getSval()                 { return _sval; }
        public void setSval(String sval)        { _sval = sval; }

        public int getIval()                    { return _ival; }
        public void setIval(int ival)           { _ival = ival; }

        public BigDecimal getDval()             { return _dval; }
        public void setDval(BigDecimal dval)    { _dval = dval; }

        public boolean isBval()                 { return _bval; }
        public void setBval(boolean bval)       { _bval = bval; }

        public void assertEquals(SimpleBean that)
        {
            assertNotNull(that);
            Assert.assertEquals("sval", _sval, that._sval);
            Assert.assertEquals("ival", _ival, that._ival);
            Assert.assertEquals("dval", _dval, that._dval);
            Assert.assertEquals("bval", _bval, that._bval);
        }
    }


    public static class CompoundBean
    {
        private SimpleBean _simple;
        private int[] _primArray;
        private List<String> _stringList;

        public CompoundBean()
        {
            // nothing here
        }

        public CompoundBean(SimpleBean simple, int[] primArray, List<String> stringList)
        {
            super();
            _simple = simple;
            _primArray = primArray;
            _stringList = stringList;
        }

        public SimpleBean getSimple()                   { return _simple; }
        public void setSimple(SimpleBean simple)        { _simple = simple; }

        public int[] getPrimArray()                     { return _primArray; }
        public void setPrimArray(int[] primArray)       { _primArray = primArray; }

        public List<String> getStringList()             { return _stringList; }
        public void setStringList(List<String> list)    { _stringList = list; }

        public void assertEquals(CompoundBean that)
        {
            _simple.assertEquals(that._simple);
            Assert.assertTrue("primArray", Arrays.equals(_primArray, that._primArray));
            Assert.assertEquals("stringlist", _stringList, that._stringList);
        }
    }


    // used to test a failure that was fixed with release 1.1.10
    // technically, this is an invalid bean, but we shouldn't fail ...
    public static class BeanWithDifferentGetterAndSetter
    {
        public int _val;

        public BeanWithDifferentGetterAndSetter()
        {
            /* default, per bean spec */
        }

        public BeanWithDifferentGetterAndSetter(int init)
        {
            _val = init;
        }

        public int getValue()           { return _val; }
        public void setVal(int value)   { _val = value; }
    }


    public static class ReadOnlyBean
    {
        private String _sval;
        public String getSval() { return _sval; }
    }


    // added in 1.1.10; these classes previously caused exceptions
    public static class StringableBean
    {
        private Class<?> _klass;                // infinite recursion
        private TimeZone _zone;                 // out of memory

        public Class<?> getKlass()              { return _klass; }
        public void setKlass(Class<?> value)    { _klass = value; }

        public TimeZone getZone()               { return _zone; }
        public void setZone(TimeZone value)     { _zone = value; }
    }


    // added in 1.1.10
    public static class CalendarBean
    {
        private Calendar _value;

        public CalendarBean()                   { /* default ctor */ }
        public CalendarBean(Calendar value)     { _value = value; }

        public Calendar getValue()              { return _value; }
        public void setValue(Calendar value)    { _value = value; }
    }


    // added in 1.1.14
    public static class CircularBean1
    {
        private String _parentData;
        private CircularBean2 _ref;

        public CircularBean1(String value)          { _parentData = value; }

        public String getParentData()               { return _parentData; }
        public void setParentData(String value)     { _parentData = value; }

        public CircularBean2 getRef()               { return _ref; }
        public void setRef(CircularBean2 value)     { _ref = value; }
    }


    // added in 1.1.14
    public static class CircularBean2
    {
        private String _childData;
        private CircularBean1 _ref;

        public CircularBean2(String value)          { _childData = value; }

        public String getChildData()                { return _childData; }
        public void setChildData(String value)      { _childData = value; }

        public CircularBean1 getRef()               { return _ref; }
        public void setRef(CircularBean1 value)     { _ref = value; }
    }


    // added in 1.1.14
    public static class SelfReferencingBean
    {
        private String _data;
        private SelfReferencingBean _ref;

        public SelfReferencingBean(String value)        { _data = value; _ref = this; }

        public String getData()                         { return _data; }
        public void setData(String value)               { _data = value; }

        public SelfReferencingBean getRef()             { return _ref; }
        public void setRef(SelfReferencingBean value)   { _ref = value; }
    }


    // added in 1.1.15
    public static class ThrowingBean
    {
        private String _data;

        public ThrowingBean()                           { /* nothing here */ }
        public ThrowingBean(String value)               { _data = value; }

        public String getData()                         { return _data; }
        public void setData(String value)               { _data = value; }

        public String getThrow1()                       { throw new IllegalArgumentException("foo"); }
        public void setThrow1(String value)             { throw new IllegalArgumentException("bar"); }

        public String getThrow2()                       { throw new IllegalStateException("foo"); }
        public void setThrow2(String value)             { throw new IllegalStateException("bar"); }
    }


    // added in 1.1.15
    public static class NonInstantiableBean
    {
        private String _data;

        // missing no-args ctor
        public NonInstantiableBean(String value)        { _data = value; }

        public String getData()                         { return _data; }
        public void setData(String value)               { _data = value; }
    }


    // added in 1.1.16, used only in DateBean
    public static class MyDate extends Date
    {
        private static final long serialVersionUID = 1L;

        public MyDate(long timestamp)
        {
            super(timestamp);
        }
    }


    // added in 1.1.16
    public static class DateBean
    {
        private java.util.Date _date;
        private java.sql.Date _sqlDate;
        private java.sql.Time _sqlTime;
        private java.sql.Timestamp _timestamp;
        private MyDate _myDate;

        public DateBean()                                   { /* required for XML->Bean conversion */ }

        public DateBean(Date date)
        {
            _date = date;
            _sqlDate = new java.sql.Date(date.getTime());
            _sqlTime = new java.sql.Time(date.getTime());
            _timestamp = new java.sql.Timestamp(date.getTime());
            _myDate = new MyDate(date.getTime());
        }

        public java.util.Date getDate()                     {  return _date; }
        public void setDate(java.util.Date value)           { _date = value; }

        public java.sql.Date getSqlDate()                   { return _sqlDate; }
        public void setSqlDate(java.sql.Date value)         { _sqlDate = value; }

        public java.sql.Time getSqlTime()                   { return _sqlTime; }
        public void setSqlTime(java.sql.Time value)         { _sqlTime = value; }

        public java.sql.Timestamp getTimestamp()            { return _timestamp; }
        public void setTimestamp(java.sql.Timestamp value)  { _timestamp = value; }

        public MyDate getMyDate()                           { return _myDate; }
        public void setMyDate(MyDate value)                 { _myDate = value; }
    }


    // added in 1.1.17
    public static class EnumBean
    {
        private MyEnum _value;

        public EnumBean()                                   { /* required for XML->Bean conversion */ }
        public EnumBean(MyEnum value)                       { _value = value; }

        public MyEnum getValue()                            { return _value; }
        public void setValue(MyEnum value)                  { _value = value; }
    }


//----------------------------------------------------------------------------
//  Common Assertions
//----------------------------------------------------------------------------

    protected void assertPrimitiveElement(
            String message,
            Element elem,
            String expectedName,
            String expectedType,
            String expectedValue,
            boolean isNil)
    {
        assertName(message, elem, expectedName);
        assertValue(message, elem, expectedValue);
        assertType(message, elem, expectedType);
        assertXsiNil(message, elem, isNil);
    }


    protected void assertNameTypeValue(
        Element elem,
        String expectedName, String expectedType, String expectedValue)
    {
        assertNameTypeValue("", elem, expectedName, expectedType, expectedValue);
    }


    protected void assertNameTypeValue(
        String message, Element elem,
        String expectedName, String expectedType, String expectedValue)
    {
        if (message.length() > 0)
            message += " ";

        assertName(message + "name", elem, expectedName);
        assertType(message + "type", elem, expectedType);
        assertValue(message + "value", elem, expectedValue);
    }


    protected void assertName(String message, Element elem, String expectedName)
    {
        assertEquals(message, expectedName, DomUtil.getLocalName(elem));
    }


    protected void assertType(String message, Element elem, String expected)
    {
        assertEquals(message, expected, ConversionUtils.getAttribute(elem, "type"));
    }


    protected void assertValue(String message, Element elem, String expectedValue)
    {
        assertEquals(message, expectedValue, DomUtil.getText(elem));
    }
}
