/*
 * Copyright 2013-2015 Makoto YUI
 *
 * 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 org.apache.hadoop.hive.jdbc.storagehandler;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hive.serde2.SerDe;
import org.apache.hadoop.hive.serde2.SerDeException;
import org.apache.hadoop.hive.serde2.SerDeStats;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.StructField;
import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector;
import org.apache.hadoop.io.Writable;

/**
 * Serialize/Deserialize a tuple.
 */
public class JdbcSerDe implements SerDe {
    private static final Log LOG = LogFactory.getLog(JdbcSerDe.class);

    private DbRecordWritable cachedWritable;

    private int fieldCount;

    private StructObjectInspector objectInspector;
    private List<Object> deserializeCache;

    public JdbcSerDe() {}

    @Override
    public void initialize(Configuration sysConf, Properties tblProps) throws SerDeException {
        if(LOG.isDebugEnabled()) {
            LOG.debug("tblProps: " + tblProps);
        }

        String columnNameProperty = tblProps.getProperty(Constants.LIST_COLUMNS);
        String columnTypeProperty = tblProps.getProperty(Constants.LIST_COLUMN_TYPES);

        List<String> columnNames = Arrays.asList(columnNameProperty.split(","));
        String[] columnTypes = columnTypeProperty.split(":");
        assert (columnTypes.length == columnNames.size()) : "columnNames: " + columnNames
                + ", columnTypes: " + Arrays.toString(columnTypes);

        int[] types = HiveJdbcBridgeUtils.hiveTypesToSqlTypes(columnTypes);
        this.cachedWritable = new DbRecordWritable(types);
        this.fieldCount = types.length;

        final List<ObjectInspector> fieldOIs = new ArrayList<ObjectInspector>(columnTypes.length);
        for(int i = 0; i < types.length; i++) {
            ObjectInspector oi = HiveJdbcBridgeUtils.getObjectInspector(types[i], columnTypes[i]);
            fieldOIs.add(oi);
        }
        this.objectInspector = ObjectInspectorFactory.getStandardStructObjectInspector(columnNames, fieldOIs);
        this.deserializeCache = new ArrayList<Object>(columnTypes.length);
    }

    @Override
    public ObjectInspector getObjectInspector() throws SerDeException {
        return objectInspector;
    }

    @Override
    public Class<? extends Writable> getSerializedClass() {
        return DbRecordWritable.class;
    }

    /**
     * This method takes an object representing a row of data from Hive, and uses
     * the ObjectInspector to get the data for each column and serialize.
     */
    @Override
    public DbRecordWritable serialize(Object row, ObjectInspector inspector) throws SerDeException {
        final StructObjectInspector structInspector = (StructObjectInspector) inspector;
        final List<? extends StructField> fields = structInspector.getAllStructFieldRefs();
        if(fields.size() != fieldCount) {
            throw new SerDeException(String.format("Required %d columns, received %d.", fieldCount, fields.size()));
        }

        cachedWritable.clear();

        for(int i = 0; i < fieldCount; i++) {
            StructField structField = fields.get(i);
            if(structField != null) {
                Object field = structInspector.getStructFieldData(row, structField);
                ObjectInspector fieldOI = structField.getFieldObjectInspector();
                Object javaObject = HiveJdbcBridgeUtils.deparseObject(field, fieldOI);
                cachedWritable.set(i, javaObject);
            }
        }

        return cachedWritable;
    }

    /**
     * This method does the work of deserializing a record into Java objects that
     * Hive can work with via the ObjectInspector interface.
     */
    @Override
    public Object deserialize(Writable record) throws SerDeException {
        if(!(record instanceof DbRecordWritable)) {
            throw new SerDeException("Expected DbTupleWritable, received "
                    + record.getClass().getName());
        }
        DbRecordWritable tuple = (DbRecordWritable) record;
        deserializeCache.clear();

        for(int i = 0; i < fieldCount; i++) {
            Object o = tuple.get(i);
            deserializeCache.add(o);
        }

        return deserializeCache;
    }

    @Override
    public SerDeStats getSerDeStats() {
        // TODO Auto-generated method stub
        return null;
    }
}