package com.xceptance.neodymium.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.yaml.snakeyaml.Yaml;

public class YamlProperties
{
    /**
     * Our properties that will hit the outside world later
     */
    private final Properties properties = new Properties();

    /**
     * Don't publish the constructor
     */
    private YamlProperties()
    {
    }

    /**
     * Build us a property key from the current state on the stack
     *
     * @param state
     *            the stack
     * @return a property key
     */
    private String buildKey(final Deque<String> state)
    {
        final Iterator<String> i = state.descendingIterator();

        final StringBuilder sb = new StringBuilder(64);
        while (i.hasNext())
        {
            if (sb.length() > 0)
            {
                sb.append('.');
            }
            sb.append(i.next());
        }

        return sb.toString();
    }

    /**
     * Handle all remaining, hopefully primitive types
     *
     * @param entry
     *            the entry
     * @param state
     *            the current state of the stack
     */
    private void handleTypes(final Object entry, final Deque<String> state)
    {
        if (entry != null)
        {
            properties.setProperty(buildKey(state), entry.toString());
        }
        else
        {
            properties.setProperty(buildKey(state), "");
        }
    }

    /**
     * Handle lists
     *
     * @param list
     *            the list to deal with
     * @param state
     *            the current state
     */
    @SuppressWarnings("unchecked")
    private void list(final List<Object> list, final Deque<String> state)
    {
        for (int i = 0; i < list.size(); i++)
        {
            final Object entry = list.get(i);
            state.push(String.valueOf(i));

            if (entry instanceof Map)
            {
                map((Map<String, Object>) entry, state);
            }
            else if (entry instanceof List)
            {
                list((List<Object>) entry, state);
            }
            else
            {
                handleTypes(entry, state);
            }

            state.pop();
        }
    }

    /**
     * Deal with maps
     *
     * @param map
     *            the map of data
     * @param state
     *            the current state
     */
    @SuppressWarnings("unchecked")
    private void map(final Map<String, Object> map, final Deque<String> state)
    {
        for (final String key : map.keySet())
        {
            state.push(key);

            final Object value = map.get(key);

            if (value instanceof Map)
            {
                map((Map<String, Object>) value, state);
            }
            else if (value instanceof List)
            {
                list((List<Object>) value, state);
            }
            else
            {
                handleTypes(value, state);
            }

            state.pop();
        }
    }

    /**
     * Just do the magic
     *
     * @param map
     *            the map to convert
     */
    private void yamlToJavaProperties(final Map<String, Object> map)
    {
        map(map, new ArrayDeque<String>());
    }

    /**
     * Parses yaml properties and turns them into regular Java properties. If the file does not exists, it returns null.
     *
     * @param file
     *            a {@link File} for a YAML file
     * @return a Java properties file reader
     */
    public static Properties build(final File file)
    {
        try (final Reader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8"))))
        {
            final Yaml yaml = new Yaml();

            final Map<String, Object> map = yaml.load(reader);

            if (map == null)
            {
                // the localization file is empty
                return null;
            }

            final YamlProperties yamlProperties = new YamlProperties();
            yamlProperties.yamlToJavaProperties(map);

            return yamlProperties.properties;
        }
        catch (final FileNotFoundException e)
        {
            return null;
        }
        catch (final IOException e)
        {
            return null;
        }
    }
}