/**
 * 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.camel.component.salesforce.internal.client;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import org.apache.camel.Service;
import org.apache.camel.component.salesforce.api.SalesforceException;
import org.apache.camel.component.salesforce.internal.SalesforceSession;
import org.eclipse.jetty.client.ContentExchange;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpEventListenerWrapper;
import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.http.HttpSchemes;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class AbstractClientBase implements SalesforceSession.SalesforceSessionListener, Service {

    protected static final String APPLICATION_JSON_UTF8 = "application/json;charset=utf-8";
    protected static final String APPLICATION_XML_UTF8 = "application/xml;charset=utf-8";

    protected final Logger log = LoggerFactory.getLogger(getClass());

    protected final HttpClient httpClient;
    protected final SalesforceSession session;
    protected final String version;

    protected String accessToken;
    protected String instanceUrl;

    public AbstractClientBase(String version,
                              SalesforceSession session, HttpClient httpClient) throws SalesforceException {

        this.version = version;
        this.session = session;
        this.httpClient = httpClient;
    }

    public void start() throws Exception {
        // local cache
        accessToken = session.getAccessToken();
        if (accessToken == null) {
            // lazy login here!
            accessToken = session.login(accessToken);
        }
        instanceUrl = session.getInstanceUrl();

        // also register this client as a session listener
        session.addListener(this);
    }

    @Override
    public void stop() throws Exception {
        // deregister listener
        session.removeListener(this);
    }

    @Override
    public void onLogin(String accessToken, String instanceUrl) {
        if (!accessToken.equals(this.accessToken)) {
            this.accessToken = accessToken;
            this.instanceUrl = instanceUrl;
        }
    }

    @Override
    public void onLogout() {
        // ignore, if this client makes another request with stale token,
        // SalesforceSecurityListener will auto login!
    }

    protected SalesforceExchange getContentExchange(String method, String url) {
        SalesforceExchange get = new SalesforceExchange();
        get.setMethod(method);
        get.setURL(url);
        get.setClient(this);
        return get;
    }

    protected interface ClientResponseCallback {
        void onResponse(InputStream response, SalesforceException ex);
    }

    protected void doHttpRequest(final ContentExchange request, final ClientResponseCallback callback) {

        // use SalesforceSecurityListener for security login retries
        try {
            final boolean isHttps = HttpSchemes.HTTPS.equals(String.valueOf(request.getScheme()));
            request.setEventListener(new SalesforceSecurityListener(
                    httpClient.getDestination(request.getAddress(), isHttps),
                    request, session, accessToken));
        } catch (IOException e) {
            // propagate exception
            callback.onResponse(null, new SalesforceException(
                    String.format("Error registering security listener: %s", e.getMessage()),
                    e));
        }

        // use HttpEventListener for lifecycle events
        request.setEventListener(new HttpEventListenerWrapper(request.getEventListener(), true) {

            public String reason;

            @Override
            public void onConnectionFailed(Throwable ex) {
                super.onConnectionFailed(ex);
                callback.onResponse(null,
                        new SalesforceException("Connection error: " + ex.getMessage(), ex));
            }

            @Override
            public void onException(Throwable ex) {
                super.onException(ex);
                callback.onResponse(null,
                        new SalesforceException("Unexpected exception: " + ex.getMessage(), ex));
            }

            @Override
            public void onExpire() {
                super.onExpire();
                callback.onResponse(null,
                        new SalesforceException("Request expired", null));
            }

            @Override
            public void onResponseComplete() throws IOException {
                super.onResponseComplete();

                final int responseStatus = request.getResponseStatus();
                if (responseStatus < HttpStatus.OK_200 || responseStatus >= HttpStatus.MULTIPLE_CHOICES_300) {
                    final String msg = String.format("Error {%s:%s} executing {%s:%s}",
                            responseStatus, reason, request.getMethod(), request.getRequestURI());
                    final SalesforceException exception = new SalesforceException(msg, responseStatus, createRestException(request));
                    callback.onResponse(null, exception);
                } else {
                    // TODO not memory efficient for large response messages,
                    // doesn't seem to be possible in Jetty 7 to directly stream to response parsers
                    final byte[] bytes = request.getResponseContentBytes();
                    callback.onResponse(bytes != null ? new ByteArrayInputStream(bytes) : null, null);
                }

            }

            @Override
            public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException {
                super.onResponseStatus(version, status, reason);
                // remember status reason
                this.reason = reason.toString(StringUtil.__ISO_8859_1);
            }
        });

        // execute the request
        try {
            httpClient.send(request);
        } catch (IOException e) {
            String msg = "Unexpected Error: " + e.getMessage();
            // send error through callback
            callback.onResponse(null, new SalesforceException(msg, e));
        }

    }

    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }

    public void setInstanceUrl(String instanceUrl) {
        this.instanceUrl = instanceUrl;
    }

    protected abstract void setAccessToken(HttpExchange httpExchange);

    protected abstract SalesforceException createRestException(ContentExchange httpExchange);

}
