/*
 * 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.empire.rest.json;

import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedHashMap;

import org.apache.empire.commons.ObjectUtils;
import org.apache.empire.data.Column;
import org.apache.empire.data.DataType;
import org.apache.empire.db.DBRecord;
import org.apache.empire.db.DBRecordData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.node.BooleanNode;
import com.fasterxml.jackson.databind.node.DecimalNode;
import com.fasterxml.jackson.databind.node.IntNode;
import com.fasterxml.jackson.databind.node.LongNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.NumericNode;
import com.fasterxml.jackson.databind.node.TextNode;

@JsonDeserialize(using = JsoRecordData.Deserializer.class)   
public class JsoRecordData extends LinkedHashMap<String, Object>
{
    private static final long serialVersionUID = 1L;

    private static final Logger log = LoggerFactory.getLogger(JsoRecordData.class);
    
    /**
     * Deserializer
     * @author doebele
     */
    public static class Deserializer extends StdDeserializer<JsoRecordData> { 
     
        private static final long serialVersionUID = 1L;
    
        public Deserializer() { 
            this(null); 
        } 
     
        public Deserializer(Class<?> vc) { 
            super(vc); 
        }
     
        @Override
        public JsoRecordData deserialize(JsonParser jp, DeserializationContext ctxt) 
          throws IOException, JsonProcessingException {
            // read and parse
            JsonNode node = jp.getCodec().readTree(jp);
            return new JsoRecordData(node);
        }
    }
    
    /**
     * Serialize to JSON
     * @param rec
     */
    public JsoRecordData(DBRecordData rec)
    {
        super(rec.getFieldCount());
        for (int i=0; i<rec.getFieldCount(); i++)
        {
            String prop = rec.getColumnExpr(i).getBeanPropertyName();
            if (prop==null)
                continue;
            put(prop, rec.getValue(i));
        }
    }

    public JsoRecordData(DBRecord rec)
    {
        this((DBRecordData)rec);
        // add new flag
        put("_newRecord", rec.isNew());
    }
    
    public JsoRecordData(JsoColumnMeta[] meta)
    {
        super(meta.length);
        for (int i=0; i<meta.length; i++)
        {
            String prop = meta[i].getProperty();
            put(prop, null);
        }
    }
    
    /**
     * Deserialize from JSON
     * @param node
     */
    public JsoRecordData(JsonNode node)
    {
        Iterator<String> fields = node.fieldNames();
        while (fields.hasNext())
        {   // add all fields
            String field = fields.next();
            JsonNode vn = node.get(field);
            final Object value;
            if (vn instanceof NullNode)
                value = null;
            else if (vn instanceof TextNode)
                value = vn.textValue();
            else if (vn instanceof DecimalNode)
                value = vn.decimalValue();
            else if (vn instanceof LongNode)
                value = vn.longValue();
            else if (vn instanceof IntNode)
                value = vn.intValue();
            else if (vn instanceof NumericNode)
                value = vn.numberValue();
            else if (vn instanceof NumericNode)
                value = vn.numberValue();
            else if (vn instanceof BooleanNode)
                value = vn.booleanValue();
            else // default
            {
                log.warn("Unknown JSon Node type: {} for {}", vn.getClass().getSimpleName(), field); 
                value = vn.asText();
            }
            // put value
            put(field, value);
        }
    }
    
    /**
     * other methos
     */
    public boolean hasValue(Column c)
    {
        return this.containsKey(c.getBeanPropertyName());
    }

    public boolean hasNonNullValue(Column c)
    {
        Object val = getValue(c);
        return !ObjectUtils.isEmpty(val);
    }

    public Object getValue(String prop, DataType dataType)
    {
        Object value = this.get(prop);
        switch(dataType) 
        {
            case BOOL:
                return ObjectUtils.getBoolean(value);
            default:
                return value;
        }
    }

    public final Object getValue(Column c)
    {
        return getValue(c.getBeanPropertyName(), c.getDataType());
    }

    public String getString(Column c)
    {
        return String.valueOf(get(c.getBeanPropertyName()));
    }
    
    public boolean isNewRecord()
    {
        return ObjectUtils.getBoolean(get("_newRecord"));
    }
}
