/*
 *  Copyright 2006-2009 Mikhail Kryshen
 *
 *  This file is part of Tema.
 *
 *  Tema 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 3 of the
 *  License, or (at your option) any later version.
 *
 *  Tema 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 Tema.  
 *  If not, see <http://www.gnu.org/licenses/>.
 */

package kryshen.tema.io;

import java.io.FilterReader;
import java.io.IOException;
import java.io.Reader;
import java.io.LineNumberReader;
import kryshen.tema.TemplateParser;

/**
 * Reader for Tema templates. Stores data source name (commonly
 * filename) and tracks line numbers for error reporting.
 *
 * @author Mikhail Kryshen
 */
public class TemplateReader extends FilterReader {
    static final int UNREAD_BUFFER_SIZE;
    
    /* Calculate UNREAD_BUFFER_SIZE value. */
    static {
        int max = 0;
        
        for (String s : TemplateParser.BRACKET_OPEN) {
            max = Math.max(max, s.length());
        }
        
        for (String s : TemplateParser.BRACKET_CLOSE) {
            max = Math.max(max, s.length());
        }
        
        max = Math.max(max, TemplateParser.ESCAPE_NEWLINE.length() + 2);
        max = Math.max(max, TemplateParser.ESCAPE_WHITESPACE.length());
        
        UNREAD_BUFFER_SIZE = max + 1;
    }

    private final String source;
    private final LineNumberReader lnReader;
    private final TemplateReader parentReader;

    private final int[] unreadBuffer = new int[UNREAD_BUFFER_SIZE];
    private int unread = 0;

    public TemplateReader(Reader in) {
        this(new LineNumberReader(in));
    }
    
    public TemplateReader(Reader in, String source) {
        this(new LineNumberReader(in), source);
    }
    
    public TemplateReader(LineNumberReader in) {
        this(in, "");
    }
    
    public TemplateReader(LineNumberReader in, String source) {
        super(in);
        
        this.parentReader = null;
        this.lnReader = in;
        this.source = source;
    }
    
    public TemplateReader(Reader in, TemplateReader parent) {
        super(in);
        
        this.parentReader = parent;
        this.lnReader = null;
        this.source = null;
    }
    
    public String getSource() {
        if (source != null)
            return source;
        
        return parentReader.getSource();
    }
    
    public int getLineNumber() {
        if (lnReader != null)
            return lnReader.getLineNumber();
        
        return parentReader.getLineNumber();
    }

    @Override
    public int read() throws IOException {
        if (unread == 0) {
            return super.read();
        }

        return unreadBuffer[--unread];
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        int n = 0;

        while (unread > 0) {
            cbuf[off + n++] = (char) unreadBuffer[--unread];

            if (n == len) {
                return n;
            }
        }

        return n + super.read(cbuf, off + n, len - n);
    }

    @Override
    public long skip(long n) throws IOException {
        if (n <= unread) {
            unread -= n;
            return n;
        }

        n -= unread;
        long ret = super.skip(n) + unread;
        unread = 0;

        return ret;
    }


    public void unread(String s) throws IOException {
        unread(s.toCharArray());
    }

    public void unread(int c) {
        if (unread + 1 > UNREAD_BUFFER_SIZE) {
            throw new IndexOutOfBoundsException();
        }

        unreadBuffer[unread++] = c;
    }

    public void unread(char[] buff) {
        unread(buff, 0, buff.length);
    }

    public void unread(char[] buff, int off, int len) {
        if (unread + len > UNREAD_BUFFER_SIZE) {
            throw new IndexOutOfBoundsException();
        }

        for (int i = off + len - 1; i >= off; i--) {
            unreadBuffer[unread++] = buff[i];
        }
    }

    @Override
    public boolean ready() throws IOException {
        return unread > 0 || super.ready();
    }

    @Override
    public void mark(int readAheadLimit) throws IOException {
        throw new IOException("Not supported");
    }

    @Override
    public void reset() throws IOException {
        throw new IOException("Not supported");
    }

    @Override
    public boolean markSupported() {
        return false;
    }
}