/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */
package org.teiid.translator.odata4;

import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.apache.olingo.client.api.serialization.ODataDeserializerException;
import org.apache.olingo.client.core.serialization.JsonDeserializer;
import org.apache.olingo.commons.api.data.Property;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException;
import org.apache.olingo.commons.api.http.HttpStatusCode;
import org.teiid.language.Argument;
import org.teiid.language.Argument.Direction;
import org.teiid.language.Call;
import org.teiid.language.SQLConstants.Tokens;
import org.teiid.metadata.Procedure;
import org.teiid.metadata.ProcedureParameter;
import org.teiid.metadata.RuntimeMetadata;
import org.teiid.olingo.common.ODataTypeManager;
import org.teiid.translator.DataNotAvailableException;
import org.teiid.translator.ExecutionContext;
import org.teiid.translator.ProcedureExecution;
import org.teiid.translator.TranslatorException;
import org.teiid.translator.WSConnection;
import org.teiid.translator.document.DocumentNode;
import org.teiid.translator.odata4.ODataMetadataProcessor.ODataType;

public class ODataProcedureExecution extends BaseQueryExecution implements ProcedureExecution {
    private Object returnValue;
    private ODataResponse response;
    private Class<?>[] expectedColumnTypes;
    private Call command;

    public ODataProcedureExecution(Call command,
            ODataExecutionFactory translator,
            ExecutionContext executionContext, RuntimeMetadata metadata,
            WSConnection connection) throws TranslatorException {
        super(translator, executionContext, metadata, connection);
        this.command = command;
        this.expectedColumnTypes = command.getResultSetColumnTypes();
    }
    
    private boolean isFunction(Procedure proc) {
        ODataType type = ODataType.valueOf(proc.getProperty(ODataMetadataProcessor.ODATA_TYPE, false));
        return type == ODataType.FUNCTION;
    }   

    private ProcedureParameter getReturnParameter() {
        for (ProcedureParameter pp : this.command.getMetadataObject().getParameters()) {
            if (pp.getType() == ProcedureParameter.Type.ReturnValue) {
                return pp;
            }
        }
        return null;
    }
    
    private String getQueryParameters(Call obj) throws EdmPrimitiveTypeException {
        StringBuilder sb = new StringBuilder();
        final List<Argument> params = obj.getArguments();
        if (params != null && params.size() != 0) {
            Argument param = null;
            for (int i = 0; i < params.size(); i++) {
                param = params.get(i);
                if (param.getDirection() == Direction.IN || param.getDirection() == Direction.INOUT) {
                    if (i != 0) {
                        sb.append("&"); //$NON-NLS-1$
                    }
                    sb.append(WSConnection.Util.httpURLEncode(param.getMetadataObject().getName()));
                    sb.append(Tokens.EQ);
                    sb.append(WSConnection.Util.httpURLEncode(ODataTypeManager.convertToODataURIValue(param.getArgumentValue().getValue(), 
                            ODataTypeManager.odataType(param.getType()).getFullQualifiedName()
                            .getFullQualifiedNameAsString())));
                }
            }
        }
        return sb.toString();
    }
    
    private String buildFunctionURL(Call obj, String parameters) {
        StringBuilder sb = new StringBuilder();
        sb.append(obj.getProcedureName());
        if (parameters != null && !parameters.isEmpty()) {
            sb.append("?");
            sb.append(parameters);
        }
        return sb.toString();
    }
    
    @Override
    public void execute() throws TranslatorException {
        try {
        	String  parameters = getQueryParameters(this.command); 
                
            InputStream response = null;
            Procedure procedure = this.command.getMetadataObject();
            if (isFunction(procedure)) {
                String URI = buildFunctionURL(this.command, parameters);
                response = executeQuery("GET", URI, null, null, new HttpStatusCode[] { HttpStatusCode.OK });
                handleResponse(procedure, URI, response);
            } else {
                String URI = this.command.getProcedureName();
                response = executeQuery("POST", URI, parameters, null, new HttpStatusCode[] { HttpStatusCode.OK });
                handleResponse(procedure, URI, response);
            }
        } catch (ODataDeserializerException e) {
            throw new TranslatorException(e);
        } catch (EdmPrimitiveTypeException e) {
        	throw new TranslatorException(e);
		}
    }

    private void handleResponse(final Procedure procedure, final String baseUri, final InputStream payload)
            throws TranslatorException, ODataDeserializerException {
        if (procedure.getResultSet() != null) {
            ODataType type = ODataType.valueOf(procedure.getResultSet().getProperty(
                    ODataMetadataProcessor.ODATA_TYPE, false));
            this.response = new ODataResponse(payload, type, new DocumentNode()) {
                @Override
                public InputStream nextBatch(java.net.URI uri) throws TranslatorException {
                    return executeSkipToken(uri, baseUri,
                            new HttpStatusCode[] { HttpStatusCode.OK });
                }
            };
        } else if (getReturnParameter() != null) {
            // this is scalar result
            JsonDeserializer parser = new JsonDeserializer(false);
            Property property = parser.toProperty(payload).getPayload();
            if (property.isCollection()) {
                this.returnValue = property.asCollection();
            } else {
                this.returnValue = property.asPrimitive();    
            }
        }
    }

    @Override
    public List<?> next() throws TranslatorException, DataNotAvailableException {
        Procedure procedure = this.command.getMetadataObject();
        if (this.response != null) {
            Map<String, Object> row = this.response.getNext();
            if (row != null) {
                return buildRow(procedure.getResultSet(), 
                        procedure.getResultSet().getColumns(), 
                        this.expectedColumnTypes, row);
            }
        }
        return null;
    }

    @Override
    public List<?> getOutputParameterValues() throws TranslatorException {
        return Arrays.asList(this.returnValue);
    }

    @Override
    public void close() {
    }

    @Override
    public void cancel() throws TranslatorException {
    }
}
