/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.clerezza.rdf.core.impl;

import java.math.BigInteger;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.clerezza.rdf.core.InvalidLiteralTypeException;
import org.apache.clerezza.rdf.core.LiteralFactory;
import org.apache.clerezza.rdf.core.NoConvertorException;
import org.apache.clerezza.rdf.core.TypedLiteral;
import org.apache.clerezza.rdf.core.UriRef;
import org.apache.clerezza.rdf.core.impl.util.Base64;
import org.apache.clerezza.rdf.core.impl.util.W3CDateFormat;

/**
 * An implementation of literal factory currently supporting only
 * byte[]/base64Binary and Java.util.Date/date
 * 
 * @author reto
 */

public class SimpleLiteralFactory extends LiteralFactory {

    private static final String XSD = "http://www.w3.org/2001/XMLSchema#";
    final private static UriRef xsdInteger = xsd("integer");
    final private static UriRef xsdInt = xsd("int");
    final private static UriRef xsdShort = xsd("short");
    final private static UriRef xsdByte = xsd("byte");
    final private static UriRef xsdLong = xsd("long");
    

    final private static Set<UriRef> decimalTypes = new HashSet<UriRef>();

    final private static Map<Class<?>, TypeConverter<?>> typeConverterMap = new HashMap<Class<?>, TypeConverter<?>>();
    final static Class<? extends byte[]> byteArrayType;

    static {
        Collections.addAll(decimalTypes, xsdInteger, xsdInt, xsdByte, xsdShort, xsdLong );

        byte[] byteArray = new byte[0];
        byteArrayType = byteArray.getClass();
        typeConverterMap.put(byteArrayType, new ByteArrayConverter());
        typeConverterMap.put(Date.class, new DateConverter());
        typeConverterMap.put(Boolean.class, new BooleanConverter());
        typeConverterMap.put(String.class, new StringConverter());
        typeConverterMap.put(Integer.class, new IntegerConverter());
        typeConverterMap.put(BigInteger.class, new BigIntegerConverter());
        typeConverterMap.put(Long.class, new LongConverter());
        typeConverterMap.put(Double.class, new DoubleConverter());
        typeConverterMap.put(Float.class, new FloatConverter());
        typeConverterMap.put(UriRef.class, new UriRefConverter());
    }

    final private static UriRef xsdDouble =xsd("double");
    final private static UriRef xsdFloat =xsd("float");
    final private static UriRef xsdAnyURI =xsd("anyURI");

    final private static UriRef xsd(String name) {
       return new UriRef(XSD+name);
    }

    private static interface TypeConverter<T> {
        TypedLiteral createTypedLiteral(T value);
        T createObject(TypedLiteral literal);        
    }

    private static class  ByteArrayConverter implements TypeConverter<byte[]> {

        private static final UriRef base64Uri =xsd("base64Binary");

        @Override
        public TypedLiteral createTypedLiteral(byte[] value) {
            return new TypedLiteralImpl(Base64.encode((byte[]) value), base64Uri);
        }

        @Override
        public byte[] createObject(TypedLiteral literal) {
            if (!literal.getDataType().equals(base64Uri)) {
                throw new InvalidLiteralTypeException(byteArrayType, literal.getDataType());
            }
            return (byte[])Base64.decode(literal.getLexicalForm());
        }

        
    }
    private static class  DateConverter implements TypeConverter<Date> {

        private static final UriRef dateTimeUri =xsd("dateTime");
        private static final DateFormat DATE_FORMAT = new W3CDateFormat();

        @Override
        public TypedLiteral createTypedLiteral(Date value) {
            return new TypedLiteralImpl(DATE_FORMAT.format(value), dateTimeUri);
        }

        @Override
        public Date createObject(TypedLiteral literal) {
            if (!literal.getDataType().equals(dateTimeUri)) {
                throw new InvalidLiteralTypeException(Date.class, literal.getDataType());
            }
            try {
                return DATE_FORMAT.parse(literal.getLexicalForm());
            } catch (ParseException ex) {
                throw new RuntimeException("Exception parsing literal as date", ex);
            }
        }


    }

    private static class BooleanConverter implements TypeConverter<Boolean> {

        private static final UriRef booleanUri =xsd("boolean");
        public static final TypedLiteralImpl TRUE = new TypedLiteralImpl("true", booleanUri);
        public static final TypedLiteralImpl FALSE = new TypedLiteralImpl("false", booleanUri);

        @Override
        public TypedLiteral createTypedLiteral(Boolean value) {
            if (value) return TRUE;
            else return FALSE;
        }

        @Override
        public Boolean createObject(TypedLiteral literal) {
            if (literal == TRUE) return true;
            else if (literal == FALSE) return false;
            else if (!literal.getDataType().equals(booleanUri)) {
                throw new InvalidLiteralTypeException(Boolean.class, literal.getDataType());
            }
            return Boolean.valueOf(literal.getLexicalForm());
        }
    }

