/*
 * 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
 *
 *   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.apache.myfaces.view.facelets.impl;

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

/**
 * Hierarchical counter to generate unique ids.
 * 
 * @author Leonardo Uribe
 *
 */
public class SectionUniqueIdCounter
{
    private List<Section> _counterStack;
    private int _activeSection;
    private final String _prefix;
    private final StringBuilder _builder;
    private char[] _bufferConversion;
    private final int _radix;
    private final String[] _uniqueIdsCache;
    
    public SectionUniqueIdCounter()
    {
        _activeSection = 0;
        _radix = Character.MAX_RADIX;
        _counterStack = new ArrayList<>();
        _counterStack.add(new Section(null, 1, _radix));
        _prefix = null;
        _builder = new StringBuilder(30);
        _bufferConversion = new char[15];
        _uniqueIdsCache = null;
    }
    
    public SectionUniqueIdCounter(String prefix)
    {
        _activeSection = 0;
        _radix = Character.MAX_RADIX;
        _counterStack = new ArrayList<>();
        _counterStack.add(new Section(null, 1, _radix));
        _prefix = prefix;
        _builder = new StringBuilder(30);
        _bufferConversion = new char[15];
        _uniqueIdsCache = null;
    }
    
    public SectionUniqueIdCounter(String prefix, String[] cache)
    {
        _activeSection = 0;
        _radix = Character.MAX_RADIX;
        _counterStack = new ArrayList<>();
        _counterStack.add(new Section(null, 1 ,_radix));
        _prefix = prefix;
        _builder = new StringBuilder(30);
        _bufferConversion = new char[15];
        _uniqueIdsCache = cache;
    }
    
    public SectionUniqueIdCounter(String prefix, int radix)
    {
        _activeSection = 0;
        _radix = radix;
        _counterStack = new ArrayList<>();
        _counterStack.add(new Section(null, 1, _radix));
        _prefix = prefix;
        _builder = new StringBuilder(30);
        _bufferConversion = new char[15];
        _uniqueIdsCache = null;
    }

    /**
     * Creates an array of the generated unique ids for an specified prefix,
     * than can be used later to prevent calculate the same String over and over.
     * 
     * @param prefix
     * @param count
     * @return 
     */
    public static String[] generateUniqueIdCache(String prefix, int count)
    {
        String[] cache = new String[count];
        SectionUniqueIdCounter counter = new SectionUniqueIdCounter(prefix);
        for (int i = 0; i < count ; i++)
        {
            cache[i] = counter.generateUniqueId();
        }
        return cache;
    }

    public String startUniqueIdSection()
    {
        //1. Calculate prefix
        _builder.delete(0, _builder.length());
        
        if (!_counterStack.isEmpty())
        {
            String lastPrefix = _counterStack.get(_counterStack.size() - 1).getPrefix();
            if (lastPrefix != null)
            {
                _builder.append(lastPrefix);
                _builder.append('_');
            }
            appendToBuilder(_counterStack.get(_counterStack.size() - 1).getCounter(),
                _radix, _builder, _bufferConversion);
        }
        
        _counterStack.add(new Section(_builder.toString(),1,_radix));
        _activeSection++;
        return _builder.toString();
    }
    
    public String startUniqueIdSection(String base)
    {
        //1. Calculate prefix
        _builder.delete(0, _builder.length());
        
        if (!_counterStack.isEmpty())
        {
            String lastPrefix = _counterStack.get(_counterStack.size() - 1).getPrefix();
            if (lastPrefix != null)
            {
                _builder.append(lastPrefix);
                _builder.append('_');
            }
            appendToBuilder(_counterStack.get(_counterStack.size() - 1).getCounter() - 1,
                _radix, _builder, _bufferConversion);
        }

        if (base != null && base.length() > 0)
        {
            _builder.append('_');
            _builder.append(base);
        }
        _counterStack.add(new Section(_builder.toString(), 1, _radix));
        _activeSection++;
        return _builder.toString();
    }

