/****************************************************************
 * 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.james.mime4j;

import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;

/**
 * Stream that constrains itself to a single MIME body part.
 * After the stream ends (i.e. read() returns -1) {@link #hasMoreParts()}
 * can be used to determine if a final boundary has been seen or not.
 * If {@link #parentEOF()} is <code>true</code> an unexpected end of stream
 * has been detected in the parent stream.
 * 
 * 
 * 
 * @version $Id: MimeBoundaryInputStream.java,v 1.2 2004/11/29 13:15:42 ntherning Exp $
 */
public class MimeBoundaryInputStream extends InputStream {
    
    private PushbackInputStream s = null;
    private byte[] boundary = null;
    private boolean first = true;
    private boolean eof = false;
    private boolean parenteof = false;
    private boolean moreParts = true;

    /**
     * Creates a new MimeBoundaryInputStream.
     * @param s The underlying stream.
     * @param boundary Boundary string (not including leading hyphens).
     */
    public MimeBoundaryInputStream(InputStream s, String boundary) 
            throws IOException {
        
        this.s = new PushbackInputStream(s, boundary.length() + 4);

        boundary = "--" + boundary;
        this.boundary = new byte[boundary.length()];
        for (int i = 0; i < this.boundary.length; i++) {
            this.boundary[i] = (byte) boundary.charAt(i);
        }
        
        /*
         * By reading one byte we will update moreParts to be as expected
         * before any bytes have been read.
         */
        int b = read();
        if (b != -1) {
            this.s.unread(b);
        }
    }

    /**
     * Closes the underlying stream.
     * 
     * @throws IOException on I/O errors.
     */
    public void close() throws IOException {
        s.close();
    }

    /**
     * Determines if the underlying stream has more parts (this stream has
     * not seen an end boundary).
     * 
     * @return <code>true</code> if there are more parts in the underlying 
     *         stream, <code>false</code> otherwise.
     */
    public boolean hasMoreParts() {
        return moreParts;
    }

    /**
     * Determines if the parent stream has reached EOF
     * 
     * @return <code>true</code>  if EOF has been reached for the parent stream, 
     *         <code>false</code> otherwise.
     */
    public boolean parentEOF() {
        return parenteof;
    }
    
    /**
     * Consumes all unread bytes of this stream. After a call to this method
     * this stream will have reached EOF.
     * 
     * @throws IOException on I/O errors.
     */
    public void consume() throws IOException {
        while (read() != -1) {
        }
    }
    
    /**
     * @see java.io.InputStream#read()
     */
    public int read() throws IOException {
        if (eof) {
            return -1;
        }
        
        if (first) {
            first = false;
            if (matchBoundary()) {
                return -1;
            }
        }
        
        int b1 = s.read();
        int b2 = s.read();
        
        if (b1 == '\r' && b2 == '\n') {
            if (matchBoundary()) {
                return -1;
            }
        }
        
        if (b2 != -1) {
            s.unread(b2);
        }

        parenteof = b1 == -1;
        eof = parenteof;
        
        return b1;
    }
    
    private boolean matchBoundary() throws IOException {
        
        for (int i = 0; i < boundary.length; i++) {
            int b = s.read();
            if (b != boundary[i]) {
                if (b != -1) {
                    s.unread(b);
                }
                for (int j = i - 1; j >= 0; j--) {
                    s.unread(boundary[j]);
                }
                return false;
            }
        }
        
        /*
         * We have a match. Is it an end boundary?
         */
        int prev = s.read();
        int curr = s.read();
        moreParts = !(prev == '-' && curr == '-');
        do {
            if (curr == '\n' && prev == '\r') {
                break;
            }
            prev = curr;
        } while ((curr = s.read()) != -1);
        
        if (curr == -1) {
            moreParts = false;
            parenteof = true;
        }
        
        eof = true;
        
        return true;
    }
}
