/*
 * ConfigurationExpander.java  $Revision: 1.21 $  $Date: 2007/10/03 04:39:39 $
 *
 * Copyright (C) 2007 Meldcraft Corporation
 *
 * This file is part of Scaffold.
 * 
 * Scaffold is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Scaffold is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Scaffold.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.meldcraft.util;

import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @version $Revision: 1.21 $ $Date: 2007/10/03 04:39:39 $
 * @author Rob Richards
 */
public class ConfigurationExpander
{
    public static final String  BASE_RESOURCE_KEY = "baseResource";
    public static final String  BASE_PROPERTIES_KEY = "baseProperties";
    public static final String  REPLICATE_KEY = "replicateElement";
    public static final String  REPLICATE_LIST = "replicateList";

    protected ConfigurationExpander()
    {
    }

    public static Map getExpandedConfig(Map config)
    {
        return getExpandedConfig(config, "");
    }

    protected static Map getExpandedConfig(Map config, String prefix)
    {
        ArrayList props = new ArrayList();

        // expand propertyRef
        config = new HashMap(config);
        Object[] keys = config.keySet().toArray();
        if ((keys != null) && (keys.length > 0))
        {
            for (int i=0 ; i < keys.length ; i++)
            {
                Object key = keys[i];
                Object val = config.get(key);
                if (val instanceof String)
                {
                    String sval = expandPropertyRef(config, (String)val);
                    config.put(key, sval);
                }
            }
        }

        // expand replicates
        if ((keys != null) && (keys.length > 0))
        {
            String baserep = "." + REPLICATE_KEY;
            for (int i=0 ; i < keys.length ; i++)
            {
                Object key = keys[i];
                if (key instanceof String)
                {
                    String skey = (String)key;
                    if (skey.endsWith(baserep) && (skey.length() > baserep.length()))
                    {
                        String[] repVals = PropertiesHelper.getStringArray(config, (String)skey);
                        if (repVals != null)
                        {
                            String reppre = REPLICATE_LIST + ".";
                            for (int j=0 ; j < repVals.length ; j++)
                            {
                                String[] parts = PropertiesHelper.getStringArray(repVals[j], ";");
                                if (parts != null)
                                {
                                    String[] tmp = new String[parts.length + 1];
                                    tmp[0] = String.valueOf(j);
                                    System.arraycopy(parts,0, tmp,1,parts.length);
                                    parts = tmp;

                                    String rep = skey.substring(0, skey.length() - baserep.length());
                                    Map map = PropertiesHelper.getSubset(config, rep + ".");
                                    Object[] mkeys = map.keySet().toArray();
                                    for (int m=0 ; m < mkeys.length ; m++)
                                    {
                                        String mkey = (String)mkeys[m];
                                        Object val = map.get(mkey);
                                        if (val instanceof String)
                                        {
                                            MessageFormat fmt = new MessageFormat((String)val);
                                            val = fmt.format(parts);
                                        }
                                        MessageFormat fmt = new MessageFormat(mkey);
                                        mkey = fmt.format(parts);
                                        if (mkey.startsWith(reppre) && (mkey.length() > reppre.length()))
                                        {
                                            mkey = prefix + mkey.substring(reppre.length());
                                            String str = (val == null) ? "" : String.valueOf(val);
                                            PropertiesHelper.insertStringArray(config, Integer.MAX_VALUE, mkey, new String[]{str});
                                        }
                                        else if (config.get(/*prefix + */mkey) == null)
                                        {
                                            config.put(/*prefix + */mkey, val);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        // expand baseProperties
        keys = config.keySet().toArray();
        if ((keys != null) && (keys.length > 0))
        {
            String baseprop = "." + BASE_PROPERTIES_KEY;
            for (int i=0 ; i < keys.length ; i++)
            {
                Object key = keys[i];
                if (key instanceof String)
                {
                    String skey = (String)key;
                    if (skey.endsWith(baseprop) && (skey.length() > baseprop.length()))
                    {
                        props.add(skey.substring(0, skey.length() - BASE_PROPERTIES_KEY.length()));
                    }
                }
            }
        }

        HashMap propmap = new HashMap();
        propmap.putAll(config);
        for (int i=0 ; i < props.size() ; i++)
        {
            String prop = (String)props.get(i);
            HashMap expanded = new HashMap();
            HashMap done = new HashMap();
            expandProperties(prop, config, expanded, done);
            PropertiesHelper.addSubset(propmap, expanded, prop);
        }

        ArrayList res = new ArrayList();

        keys = propmap.keySet().toArray();
        if ((keys != null) && (keys.length > 0))
        {
            String baseres = "." + BASE_RESOURCE_KEY;
            for (int i=0 ; i < keys.length ; i++)
            {
                Object key = keys[i];
                if (key instanceof String)
                {
                    String skey = (String)key;
                    if (skey.endsWith(baseres) && (skey.length() > baseres.length()))
                    {
                        res.add(skey.substring(0, skey.length() - BASE_RESOURCE_KEY.length()));
                    }
                }
            }
        }

        HashMap ret = new HashMap();

        String baseRes = PropertiesHelper.getString(propmap, BASE_RESOURCE_KEY, null);
        if (baseRes != null)
        {
            Map map = ConfigurationManager.getConfiguration(baseRes);
            map = expandElementId(map, "");
            Map expanded = getExpandedConfig(map);
            PropertiesHelper.addSubset(ret, expanded, null);
            PropertiesHelper.insertStringArray(propmap, Integer.MAX_VALUE, ConfigurationManager.CONFIGURATION_BASE_NAME, new String[]{baseRes});
        }

        for (int i=0 ; i < res.size() ; i++)
        {
            String key = (String)res.get(i);
            Object obj = propmap.get(key + BASE_RESOURCE_KEY);
            //Map map = ConfigurationManager.getConfiguration(obj);
            Map map = null;
            try
            {
                //map = LayeredProperties.getProperties(ConfigurationExpander.class, getResourceLayers(ConfigurationExpander.class, String.valueOf(obj)));
                map = LayeredProperties.getProperties(ConfigurationExpander.class, new String[] {String.valueOf(obj)});
                if (map == null)
                {
                    map = new HashMap();
                }
                else
                {
                    map = getExpandedConfig(map, prefix + key);
                }
            }
            catch (IOException e1)
            {
            }

            if (map.size() == 0)
            {
                try
                {
                    String resource = String.valueOf(obj).replaceAll("\\.", "/");
                    map = LayeredProperties.getProperties(ConfigurationExpander.class, new String[]{resource});
                }
                catch (IOException e)
                {
                    // ignored
                }
                if (map == null)
                {
                    map = new HashMap();
                }
            }
            map = expandElementId(map, prefix + key);

            Map expanded = getExpandedConfig(map, prefix + key);
            PropertiesHelper.addSubset(ret, expanded, key);
        }
        ret.putAll(propmap);
        return ret;
    }

    protected static String expandPropertyRef(Map config, String value)
    {
        String sval = value;
        String pref = "<propertyRef:";
        String find = pref + "[^>]*?>";
        Pattern pat = Pattern.compile(find);
        Matcher match = pat.matcher(value);
        if (match.find())
        {
            sval = "";
            int end = match.end();
            int oldend = 0;
            do
            {
                end = match.end();
                int start = match.start();
                if (oldend < start)
                {
                    sval += value.substring(oldend, start);
                }
                oldend = end;

                String group = match.group();
                String prop = group.substring(pref.length(), group.length()-1);
                if (prop.indexOf(':') != -1)
                {
                    // check for regex
                    String[] parts = PropertiesHelper.getStringArray(prop, ":");
                    int mods = 0;
                    boolean all = false;
                    if (parts.length == 4)
                    {
                        char[] chars = parts[3].toCharArray();
                        for (int i=0 ; i < chars.length ; i++)
                        {
                            if (chars[i] == 'g')
                            {
                                all = true;
                            }
                            else if (chars[i] == 'm')
                            {
                                mods |= Pattern.MULTILINE;
                            }
                            else if (chars[i] == 's')
                            {
                                mods |= Pattern.DOTALL;
                            }
                            else if (chars[i] == 'i')
                            {
                                mods |= Pattern.CASE_INSENSITIVE;
                            }
                        }
                    }

                    if ((parts.length == 3) || (parts.length == 4))
                    {
                        Pattern rpat = Pattern.compile(parts[1], mods);
                        String mstr = PropertiesHelper.getString(config, parts[0]);
                        Matcher rmat = rpat.matcher(mstr);
                        if (all)
                        {
                            sval += rmat.replaceAll(parts[2]);
                        }
                        else
                        {
                            sval += rmat.replaceFirst(parts[2]);
                        }
                    }
                    else
                    {
                        sval += PropertiesHelper.getString(config, prop);
                    }
                }
                else
                {
                    sval += PropertiesHelper.getString(config, prop);
                }
            }
            while (match.find());
            if (end < value.length())
            {
                sval += value.substring(end);
            }
        }
        return sval;
    }

    protected static void expandProperties(String property, Map config, Map expanded, Map done)
    {
        if (done.get(property) == null)
        {
            done.put(property, property);

            Object obj = config.get(property + BASE_PROPERTIES_KEY);
            if (obj instanceof String)
            {
                String exprop = (String)obj;
                Map subset = PropertiesHelper.getSubset(config, exprop + ".");
                Object val = subset.get(BASE_PROPERTIES_KEY);
                if (val instanceof String)
                {
                    expandProperties(exprop, config, expanded, done);
                }

                expanded.putAll(subset);
            }
        }
    }

    /*
    protected static String[] getResourceLayers(Object context, String resourceName)
        throws IOException
    {
        String[] allLayers = new String[]{resourceName.replace('.', '/')};
        Map map = LayeredProperties.getProperties(context, allLayers);
        if (map != null)
        {
            String[] layers = PropertiesHelper.getStringArray(map, "baseResourceLayers");
            if (layers != null)
            {
                String layer = allLayers[0];
                allLayers = new String[layers.length + 1];
                allLayers[layers.length] = layer;
                for (int i=0 ; i < layers.length ; i++)
                {
                    allLayers[i] = layers[i].replace('.', '/');
                }
            }
        }
        return allLayers;
    }
    */

    private static Map expandElementId(Map map, String elementId)
    {
        map = new HashMap(map);
        Object[] keys = map.keySet().toArray();
        if ((keys != null) && (keys.length > 0))
        {
            for (int i=0 ; i < keys.length ; i++)
            {
                Object key = keys[i];
                Object value = map.get(key);
                if (value instanceof String)
                {
                    value = ((String)value).replaceAll("<elementId>\\.", elementId);
                    if (elementId.length() > 0)
                    {
                        value = ((String)value).replaceAll("<elementId>", elementId.substring(0, elementId.length()-1));
                    }
                    map.put(key, value);
                }
            }
        }

        return map;
    }
}