package org.twopm.laf;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Set;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import javax.swing.UIManager;
import javax.swing.text.AttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.NbPreferences;

/**
 * @author Edward M. Kagan <pagan@tabor.one>
 */
@SuppressWarnings ({"rawtypes","unchecked"})
public class TitanEditor {
    
    private static final String COLOR_MODEL_CLASS_NAME     = "org.netbeans.modules.options.colors.ColorModel"; //NOI18N
    private static final String EDITOR_SETTINGS_CLASS_NAME = "org.netbeans.api.editor.settings.EditorStyleConstants"; //NOI18N
    private static final String ANNOTATION_TYPE_CLASS_NAME = "org.netbeans.editor.AnnotationType"; //NOI18N
    
    private static final String PROVIDER_CLASS_NAME      = "org.netbeans.modules.versioning.util.OptionsPanelColorProvider"; //NOI18N
    private static final String PROVIDER_GIT_CLASS_NAME  = "org.netbeans.modules.git.GitModuleConfig"; //NOI18N
    private static final String PROVIDER_SVN_CLASS_NAME  = "org.netbeans.modules.subversion.SvnModuleConfig"; //NOI18N
    private static final String PROVIDER_HG_CLASS_NAME   = "org.netbeans.modules.mercurial.HgModuleConfig"; //NOI18N
    private static final String PROVIDER_DIFF_CLASS_NAME = "org.netbeans.modules.diff.DiffModuleConfig"; //NOI18N

    private static final boolean no_load           = false;
    private static final String DEFAULT_THEME_NAME = "NetBeans 5.5";   //NOI18N
    
    private static ClassLoader working_classLoader = null;
    
    private static Class COLOR_MODEL;
    private static Class EDITOR_SETTINGS;
    private static Class ANNOTATION_TYPE;
    
    private static Field WaveUnderlineColor;

    private static Method getName;
    
    private static Method getCurrentProfile;
    private static Method setCurrentProfile;

    private static Method isUseHighlightColor;
    private static Method isInheritForegroundColor;
    private static Method isUseWaveUnderlineColor;
    
    private static Method getHighlight;
    private static Method getForegroundColor;
    private static Method getWaveUnderlineColor;
                
    private static Method setHighlight;
    private static Method setForegroundColor;
    private static Method setWaveUnderlineColor;
    

    private static Method getAnnotations;
    private static Method getHighlightings;
    private static Method getCategories;
    
    private static Method setAnnotations;
    private static Method setHighlightings;
    private static Method setCategories;
    
    private static Method getLanguages;

    private static Object const_WaveUnderlineColor;
    private static Object colorModel;
       
    private static Class provider_GIT = null;
    private static Class provider_SVN = null;
    private static Class provider_HG  = null;
    private static Class provider_CLASS  = null;
    private static Class provider_DIFF  = null;
    private static Method provider_getDefault;
    private static Method provider_getPreferences;
    

