/*
 * 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
 *
 *   https://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.
 *
 *   SPDX-License-Identifier: Apache-2.0
 */

package org.apache.jena.sparql.sse;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.jena.graph.Node;

public class ItemList extends ItemLocation implements Iterable<Item> {
    private final List<Item> elements;
    // Pointer to the start of the list
    // Because of repeated cdr's, we can not use a .subList (leads to a whole
    // chain subLists, one per call).
    private final int offset;

    private int index(int i) {
        return offset + i;
    }

    private int _size() {
        return elements.size() - offset;
    }

    private List<Item> slice() {
        return elements.subList(offset, elements.size());
    }

    public ItemList(int line, int column) {
        this(line, column, 0, new ArrayList<Item>());
    }

    public ItemList() {
        this(noLine, noColumn);
    }

    public ItemList(int line, int column, int offset, List<Item> elts) {
        super(line, column);
        this.elements = elts;
        this.offset = offset;
    }

    public int size() {
        return _size();
    }

    public boolean isEmpty() {
        return _size() == 0;
    }

    @Override
    public int hashCode() {
        return elements.hashCode() ^ offset;
    }

    @Override
    public boolean equals(Object other) {
        if ( this == other )
            return true;
        if ( !(other instanceof ItemList) )
            return false;
        ItemList list = (ItemList)other;

        if ( list.size() != size() )
            return false;
        return slice().equals(list.slice());
    }

    public void addAll(ItemList itemList) {
        elements.addAll(itemList.elements);
    }

    public void add(Item item) {
        elements.add(item);
    }

    public void add(Node node) {
        elements.add(Item.createNode(node));
    }

    public void add(String symbol) {
        elements.add(Item.createSymbol(symbol));
    }

    public Item get(int idx) {
        return elements.get(index(idx));
    }

    public Item getFirst() {
        return get(0);
    }

    public boolean isTaggedIgnoreCase(String symbol) {
        return !isEmpty() && getFirst().isSymbol(symbol);
    }

    public boolean isTagged(String symbol) {
        return !isEmpty() && getFirst().isSymbolIgnoreCase(symbol);
    }

    public Item getLast() {
        return get(_size() - 1);
    }

    @Override
    public Iterator<Item> iterator() {
        return elements.listIterator(offset);
    }

    public Item car() {
        if ( _size() == 0 )
            throw new ItemException("ItemList.car: list is zero length");
        return get(0);
    }

    public ItemList cdr() {
        if ( _size() == 0 )
            throw new ItemException("ItemList.cdr: list is zero length");
        ItemList x = new ItemList(super.getLine(), super.getColumn(), offset + 1, elements);
        return x;
    }

    public ItemList cdrOrNull() {
        if ( _size() == 0 )
            return null;
        ItemList x = new ItemList(super.getLine(), super.getColumn(), offset + 1, elements);
        return x;
    }

    public ItemList sublist(int start) {
        if ( _size() < start + offset )
            return null;
        ItemList x = new ItemList(noLine, noColumn, offset + start, elements);
        return x;
    }

    /** Slice of the list from start (inclusive) to finish (exclusive) */
    public ItemList sublist(int start, int finish) {
        if ( start < 0 || finish < 0 || finish < start )
            return null;

        if ( _size() < start )
            return null;
        if ( finish > _size() )
            return null;

        ItemList x = new ItemList();
        // Take a slice.
        // Note : this is a copy.
        // Note: List.subList just puts a wrapper around the overlying list
        // but don't do this a lot because ArrayList.get recurses to the core list
        // (may run out of stack).

        // Better would an adjusting slice over base array but it's quite tricky to
        // do the calculations correctly.
        // This way is mnore likely to be correct.

        x.elements.addAll(elements.subList(start + offset, finish + offset));
        return x;
    }

    public String shortString() {
        if ( _size() == 0 )
            return "()";
        if ( get(0).isSymbol() ) {
            if ( _size() == 1 )
                return "(" + get(0).getSymbol() + ")";
            else
                return "(" + get(0).getSymbol() + " ...)";
        }
        return "(...)";
    }

    @Override
    public String toString() {
        String str = "";
        if ( hasLocation() )
            str = str.concat(location());
        return str + slice().toString();
    }
}
