/*
 * Copyright (c) 2008-2016, Hazelcast, Inc. All Rights Reserved.
 *
 * 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 com.hazelcast.internal.serialization.impl;

import com.hazelcast.util.StringUtil;

import static com.hazelcast.util.Preconditions.checkHasText;

/**
 * Mutable cursor that allows iterating over path tokens split by a dot (.)
 * It may be reused, just init it with the init() method and then reset() to tear it down after the usage.
 */
final class PortablePathCursor {

    private String path;
    private int index;

    private int offset;
    private int nextSplit;
    private String token;

    PortablePathCursor() {
    }

    /**
     * Inits the cursor with the given path and sets the current position to the first token.
     *
     * @param path path to initialise the cursor with
     */
    void init(String path) {
        this.path = checkHasText(path, "path cannot be null or empty");
        this.index = 0;
        this.offset = 0;
        this.nextSplit = StringUtil.indexOf(path, '.', 0);
        this.token = null;
        if (nextSplit == 0) {
            throw new IllegalArgumentException("The path cannot begin with a dot: " + path);
        }
    }

    /**
     * Inits the cursor with the given assuming it's a path with single token (and without validating it).
     * It's an optimisation for the common execution path where there's only a single field beeing read.
     *
     * @param path path to initialise the cursor with
     */
    void initWithSingleTokenPath(String path) {
        this.path = path;
        this.index = 0;
        this.offset = 0;
        this.nextSplit = -1;
        this.token = path;
    }

    /**
     * Resets the cursor to a null state.
     */
    void reset() {
        this.path = null;
        this.index = -1;
        this.offset = 0;
        this.token = null;
    }

    boolean isLastToken() {
        return nextSplit == -1;
    }

    String token() {
        if (token != null) {
            return token;
        }
        int endIndex = (nextSplit < 0 ? path.length() : nextSplit);
        if (endIndex <= offset) {
            throw new IllegalArgumentException("The token's length cannot be zero: " + path);
        }
        token = checkHasText(path.substring(offset, endIndex), "Token cannot be null or empty");
        return token;
    }

    String path() {
        return path;
    }

    boolean advanceToNextToken() {
        if (nextSplit == -1) {
            return false;
        }
        token = null;
        int oldNextSplit = nextSplit;
        nextSplit = StringUtil.indexOf(path, '.', oldNextSplit + 1);
        offset = oldNextSplit + 1;
        index++;
        return true;
    }

    /**
     * Sets the index to the given index without validating. If the index is out of bound the consecutive token() call
     * will throw a runtime exception.
     *
     * @param indexToNavigateTo value to set the cursor's index to.
     */
    void index(int indexToNavigateTo) {
        this.index = 0;
        this.offset = 0;
        this.nextSplit = StringUtil.indexOf(path, '.', 0);
        this.token = null;

        for (int i = 1; i <= indexToNavigateTo; i++) {
            if (!advanceToNextToken()) {
                throw new IndexOutOfBoundsException("Index out of bound " + indexToNavigateTo + " in " + path);
            }
        }
    }

    int index() {
        return index;
    }

    boolean isAnyPath() {
        return path.contains("[any]");
    }
}