    private static void extractDiffModule () throws IllegalAccessException
    {
        if (provider_DIFF != null)
        {            
            HashMap <String, Integer> linkMap = new HashMap<String, Integer>();
        
            java.awt.Color nb_diff_added_color            = (java.awt.Color) UIManager.get ("nb.diff.added.color");
            java.awt.Color nb_diff_changed_color          = (java.awt.Color) UIManager.get ("nb.diff.changed.color");
            java.awt.Color nb_diff_deleted_color          = (java.awt.Color) UIManager.get ("nb.diff.deleted.color");
            java.awt.Color nb_diff_applied_color          = (java.awt.Color) UIManager.get ("nb.diff.applied.color");
            java.awt.Color nb_diff_notapplied_color       = (java.awt.Color) UIManager.get ("nb.diff.notapplied.color");
            java.awt.Color nb_diff_unresolved_color       = (java.awt.Color) UIManager.get ("nb.diff.unresolved.color");
            java.awt.Color nb_diff_sidebar_deleted_color  = (java.awt.Color) UIManager.get ("nb.diff.sidebar.deleted.color");
            java.awt.Color nb_diff_sidebar_changed_color  = (java.awt.Color) UIManager.get ("nb.diff.sidebar.changed.color");

            String[] keys = 
            {
                "addedColor",
                "changedColor",
                "deletedColor",
                "merge.appliedColor",
                "merge.notappliedColor",
                "merge.unresolvedColor",
                "sidebar.changedColor",
                "sidebar.deletedColor"
            };

            linkMap.put("addedColor"            , nb_diff_added_color != null ? nb_diff_added_color.getRGB() : java.awt.Color.GREEN.getRGB());
            linkMap.put("changedColor"          , nb_diff_changed_color != null ? nb_diff_changed_color.getRGB() : java.awt.Color.BLUE.getRGB());
            linkMap.put("deletedColor"          , nb_diff_deleted_color != null ? nb_diff_deleted_color.getRGB() : java.awt.Color.RED.getRGB());
            linkMap.put("merge.appliedColor"    , nb_diff_applied_color != null ? nb_diff_applied_color.getRGB() : java.awt.Color.YELLOW.getRGB());
            linkMap.put("merge.notappliedColor" , nb_diff_notapplied_color != null ? nb_diff_notapplied_color.getRGB() : java.awt.Color.ORANGE.getRGB());
            linkMap.put("merge.unresolvedColor" , nb_diff_unresolved_color != null ? nb_diff_unresolved_color.getRGB() : java.awt.Color.RED.getRGB());
            linkMap.put("sidebar.changedColor"  , nb_diff_sidebar_deleted_color != null ? nb_diff_sidebar_deleted_color.getRGB() : java.awt.Color.CYAN.getRGB());
            linkMap.put("sidebar.deletedColor"  , nb_diff_sidebar_changed_color != null ? nb_diff_sidebar_changed_color.getRGB() : java.awt.Color.DARK_GRAY.getRGB());
            
        
            String res = "// <editor-fold desc=\"DIFF\" defaultstate=\"collapsed\">" +
                "\npublic static final org.twopm.laf.Color[] DIFF = {\n";
            
            try 
            {                    
                Object defaults          = provider_getDefault.invoke(null, new Object[0]);
                Preferences preferences  = (Preferences) provider_getPreferences.invoke(defaults, new Object[0]);
                for (int i = 0; i < keys.length; i ++)
                {
                    java.awt.Color c = new java.awt.Color(preferences.getInt(keys[i], linkMap.get(keys[i])));
                    
                    res += "    " + (new Color(keys[i], Color.ColorClass.AT, 
                                        c.getRed(), 
                                        c.getGreen(), 
                                        c.getBlue(), 
                                        c.getAlpha(), "")).getString();
                    
                    if (i != keys.length - 1)
                    {
                        res += ", \n";
                    }
                    else
                    {
                        res += "\n";
                    }
                }
            } 
            catch (InvocationTargetException ex) {} 
            
            res += "};\n";
            res += "// </editor-fold>\n";
            System.out.println(res);
        }
    }

    private static void applyColorForDiffModule () throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
    {
        Object defaults          = provider_getDefault.invoke(null, new Object[0]);
        Preferences preferences  = (Preferences) provider_getPreferences.invoke(defaults, new Object[0]);
        
        for (org.twopm.laf.Color cc : ColorTheme.DIFF)
        {
            preferences.putInt(cc.getPname(), ((java.awt.Color)cc.getObject()).getRGB());
        }
    }
    
