/*
 * Copyright (c) 2015-2019 Dzikoysk
 *
 * Licensed 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.panda_lang.panda.framework.language.interpreter.token.stream;

import org.panda_lang.panda.framework.design.interpreter.token.TokenRepresentation;
import org.panda_lang.panda.framework.design.interpreter.token.snippet.Snippet;
import org.panda_lang.panda.framework.design.interpreter.token.stream.SourceStream;
import org.panda_lang.panda.framework.language.interpreter.token.PandaSnippet;

import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;

public class PandaSourceStream implements SourceStream {

    private final Snippet original;
    private Snippet source;
    private Snippet cachedSource;

    public PandaSourceStream(Snippet source) {
        if (source == null) {
            throw new IllegalArgumentException("Source cannot be null");
        }

        this.original = source;
        this.source = source;
        this.cachedSource = source;
    }

    @Override
    public TokenRepresentation read() {
        if (source.size() < 1) {
            throw new NoSuchElementException("SourceStream is empty, cannot read next TokenRepresentation");
        }

        TokenRepresentation representation = source.get(0);
        List<TokenRepresentation> tokens = source.getTokensRepresentations().subList(1, source.size());

        this.cachedSource = this.source;
        this.source = new PandaSnippet(tokens);

        return representation;
    }

    @Override
    public Snippet read(int length) {
        TokenRepresentation[] array = new TokenRepresentation[length];

        for (int i = 0; i < length; i++) {
            array[i] = source.get(i);
        }

        this.cachedSource = this.source;
        this.source = new PandaSnippet(source.getTokensRepresentations().subList(length, source.size()));

        return new PandaSnippet(array);
    }

    @Override
    public Snippet readLineResidue() {
        List<TokenRepresentation> residue = new ArrayList<>();
        int currentLine = this.getCurrentLine();

        while (this.hasUnreadSource()) {
            TokenRepresentation representation = this.read();

            if (representation.getLine() != currentLine) {
                this.restoreCachedSource();
                break;
            }

            residue.add(representation);
        }

        return new PandaSnippet(residue);
    }

    @Override
    public PandaSourceStream restoreCachedSource() {
        this.source = cachedSource;
        return this;
    }

    @Override
    public PandaSourceStream updateCachedSource() {
        this.cachedSource = source;
        return this;
    }

    @Override
    public SourceStream update(Snippet source) {
        this.source = source;
        return this;
    }

    @Override
    public Snippet getOriginalSource() {
        return original;
    }

    @Override
    public Snippet toSnippet() {
        return new PandaSnippet(new ArrayList<>(this.source.getTokensRepresentations()));
    }

    @Override
    public boolean hasUnreadSource() {
        return source.size() > 0;
    }

    @Override
    public String toString() {
        return "PandaSourceStream['" + this.source + "']";
    }

}

@@ -1201,56 +1201,17 @@
 ate 
-Snippet source;%0A    private Snippet cachedSource
+int index
 ;%0A%0A 
@@ -1406,74 +1406,8 @@
 ce;%0A
-        this.source = source;%0A        this.cachedSource = source;%0A
     
@@ -1479,25 +1479,26 @@
 if (
-source.size() %3C 1
+!hasUnreadSource()
 ) %7B%0A
@@ -1583,22 +1583,17 @@
 ad next 
-TokenR
+r
 epresent
@@ -1624,456 +1624,132 @@
     
-TokenRepresentation representation = source.get(0);%0A        List%3CTokenRepresentation%3E tokens = source.getTokensRepresentations().subList(1, source.size());%0A%0A        this.cachedSource = this.source;%0A        this.source = new PandaSnippet(tokens);%0A%0A        return representation;%0A    %7D%0A%0A    @Override%0A    public Snippet read(int length) %7B%0A        TokenRepresentation%5B%5D array = new TokenRepresentation%5Blength%5D;%0A%0A        for (int i = 0; i %3C 
+return original.get(index++);%0A    %7D%0A%0A    @Override%0A    public Snippet read(int length) %7B%0A        if (hasUnreadSource(
 length
-; i++
+)
 ) %7B%0A
@@ -1764,230 +1764,189 @@
     
-array%5Bi%5D = source.get(i);%0A        %7D%0A%0A        this.cachedSource = this.source;%0A        this.source = new PandaSnippet(source.getTokensRepresentations().subList(length, source.size()));%0A%0A        return new PandaSnippet(array
+throw new IndexOutOfBoundsException(%22source(%22 + index + length + %22) %3E= source.length (%22 + original.size() + %22)%22);%0A        %7D%0A%0A        return original.subSource(index, index += length
 );%0A 
@@ -2284,52 +2284,8 @@
 ) %7B%0A
-                this.restoreCachedSource();%0A
     
@@ -2409,16 +2409,23 @@
 (residue
+, false
 );%0A    %7D
@@ -2455,434 +2455,241 @@
 lic 
-PandaSourceStream restoreCachedSource() %7B%0A        this.source = cachedSource;%0A        return this;%0A    %7D%0A%0A    @Override%0A    public PandaSourceStream updateCachedSource() %7B%0A        this.cachedSource = source;%0A        return this;%0A    %7D%0A%0A    @Override%0A    public SourceStream update(Snippet source) %7B%0A        this.source = source;%0A        return this;%0A    %7D%0A%0A    @Override%0A    public Snippet getO
+void dispose(int length) %7B%0A        index += length;%0A    %7D%0A%0A    @Override%0A    public Snippet getOriginalSource() %7B%0A        return original;%0A    %7D%0A%0A    @Override%0A    public Snippet toSnippet() %7B%0A        return o
 riginal
+.sub
 Source(
-) %7B%0A        return
+index,
  ori
@@ -2685,32 +2685,40 @@
 (index, original
+.size())
 ;%0A    %7D%0A%0A    @Ov
@@ -2739,151 +2739,89 @@
 lic 
-Snippet toSnippet() %7B%0A        return new PandaSnippet(new ArrayList%3C%3E(this.source.getTokensRepresentations()));%0A    %7D%0A%0A    @Override%0A    public
+boolean hasUnreadSource() %7B%0A        return hasUnreadSource(1);%0A    %7D%0A%0A    private
  boo
@@ -2833,32 +2833,42 @@
 hasUnreadSource(
+int length
 ) %7B%0A        retu
@@ -2874,25 +2874,48 @@
 urn 
-source
+(index + (length - 1)) %3C original
 .size()
- %3E 0
 ;%0A  
@@ -3008,22 +3008,27 @@
  + this.
-source
+toSnippet()
  + %22'%5D%22;

Implement changes of SourceStream API in the default implementation