/*
 * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. 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.wso2.carbon.databridge.receiver.thrift.converter;


import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.databridge.commons.AttributeType;
import org.wso2.carbon.databridge.commons.Event;
import org.wso2.carbon.databridge.commons.thrift.data.ThriftEventBundle;
import org.wso2.carbon.databridge.commons.utils.DataBridgeCommonsUtils;
import org.wso2.carbon.databridge.commons.utils.EventDefinitionConverterUtils;
import org.wso2.carbon.databridge.core.EventConverter;
import org.wso2.carbon.databridge.core.StreamTypeHolder;
import org.wso2.carbon.databridge.core.exception.EventConversionException;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * the util class that converts Events and its definitions in to various forms
 */
public final class ThriftEventConverter implements EventConverter {

    public Object[] toObjectArray(ThriftEventBundle thriftEventBundle,
                                  AttributeType[] attributeTypeOrder,
                                  IndexCounter indexCounter) {
        if (attributeTypeOrder != null) {
            Object[] objects = new Object[attributeTypeOrder.length];
            for (int i = 0; i < attributeTypeOrder.length; i++) {
                switch (attributeTypeOrder[i]) {
                    case INT:
                        objects[i] = thriftEventBundle.getIntAttributeList().get(indexCounter.getIntCount());
                        indexCounter.incrementIntCount();
                        break;
                    case LONG:
                        objects[i] = thriftEventBundle.getLongAttributeList().get(indexCounter.getLongCount());
                        indexCounter.incrementLongCount();
                        break;
                    case STRING:
                        String stringValue = thriftEventBundle.getStringAttributeList().get(indexCounter.getStringCount());
                        if (stringValue.equals(EventDefinitionConverterUtils.nullString)) {
                            objects[i] = null;
                        } else {
                            objects[i] = stringValue;
                        }
                        indexCounter.incrementStringCount();
                        break;
                    case DOUBLE:
                        objects[i] = thriftEventBundle.getDoubleAttributeList().get(indexCounter.getDoubleCount());
                        indexCounter.incrementDoubleCount();
                        break;
                    case FLOAT:
                        objects[i] = thriftEventBundle.getDoubleAttributeList().get(indexCounter.getDoubleCount()).floatValue();
                        indexCounter.incrementDoubleCount();
                        break;
                    case BOOL:
                        objects[i] = thriftEventBundle.getBoolAttributeList().get(indexCounter.getBoolCount());
                        indexCounter.incrementBoolCount();
                        break;
                }
            }
            return objects;
        } else {
            return null;
        }
    }

    public List<Event> toEventList(Object eventBundle,
                                   StreamTypeHolder streamTypeHolder) {
        if (eventBundle instanceof ThriftEventBundle) {
            return createEventList((ThriftEventBundle) eventBundle, streamTypeHolder);
        } else {
            throw new EventConversionException("Wrong type of event received " + eventBundle.getClass());
        }

    }