    private static boolean pre_load ()
    {
        working_classLoader = Lookup.getDefault().lookup( ClassLoader.class );
        if( null != working_classLoader )
        {
            try {
 
                COLOR_MODEL                 = working_classLoader.loadClass( COLOR_MODEL_CLASS_NAME );
                EDITOR_SETTINGS             = working_classLoader.loadClass(EDITOR_SETTINGS_CLASS_NAME);
                ANNOTATION_TYPE             = working_classLoader.loadClass(ANNOTATION_TYPE_CLASS_NAME);
                
                getName                     = ANNOTATION_TYPE.getDeclaredMethod("getName",  new Class[0]);
                
                WaveUnderlineColor          = EDITOR_SETTINGS.getField("WaveUnderlineColor"); //NOI18N

                getCurrentProfile           = COLOR_MODEL.getDeclaredMethod( "getCurrentProfile", new Class[0] ); //NOI18N
                setCurrentProfile           = COLOR_MODEL.getDeclaredMethod( "setCurrentProfile", String.class ); //NOI18N
 
                getHighlight                = ANNOTATION_TYPE.getDeclaredMethod( "getHighlight", new Class[0] ); //NOI18N
                getForegroundColor          = ANNOTATION_TYPE.getDeclaredMethod( "getForegroundColor", new Class[0] ); //NOI18N
                getWaveUnderlineColor       = ANNOTATION_TYPE.getDeclaredMethod( "getWaveUnderlineColor", new Class[0] ); //NOI18N
                
                setHighlight                = ANNOTATION_TYPE.getDeclaredMethod( "setHighlight", java.awt.Color.class ); //NOI18N
                setForegroundColor          = ANNOTATION_TYPE.getDeclaredMethod( "setForegroundColor", java.awt.Color.class ); //NOI18N
                setWaveUnderlineColor       = ANNOTATION_TYPE.getDeclaredMethod( "setWaveUnderlineColor", java.awt.Color.class ); //NOI18N
                
                isUseHighlightColor         = ANNOTATION_TYPE.getDeclaredMethod( "isUseHighlightColor", new Class[0] ); //NOI18N
                isInheritForegroundColor    = ANNOTATION_TYPE.getDeclaredMethod( "isInheritForegroundColor", new Class[0] ); //NOI18N
                isUseWaveUnderlineColor     = ANNOTATION_TYPE.getDeclaredMethod( "isUseWaveUnderlineColor", new Class[0] ); //NOI18N
               
                getAnnotations              = COLOR_MODEL.getDeclaredMethod( "getAnnotations", String.class ); //NOI18N
                getHighlightings            = COLOR_MODEL.getDeclaredMethod( "getHighlightings", String.class ); //NOI18N
                getCategories               = COLOR_MODEL.getDeclaredMethod( "getCategories",String.class, String.class ); //NOI18N
                
                setAnnotations              = COLOR_MODEL.getDeclaredMethod( "setAnnotations", String.class, Collection.class ); //NOI18N
                setHighlightings            = COLOR_MODEL.getDeclaredMethod( "setHighlightings", String.class, Collection.class ); //NOI18N
                setCategories               = COLOR_MODEL.getDeclaredMethod( "setCategories",String.class, String.class, Collection.class ); //NOI18N
                
                getLanguages                = COLOR_MODEL.getDeclaredMethod( "getLanguages", new Class[0] ); //NOI18N

                const_WaveUnderlineColor    = WaveUnderlineColor.get(EDITOR_SETTINGS);
                colorModel                  = COLOR_MODEL.newInstance();
                
                provider_CLASS              = working_classLoader.loadClass(PROVIDER_CLASS_NAME);
                provider_DIFF               = working_classLoader.loadClass(PROVIDER_DIFF_CLASS_NAME);
                
                if (provider_DIFF != null)
                {
                    provider_getDefault     = provider_DIFF.getDeclaredMethod( "getDefault", new Class[0] ); //NOI18N
                    provider_getPreferences = provider_DIFF.getDeclaredMethod( "getPreferences", new Class[0] ); //NOI18N
                }
                
                if (provider_CLASS != null)
                {
                    try 
                    {
                        provider_HG = working_classLoader.loadClass(PROVIDER_HG_CLASS_NAME);
                    } 
                    catch (ClassNotFoundException ex) {}

                    try 
                    {
                        provider_SVN = working_classLoader.loadClass(PROVIDER_SVN_CLASS_NAME);
                    } 
                    catch (ClassNotFoundException ex) {}

                    try 
                    {
                        provider_GIT = working_classLoader.loadClass(PROVIDER_GIT_CLASS_NAME);
                    } 
                    catch (ClassNotFoundException ex) {}
                }
   
                return true;
            } catch (ClassNotFoundException ex) {
                Exceptions.printStackTrace(ex);
            } catch (NoSuchFieldException ex) {
                Exceptions.printStackTrace(ex);
            } catch (SecurityException ex) {
                Exceptions.printStackTrace(ex);
            } catch (NoSuchMethodException ex) {
                Exceptions.printStackTrace(ex);
            } catch (IllegalArgumentException ex) {
                Exceptions.printStackTrace(ex);
            } catch (IllegalAccessException ex) {
                Exceptions.printStackTrace(ex);
            } catch (InstantiationException ex) {
                Exceptions.printStackTrace(ex);
            }
        }
        return false;
    }
    
