/**
 * Copyright (c) 2014-2016 by the respective copyright holders.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package org.eclipse.smarthome.storage.json;

import java.io.IOException;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.Map;

import com.google.gson.Gson;
import com.google.gson.InstanceCreator;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.internal.JsonReaderInternalAccess;
import com.google.gson.internal.bind.MapTypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;

/**
 * Type adapter that makes sure that all Numeric values in Maps of type
 * Map<String, Object> are deserialized as BigDecimal instances instead of
 * doubles.
 *
 * @author Ivan Iliev
 *
 */
public class PropertiesTypeAdapter extends TypeAdapter<Map<String, Object>> {

    public static final TypeToken<Map<String, Object>> TOKEN = new TypeToken<Map<String, Object>>() {
    };

    private final TypeAdapter<Map<String, Object>> delegate;

    private final ConstructorConstructor constructor;

    private final TypeAdapter<String> keyAdapter;

    private final TypeAdapter<Object> valueAdapter;

    public PropertiesTypeAdapter(Gson gson) {
        // obtain the default type adapters for String and Object classes
        keyAdapter = gson.getAdapter(String.class);
        valueAdapter = gson.getAdapter(Object.class);

        // obtain default gson objects
        constructor = new ConstructorConstructor(Collections.<Type, InstanceCreator<?>> emptyMap());
        delegate = new MapTypeAdapterFactory(constructor, false).create(new Gson(), TOKEN);
    }

    @Override
    public void write(JsonWriter out, Map<String, Object> value) throws IOException {
        // write remains unchanged
        delegate.write(out, value);
    }

    @Override
    public Map<String, Object> read(JsonReader in) throws IOException {
        // gson implementation code is modified when deserializing numbers
        JsonToken peek = in.peek();
        if (peek == JsonToken.NULL) {
            in.nextNull();
            return null;
        }

        Map<String, Object> map = constructor.get(TOKEN).construct();

        if (peek == JsonToken.BEGIN_ARRAY) {
            in.beginArray();
            while (in.hasNext()) {
                in.beginArray(); // entry array
                String key = keyAdapter.read(in);

                // modification
                Object value = getValue(in);

                Object replaced = map.put(key, value);
                if (replaced != null) {
                    throw new JsonSyntaxException("duplicate key: " + key);
                }
                in.endArray();
            }
            in.endArray();
        } else {
            in.beginObject();
            while (in.hasNext()) {
                JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
                String key = keyAdapter.read(in);

                // modification
                Object value = getValue(in);

                Object replaced = map.put(key, value);
                if (replaced != null) {
                    throw new JsonSyntaxException("duplicate key: " + key);
                }
            }
            in.endObject();
        }
        return map;
    }

    private Object getValue(JsonReader in) throws IOException {
        Object value = null;

        // if the next json token is a number we read it as a BigDecimal,
        // otherwise use the default adapter to read it
        if (JsonToken.NUMBER.equals(in.peek())) {
            value = new BigDecimal(in.nextString());
        } else {
            value = valueAdapter.read(in);
        }

        return value;

    }

}

@@ -1946,17 +1946,16 @@
 ator%3C?%3E%3E
- 
 emptyMap
@@ -3995,48 +3995,232 @@
     
-value = new BigDecimal(in.nextString());
+String inString = in.nextString();%0A            if (inString.endsWith(%22.0%22)) %7B%0A                value = new BigDecimal(inString).setScale(0);%0A            %7D else %7B%0A                value = new BigDecimal(inString);%0A            %7D
 %0A   

Set BigDecimal scale to 0 if value ends with x.0 (#2685)