/**
 * 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.cxf.ext.logging;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.SequenceInputStream;

import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.io.CachedWriter;
import org.apache.cxf.io.DelegatingInputStream;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;

public class WireTapIn extends AbstractPhaseInterceptor<Message> {
    private static final String WIRE_TAP_STARTED = WireTapIn.class.getName() + ".Started";
    
    private long threshold = -1;
    private int limit = AbstractLoggingInterceptor.DEFAULT_LIMIT;

    /**
     * Instantiates a new WireTapIn
     * @param limit
     *
     * @param logMessageContent the log message content
     */
    public WireTapIn(int limit, long threshold) {
        super(Phase.RECEIVE);
        this.limit = limit;
        this.threshold = threshold;
    }

    @Override
    public void handleMessage(final Message message) throws Fault {
        if (message.containsKey(WIRE_TAP_STARTED)) {
            return;
        }
        message.put(WIRE_TAP_STARTED, Boolean.TRUE);
        try {
            InputStream is = message.getContent(InputStream.class);
            if (is != null) {
                handleInputStream(message, is);
            } else {
                Reader reader = message.getContent(Reader.class);
                if (reader != null) {
                    handleReader(message, reader);
                }
            }
        } catch (Exception e) {
            throw new Fault(e);
        }
    }

    private void handleReader(Message message, Reader reader) throws IOException {
        CachedWriter writer = new CachedWriter();
        IOUtils.copyAndCloseInput(reader, writer);
        message.setContent(Reader.class, writer.getReader());
        message.setContent(CachedWriter.class, writer);
    }

    private void handleInputStream(Message message, InputStream is) throws IOException {
        CachedOutputStream bos = new CachedOutputStream();
        if (threshold > 0) {
            bos.setThreshold(threshold);
        }
        // use the appropriate input stream and restore it later
        InputStream bis = is instanceof DelegatingInputStream
            ? ((DelegatingInputStream)is).getInputStream() : is;

        // only copy up to the limit since that's all we need to log
        // we can stream the rest
        IOUtils.copyAtLeast(bis, bos, limit == -1 ? Integer.MAX_VALUE : limit);
        bos.flush();
        bis = new SequenceInputStream(bos.getInputStream(), bis);

        // restore the delegating input stream or the input stream
        if (is instanceof DelegatingInputStream) {
            ((DelegatingInputStream)is).setInputStream(bis);
        } else {
            message.setContent(InputStream.class, bis);
        }
        message.setContent(CachedOutputStream.class, bos);

    }

    public void setLimit(int limit) {
        this.limit = limit;
    }

    public void setThreshold(long threshold) {
        this.threshold = threshold;
    }

}