    private static void extractColorsForModule (String name, Class provider) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
    {
        
        String res = "// <editor-fold desc=\"" +name + "\" defaultstate=\"collapsed\">" +
                "\npublic static final org.twopm.laf.Color[] " + name + " = {\n";
        
        if (provider != null)
        {
            Preferences forModule = NbPreferences.forModule(provider);
            try 
            {
                for (String key : forModule.keys())
                {
                    java.awt.Color cc = new java.awt.Color(Integer.parseInt(forModule.get(key, null)));
                    res += "    " + (new Color(key, Color.ColorClass.AT, 
                                            cc.getRed(), 
                                            cc.getGreen(), 
                                            cc.getBlue(), 
                                            cc.getAlpha(), "")).getString() + ",\n";
                }
            } 
            catch (BackingStoreException ex) {}
        }
        res += "};\n";
        res += "// </editor-fold>\n";
        
        System.out.println(res);
    }
    
    private static void applyColorForModule (Class provider, org.twopm.laf.Color[] colors_theme) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
    {
        if (provider != null)
        {
            Preferences forModule = NbPreferences.forModule(provider);
            for (org.twopm.laf.Color cc : colors_theme)
            {
                java.awt.Color toSet = (java.awt.Color) cc.getObject();
                forModule.put(cc.getPname(), String.valueOf(toSet.getRGB()));   
            }          
        }
    }
    
    public static void load_editor (boolean first_run)
    {
        if (!pre_load()) return;
        
        try {    
            
            String swith_back = (String) getCurrentProfile.invoke( colorModel, new Object[0] ); 
            
            if (no_load)
            {
                Collection<AttributeSet> annotations = (Collection<AttributeSet>) getAnnotations.invoke(colorModel, DEFAULT_THEME_NAME);
                process_set ("annotations", annotations, true);
                
                
                Collection<AttributeSet> highlightings = (Collection<AttributeSet>) getHighlightings.invoke(colorModel, DEFAULT_THEME_NAME);
                process_set ("highlightings", highlightings, false);

                Object languages = getLanguages.invoke( colorModel, new Object[0] );
                Set<String> languages_set = (Set<String>) languages;

                for (String str : languages_set)
                {
                    if (str.equals("All Languages") ||
                        str.equals("Java") ||
                        str.equals("C++") ||
                        str.equals("C"))
                    {
                        Collection<AttributeSet> category = (Collection<AttributeSet>) getCategories.invoke(colorModel, DEFAULT_THEME_NAME, str);
                        process_set (str, category, false);
                    }   
                }
                
                extractColorsForModule ("GIT", provider_GIT);
                extractColorsForModule ("SVN", provider_SVN);
                extractColorsForModule ("HG", provider_HG);
                
                extractDiffModule();
            }
            else
            {
                setCurrentProfile.invoke(colorModel, DEFAULT_THEME_NAME);
                Collection<AttributeSet> annotations = (Collection<AttributeSet>) getAnnotations.invoke(colorModel, DEFAULT_THEME_NAME);
                Collection<AttributeSet> annotations_upd = apply_set ("annotations", annotations, true);
                setAnnotations.invoke(colorModel, DEFAULT_THEME_NAME, annotations_upd);

                Collection<AttributeSet> highlightings = (Collection<AttributeSet>) getHighlightings.invoke(colorModel, DEFAULT_THEME_NAME);
                Collection<AttributeSet> highlightings_upd = apply_set ("highlightings", highlightings, false);
                setHighlightings.invoke(colorModel, DEFAULT_THEME_NAME, highlightings_upd);

                Object languages = getLanguages.invoke( colorModel, new Object[0] );
                Set<String> languages_set = (Set<String>) languages;

                for (String str : languages_set)
                {
                    if (str.equals("All Languages") ||
                        str.equals("Java") ||
                        str.equals("C++") ||
                        str.equals("C"))
                    {
                        Collection<AttributeSet> category = (Collection<AttributeSet>) getCategories.invoke(colorModel, DEFAULT_THEME_NAME, str);
                        Collection<AttributeSet> category_upd = apply_set (str, category, false);
                        setCategories.invoke(colorModel, DEFAULT_THEME_NAME, str, category_upd);

                    }   
                }

                if (!first_run)
                {
                    setCurrentProfile.invoke(colorModel, swith_back);
                }
                else
                {
                    applyColorForModule (provider_GIT, ColorTheme.GIT);
                    applyColorForModule (provider_SVN, ColorTheme.SVN);
                    applyColorForModule (provider_HG,  ColorTheme.HG);

                    applyColorForDiffModule ();
                }
            } 
        } catch (IllegalAccessException ex) {
            Exceptions.printStackTrace(ex);
        } catch (IllegalArgumentException ex) {
            Exceptions.printStackTrace(ex);
        } catch (InvocationTargetException ex) {
            Exceptions.printStackTrace(ex);
        } 
    }
    
