/* 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.activiti.content.storage.fs;

import java.io.File;
import java.math.BigInteger;

import org.activiti.content.storage.exception.ContentStorageException;

/**
 * Converts between a unique content index and a relative {@link File} path. Uses a nested tree of folders 
 * (depth based on iterationDepth) with a maximum number of children (based on blockSize). The leaves of the
 * trees will be the actual content files. This way, no folder will have more children than the 'blockSize' set,
 * keeping the folders balanced. 
 * 
 * @author Frederik Heremans
 */
public class PathConverter {

    public BigInteger blockSize = BigInteger.valueOf(1024L);
    public int iterationDepth = 4;
    public int blockSizeInt = 1024;
    
    /**
     * @return a path representing the content with the given unique content index.
     */
    public File getPathForId(BigInteger id) {
        BigInteger remainder = null;
        
        if(id.compareTo(BigInteger.ZERO) < 0) {
            throw new ContentStorageException("ID cannot be negative");
        }
        
        Long[] blocks = new Long[iterationDepth];
        for(int i=0; i< iterationDepth; i++) {
            remainder = id.remainder(blockSize);
            blocks[i] = remainder.longValue();
            id = id.subtract(remainder).divide(blockSize);
        }
        
        if(!id.equals(BigInteger.ZERO)) {
            throw new ContentStorageException("ID out of range of content storage limit: " + 
               blockSize.pow(iterationDepth).subtract(BigInteger.ONE));
        }
        
        StringBuffer buffer = new StringBuffer();
        for(int i=iterationDepth - 1; i>=0; i--) {
            buffer.append(blocks[i].toString()).append(File.separatorChar);
        }
        return new File(buffer.toString());
    }
    
    public BigInteger getIdForPath(File path) {
        BigInteger result = BigInteger.ZERO;
        BigInteger currentFactor = BigInteger.ONE;
        int depth = 0;
        File parent = path;
        
        while(parent != null && depth < iterationDepth) {
            try {
                result = result.add(new BigInteger(parent.getName()).multiply(currentFactor));
                
                // Move on to next iteration
                parent = parent.getParentFile();
                depth++;
                currentFactor = currentFactor.multiply(blockSize);
            } catch(NumberFormatException nfe) {
                throw new ContentStorageException("Illegal format of path segment: " + parent.getName(), nfe);
            }
        }
        
        return result;
    }
    
    public void setBlockSize(int blockSize) {
        this.blockSizeInt = blockSize;
        this.blockSize = BigInteger.valueOf(blockSize);
    }
    
    public int getBlockSize() {
        return blockSizeInt;
    }
    
    public void setIterationDepth(int iterationDepth) {
        this.iterationDepth = iterationDepth;
    }
    
    public int getIterationDepth() {
        return iterationDepth;
    }
}