    private static class StringConverter implements TypeConverter<String> {

        private static final UriRef stringUri =xsd("string");

        @Override
        public TypedLiteral createTypedLiteral(String value) {
            return new TypedLiteralImpl(value, stringUri);
        }

        @Override
        public String createObject(TypedLiteral literal) {
            if (!literal.getDataType().equals(stringUri)) {
                throw new InvalidLiteralTypeException(String.class, literal.getDataType());
            }
            return literal.getLexicalForm();
        }
    }

    private static class IntegerConverter implements TypeConverter<Integer> {


        @Override
        public TypedLiteral createTypedLiteral(Integer value) {
            return new TypedLiteralImpl(value.toString(), xsdInt);
        }

        @Override
        public Integer createObject(TypedLiteral literal) {
            if (!decimalTypes.contains(literal.getDataType())) {
                throw new InvalidLiteralTypeException(Integer.class, literal.getDataType());
            }
            return new Integer(literal.getLexicalForm());
        }
    }

    private static class LongConverter implements TypeConverter<Long> {

        

        @Override
        public TypedLiteral createTypedLiteral(Long value) {
            return new TypedLiteralImpl(value.toString(), xsdLong);
        }

        @Override
        public Long createObject(TypedLiteral literal) {
            if (!decimalTypes.contains(literal.getDataType())) {
                throw new InvalidLiteralTypeException(Long.class, literal.getDataType());
            }
            return new Long(literal.getLexicalForm());
        }
    }


    private static class FloatConverter implements TypeConverter<Float> {

        @Override
        public TypedLiteral createTypedLiteral(Float value) {
            return new TypedLiteralImpl(value.toString(), xsdFloat);
        }

        @Override
        public Float createObject(TypedLiteral literal) {
            if (!literal.getDataType().equals(xsdFloat)) {
                throw new InvalidLiteralTypeException(Float.class, literal.getDataType());
            }
            return Float.valueOf(literal.getLexicalForm());
        }
    }
    
    private static class DoubleConverter implements TypeConverter<Double> {



        @Override
        public TypedLiteral createTypedLiteral(Double value) {
            return new TypedLiteralImpl(value.toString(), xsdDouble);
        }

        @Override
        public Double createObject(TypedLiteral literal) {
            if (!literal.getDataType().equals(xsdDouble)) {
                throw new InvalidLiteralTypeException(Double.class, literal.getDataType());
            }
            return new Double(literal.getLexicalForm());
        }
    }

    private static class BigIntegerConverter implements TypeConverter<BigInteger> {



        @Override
        public TypedLiteral createTypedLiteral(BigInteger value) {
            return new TypedLiteralImpl(value.toString(), xsdInteger);
        }

        @Override
        public BigInteger createObject(TypedLiteral literal) {
            if (!literal.getDataType().equals(xsdInteger)) {
                throw new InvalidLiteralTypeException(Double.class, literal.getDataType());
            }
            return new BigInteger(literal.getLexicalForm());
        }
    }
    
    private static class UriRefConverter implements TypeConverter<UriRef> {



        @Override
        public TypedLiteral createTypedLiteral(UriRef value) {
            return new TypedLiteralImpl(value.getUnicodeString(), xsdAnyURI);
        }

        @Override
        public UriRef createObject(TypedLiteral literal) {
            if (!literal.getDataType().equals(xsdAnyURI)) {
                throw new InvalidLiteralTypeException(UriRef.class, literal.getDataType());
            }
            return new UriRef(literal.getLexicalForm());
        }
    }


    @SuppressWarnings("unchecked")
    @Override
    public TypedLiteral createTypedLiteral(Object value) throws NoConvertorException {
        TypeConverter converter = getConverterFor(value.getClass());
        return converter.createTypedLiteral(value);
    }

    
    
    @Override
    public <T> T createObject(Class<T> type, TypedLiteral literal)
            throws NoConvertorException, InvalidLiteralTypeException {
        final TypeConverter<T> converter = getConverterFor(type);
        return converter.createObject(literal);
        
    }

    @SuppressWarnings("unchecked")
    private <T> TypeConverter<T> getConverterFor(Class<T> type) throws NoConvertorException {
        TypeConverter<T> convertor = (TypeConverter<T>) typeConverterMap.get(type);
        if (convertor != null) {
            return convertor;
        }
        for (Map.Entry<Class<?>, TypeConverter<?>> converterEntry : typeConverterMap.entrySet()) {
            if (type.isAssignableFrom(converterEntry.getKey())) {
                return (TypeConverter<T>) converterEntry.getValue();
            }
        }
        throw new NoConvertorException(type);
    }
}