    private static Collection<AttributeSet> apply_set(String set_name, Collection<AttributeSet> data, boolean annotation) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
    {
        LinkedList<AttributeSet> res = new LinkedList<AttributeSet>();
        
        for (AttributeSet at : data) 
        {
            HashMap <String, AttributeSetConfigured> cluster = ColorTheme.color_map.get(set_name);
            if (cluster != null)
            {
                String name = "BAD ASS";
                if (annotation)
                {
                    Object annotationType = at.getAttribute("annotationType");
                    name = (String) getName.invoke(annotationType, new Object[0]);
                }
                else
                {
                    name = (String) at.getAttribute(javax.swing.text.StyleConstants.NameAttribute);
                }
              
                AttributeSetConfigured asc = cluster.get(name);
                if (asc != null)
                {            
                    if (!name.equals("default"))
                    {
                        res.add(apply_colors(asc, at, annotation));
                    }
                    else
                    {
                        res.add(apply_text_size_fix(apply_colors(asc, at, annotation)));
                    }
                }
            }
        }
        return res;
    }
    
    private static SimpleAttributeSet apply_text_size_fix (SimpleAttributeSet old)
    {
         if (old.isDefined(StyleConstants.FontSize))
         {
             old.removeAttribute(StyleConstants.FontSize);
         }
         old.addAttribute(StyleConstants.FontSize, 18);  
         return old;
    }
    
    private static SimpleAttributeSet apply_colors (AttributeSetConfigured asc, AttributeSet at, boolean annotation) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
    { 
        SimpleAttributeSet sas = new SimpleAttributeSet(at);

        if (sas.isDefined(StyleConstants.Background)) sas.removeAttribute(StyleConstants.Background);
        if (sas.isDefined(StyleConstants.Foreground)) sas.removeAttribute(StyleConstants.Foreground);
        if (sas.isDefined(const_WaveUnderlineColor))  sas.removeAttribute(const_WaveUnderlineColor);

        if (sas.isDefined(javax.swing.text.StyleConstants.FontConstants.Bold))  
            sas.removeAttribute(javax.swing.text.StyleConstants.FontConstants.Bold);

        if (sas.isDefined(javax.swing.text.StyleConstants.FontConstants.Italic))  
            sas.removeAttribute(javax.swing.text.StyleConstants.FontConstants.Italic);

        if (asc.bgColor != null) sas.addAttribute(StyleConstants.Background, asc.bgColor);
        if (asc.fgColor != null) sas.addAttribute(StyleConstants.Foreground, asc.fgColor);

        if (sas.isDefined(const_WaveUnderlineColor))
        {
            sas.removeAttribute(const_WaveUnderlineColor);
            if (asc.underColor != null) sas.addAttribute(const_WaveUnderlineColor, asc.underColor);
        }

        Object annotationType = at.getAttribute("annotationType");
        if (annotationType != null)
        {
            if (annotation)
            {   
                Boolean v_isUseHighlightColor = (Boolean) isUseHighlightColor.invoke(annotationType, new Object[0]);
                Boolean v_isInheritForegroundColor = (Boolean) isInheritForegroundColor.invoke(annotationType, new Object[0]);
                Boolean v_isUseWaveUnderlineColor = (Boolean) isUseWaveUnderlineColor.invoke(annotationType, new Object[0]);
                
                if (v_isUseHighlightColor)
                {
                    setHighlight.invoke(annotationType, asc.a_getHighlight);
                }

                if (v_isInheritForegroundColor)
                {
                    setForegroundColor.invoke(annotationType, asc.a_getForegroundColor);
                }

                if (v_isUseWaveUnderlineColor)
                {   
                    setWaveUnderlineColor.invoke(annotationType, asc.a_getWaveUnderlineColor);
                }
            }
        }
        
        return sas;
    }
    