    public String generateUniqueId()
    {
        Section activeSection = _counterStack.get(_activeSection);

        if (_activeSection == 0 && _uniqueIdsCache != null)
        {
            long i = activeSection.getCounter();
            if (((int) i) < ((long) _uniqueIdsCache.length))
            {
                activeSection.incrementUniqueId();
                return _uniqueIdsCache[((int) i) - 1];
            }
        }

        return activeSection.generateUniqueId(_prefix);
    }
    
    public void generateUniqueId(StringBuilder builderToAdd)
    {
        _counterStack.get(_activeSection).generateUniqueId(_prefix, builderToAdd);
    }
    
    public void incrementUniqueId()
    {
        _counterStack.get(_activeSection).incrementUniqueId();
    }
    
    public void endUniqueIdSection()
    {
        if (_activeSection <= 0)
        {
            _counterStack.get(_activeSection).generateUniqueId(_prefix);
            return;
        }

        _counterStack.remove(_activeSection);
        _activeSection--;
        _counterStack.get(_activeSection).generateUniqueId(_prefix);
    }
    
    public void endUniqueIdSection(String base)
    {
        if (_activeSection <= 0)
        {
            return;
        }

        _counterStack.remove(_activeSection);
        _activeSection--;
    }
    
    private static class Section
    {
        private String prefix;
        private long counter;
        private final StringBuilder builder;
        private char[] bufferConversion;
        private final int radix;
        
        public Section(String prefix, long counter, int radix)
        {
            super();
            this.prefix = prefix;
            this.counter = counter;
            this.builder = new StringBuilder(30);
            this.bufferConversion = new char[15];
            this.radix = radix;
        }

        public long getCounter()
        {
            return counter;
        }
        
        public void incrementUniqueId()
        {
            this.counter++;
        }

        public void generateUniqueId(String base, StringBuilder builder)
        {
            long i = this.counter;
            this.counter++;

            if (base != null)
            {
                builder.append(base);
            }
            if (this.prefix != null)
            {
                builder.append(this.prefix);
                builder.append('_');
            }
            // By performance reasons, Long.toString is a very expensive
            // operation in this location, because it triggers a new String()
            appendToBuilder(i, radix, builder, bufferConversion);
        }
        
        public String generateUniqueId(String base)
        {
            long i = this.counter;
            this.counter++;
            builder.delete(0, builder.length());
            if (base != null)
            {
                builder.append(base);
            }
            if (this.prefix != null)
            {
                builder.append(this.prefix);
                builder.append('_');
            }
            // By performance reasons, Long.toString is a very expensive
            // operation in this location, because it triggers a new String()
            //_builder.append(Long.toString(i, _radix));
            appendToBuilder(i, radix, builder, bufferConversion);
            return builder.toString();
        }

        public String getPrefix()
        {
            return prefix;
        }
    }
    
    //From Harmony Long.toString(l,radix)
    private static void appendToBuilder(long l, int radix, StringBuilder builder, char[] bufferConversion)
    {
        if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
        {
            radix = 10;
        }
        if (l == 0)
        {
            builder.append('0');
            return;
        }

        int count = 2;
        long j = l;
        boolean negative = l < 0;
        if (!negative)
        {
            count = 1;
            j = -l;
        }
        while ((l /= radix) != 0)
        {
            count++;
        }

        if (bufferConversion.length < count)
        {
            bufferConversion = new char[count];
        }
        int finalCount = count;

        char[] buffer = bufferConversion;
        do 
        {
            int ch = 0 - (int) (j % radix);
            if (ch > 9)
            {
                ch = ch - 10 + 'a';
            }
            else
            {
                ch += '0';
            }
            buffer[--count] = (char) ch;
            j /= radix;
        }
        while (j != 0);
        if (negative)
        {
            buffer[0] = '-';
        }
        for (int i = 0; i < finalCount; i++)
        {
            builder.append(buffer[i]);
        }
    }
}