    @Override
    public int getSize(Object eventBundle) {
        if (eventBundle instanceof ThriftEventBundle) {
            ThriftEventBundle thriftEventBundle = (ThriftEventBundle) eventBundle;
            int eventBundleSize = 0;
            //arbitray data
            if (thriftEventBundle.isSetArbitraryDataMapMap()){
                Set<Map.Entry<Integer, Map<String, String>>> arbitraryDataMap = thriftEventBundle.getArbitraryDataMapMap().entrySet();
                for (Map.Entry<Integer, Map<String, String>> arbitraryData : arbitraryDataMap){
                    eventBundleSize += DataBridgeCommonsUtils.getSize(arbitraryData.getValue());
                }
                eventBundleSize += arbitraryDataMap.size() * 4; // 4 byte per integer
                eventBundleSize += arbitraryDataMap.size() * DataBridgeCommonsUtils.getReferenceSize() * 2; //for the reference
            }
            eventBundleSize += thriftEventBundle.getBoolAttributeListSize();  //1 byte per boolean field
            eventBundleSize += thriftEventBundle.getBoolAttributeListSize() * DataBridgeCommonsUtils.getReferenceSize(); // for each reference
            eventBundleSize += thriftEventBundle.getDoubleAttributeListSize() * 8; //8 bytes per double field
            eventBundleSize += thriftEventBundle.getDoubleAttributeListSize() * DataBridgeCommonsUtils.getReferenceSize(); // for each double reference.
            eventBundleSize += thriftEventBundle.getIntAttributeListSize() * 4; // 4 bytes per int field
            eventBundleSize += thriftEventBundle.getIntAttributeListSize()  * DataBridgeCommonsUtils.getReferenceSize(); // for each int reference
            eventBundleSize += thriftEventBundle.getLongAttributeListSize() * 8; // 8 bytes per long field
            eventBundleSize += thriftEventBundle.getLongAttributeListSize() * DataBridgeCommonsUtils.getReferenceSize(); // for each long reference
            for (String aStringField : thriftEventBundle.getStringAttributeList()){
                eventBundleSize += aStringField.getBytes().length;
            }
            eventBundleSize += thriftEventBundle.getStringAttributeListSize() * DataBridgeCommonsUtils.getReferenceSize(); // for each string reference
            eventBundleSize += 4; //for eventNum field
            eventBundleSize += DataBridgeCommonsUtils.getSize(thriftEventBundle.getSessionId());
            eventBundleSize += 7 * DataBridgeCommonsUtils.getReferenceSize(); // for the references of list and string attributes in thrift event bundle
            return eventBundleSize;
        } else {
            throw new EventConversionException("Wrong type of event received " + eventBundle.getClass());
        }
    }

    @Override
    public int getNumberOfEvents(Object eventBundle) {
        if (eventBundle instanceof ThriftEventBundle) {
            return ((ThriftEventBundle) eventBundle).getEventNum();
        } else {
            throw new EventConversionException("Wrong type event relieved " + eventBundle.getClass());
        }
    }

    private List<Event> createEventList(ThriftEventBundle thriftEventBundle,
                                        StreamTypeHolder streamTypeHolder) {

        IndexCounter indexCounter = new IndexCounter();
        List<Event> eventList = new ArrayList<>(thriftEventBundle.getEventNum());
        String streamId = null;
        try {
            for (int i = 0; i < thriftEventBundle.getEventNum(); i++) {
                Event event = new Event();
                streamId = thriftEventBundle.getStringAttributeList().get(indexCounter.getStringCount());
                indexCounter.incrementStringCount();
                event.setStreamId(streamId);
                long timeStamp = thriftEventBundle.getLongAttributeList().get(indexCounter.getLongCount());
                indexCounter.incrementLongCount();
                event.setTimeStamp(timeStamp);
                AttributeType[][] attributeTypeOrder = streamTypeHolder.getDataType(streamId);
                if (attributeTypeOrder == null) {
                    PrivilegedCarbonContext privilegedCarbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext();
                    if (privilegedCarbonContext.getTenantDomain() == null) {
                        privilegedCarbonContext.setTenantDomain(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME);
                        privilegedCarbonContext.setTenantId(MultitenantConstants.SUPER_TENANT_ID);
                    }
                    streamTypeHolder.reloadStreamTypeHolder();
                    attributeTypeOrder = streamTypeHolder.getDataType(streamId);
                    if (attributeTypeOrder == null) {
                        throw new EventConversionException("No StreamDefinition for streamId " + streamId + " present in cache ");
                    }
                }
                event.setMetaData(this.toObjectArray(thriftEventBundle, attributeTypeOrder[0], indexCounter));
                event.setCorrelationData(this.toObjectArray(thriftEventBundle, attributeTypeOrder[1], indexCounter));
                event.setPayloadData(this.toObjectArray(thriftEventBundle, attributeTypeOrder[2], indexCounter));
                if (thriftEventBundle.isSetArbitraryDataMapMap()) {
                    Map<String, String> arbitraryData = thriftEventBundle.getArbitraryDataMapMap().get(i);
                    if (null != arbitraryData) {
                        event.setArbitraryDataMap(arbitraryData);
                    }
                }
                eventList.add(event);
            }
        } catch (RuntimeException re) {
            throw new EventConversionException("Error when converting " + streamId + " of event bundle with events " + thriftEventBundle.getEventNum(), re);
        }
        return eventList;
    }

}
