/*
 *  Licensed to GraphHopper GmbH under one or more contributor
 *  license agreements. See the NOTICE file distributed with this work for 
 *  additional information regarding copyright ownership.
 * 
 *  GraphHopper GmbH 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 com.graphhopper.search;

import com.graphhopper.storage.DataAccess;
import com.graphhopper.storage.Directory;
import com.graphhopper.storage.Storable;
import com.graphhopper.util.BitUtil;
import com.graphhopper.util.Helper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Ottavio Campana
 * @author Peter Karich
 */
public class NameIndex implements Storable<NameIndex> {
    private static final Logger logger = LoggerFactory.getLogger(NameIndex.class);
    private static final long START_POINTER = 1;
    private final DataAccess names;
    private long bytePointer = START_POINTER;
    // minor optimization for the previous stored name
    private String lastName;
    private long lastIndex;

    public NameIndex(Directory dir) {
        names = dir.find("names");
    }

    @Override
    public NameIndex create(long cap) {
        names.create(cap);
        return this;
    }

    @Override
    public boolean loadExisting() {
        if (names.loadExisting()) {
            bytePointer = BitUtil.LITTLE.combineIntsToLong(names.getHeader(0), names.getHeader(4));
            return true;
        }

        return false;
    }

    /**
     * @return the byte pointer to the name
     */
    public long put(String name) {
        if (name == null || name.isEmpty()) {
            return 0;
        }
        if (name.equals(lastName)) {
            return lastIndex;
        }
        byte[] bytes = getBytes(name);
        long oldPointer = bytePointer;
        names.ensureCapacity(bytePointer + 1 + bytes.length);
        byte[] sizeBytes = new byte[]{
            (byte) bytes.length
        };
        names.setBytes(bytePointer, sizeBytes, sizeBytes.length);
        bytePointer++;
        names.setBytes(bytePointer, bytes, bytes.length);
        bytePointer += bytes.length;
        lastName = name;
        lastIndex = oldPointer;
        return oldPointer;
    }

    private byte[] getBytes(String name) {
        byte[] bytes = null;
        for (int i = 0; i < 2; i++) {
            bytes = name.getBytes(Helper.UTF_CS);
            // we have to store the size of the array into *one* byte
            if (bytes.length > 255) {
                String newName = name.substring(0, 256 / 4);
                logger.info("Way name is too long: " + name + " truncated to " + newName);
                name = newName;
                continue;
            }
            break;
        }
        if (bytes.length > 255) {
            // really make sure no such problem exists
            throw new IllegalStateException("Way name is too long: " + name);
        }
        return bytes;
    }

    public String get(long pointer) {
        if (pointer < 0)
            throw new IllegalStateException("Pointer to access NameIndex cannot be negative:" + pointer);

        // default
        if (pointer == 0)
            return "";

        byte[] sizeBytes = new byte[1];
        names.getBytes(pointer, sizeBytes, 1);
        int size = sizeBytes[0] & 0xFF;
        byte[] bytes = new byte[size];
        names.getBytes(pointer + sizeBytes.length, bytes, size);
        return new String(bytes, Helper.UTF_CS);
    }

    @Override
    public void flush() {
        names.setHeader(0, BitUtil.LITTLE.getIntLow(bytePointer));
        names.setHeader(4, BitUtil.LITTLE.getIntHigh(bytePointer));
        names.flush();
    }

    @Override
    public void close() {
        names.close();
    }

    @Override
    public boolean isClosed() {
        return names.isClosed();
    }

    public void setSegmentSize(int segments) {
        names.setSegmentSize(segments);
    }

    @Override
    public long getCapacity() {
        return names.getCapacity();
    }

    public void copyTo(NameIndex nameIndex) {
        names.copyTo(nameIndex.names);
    }
}