    private static void process_set(String set_name, Collection<AttributeSet> data, boolean annotation) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException 
    {
        System.out.println("\n// <editor-fold desc=\"" +set_name + "\" defaultstate=\"collapsed\">");
        LinkedList<AttributeSetConfigured> conf_annotations = new LinkedList<AttributeSetConfigured>();

        for (AttributeSet at : data) 
        {
            //String description;
            String name = "BAD ASS";
            Boolean v_isUseHighlightColor = null;
            Boolean v_isInheritForegroundColor = null;
            Boolean v_isUseWaveUnderlineColor = null;
            java.awt.Color a_getHighlight = null;
            java.awt.Color a_getForegroundColor = null;
            java.awt.Color a_getWaveUnderlineColor = null;
            
            if (annotation)
            {
                Object annotationType = at.getAttribute("annotationType");
                if (annotationType != null)
                {
                    name = (String) getName.invoke(annotationType, new Object[0]);
                    v_isUseHighlightColor = (Boolean) isUseHighlightColor.invoke(annotationType, new Object[0]);
                    v_isInheritForegroundColor = (Boolean) isInheritForegroundColor.invoke(annotationType, new Object[0]);
                    v_isUseWaveUnderlineColor = (Boolean) isUseWaveUnderlineColor.invoke(annotationType, new Object[0]);
                    a_getHighlight = (java.awt.Color) getHighlight.invoke(annotationType, new Object[0]);
                    a_getForegroundColor = (java.awt.Color) getForegroundColor.invoke(annotationType, new Object[0]);
                    a_getWaveUnderlineColor = (java.awt.Color) getWaveUnderlineColor.invoke(annotationType, new Object[0]);
                }
                else
                {
                    System.err.println("ANNOTATION WITHOUT TYPE FOUND " + at);
                }
            }
            else
            {
                name = (String) at.getAttribute(javax.swing.text.StyleConstants.NameAttribute);
            }

            java.awt.Color bgColor = (java.awt.Color) at.getAttribute(StyleConstants.Background);
            java.awt.Color fgColor = (java.awt.Color) at.getAttribute(StyleConstants.Foreground);
            java.awt.Color underColor = (java.awt.Color) at.getAttribute(const_WaveUnderlineColor);

            Boolean bold    = (Boolean) at.getAttribute(javax.swing.text.StyleConstants.FontConstants.Bold);
            Boolean italic = (Boolean) at.getAttribute(javax.swing.text.StyleConstants.FontConstants.Italic);
          
            conf_annotations.add(new AttributeSetConfigured(name,
                                                            bgColor,
                                                            fgColor,
                                                            underColor,
                                                            v_isUseHighlightColor,
                                                            v_isInheritForegroundColor,
                                                            v_isUseWaveUnderlineColor,
                                                            a_getHighlight,
                                                            a_getForegroundColor,
                                                            a_getWaveUnderlineColor,
                                                            bold,
                                                            italic));
        }

        AttributeSetConfigured.compileSet(set_name, conf_annotations);
        System.out.println("// </editor-fold>");
    }

    
}
