/*******************************************************************************
 * Copyright (c) 2007 Exadel, Inc. and Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is made available under the terms of the
 * Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Exadel, Inc. and Red Hat, Inc. - initial API and implementation
 ******************************************************************************/ 
package org.jboss.tools.common.model.options.impl;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.net.URLConnection;
import java.text.MessageFormat;

import org.w3c.dom.*;
import org.eclipse.core.runtime.Platform;
import org.jboss.tools.common.model.*;
import org.jboss.tools.common.model.options.*;
import org.jboss.tools.common.model.plugin.ModelPlugin;
import org.jboss.tools.common.model.util.*;
import org.jboss.tools.common.xml.XMLUtilities;
import org.osgi.framework.Bundle;

public class XStudioDataLoaderImpl implements SharableConstants {
    private SharableElement studio = null;

    public static class PreferencesFileException extends XModelException {
		private static final long serialVersionUID = 1L;
		String file;
    	String xmlError;
    	public PreferencesFileException(String file, String xmlError) {
    		super("Preferences file " + file + " has errors: " + xmlError);
    		this.file = file;
    		this.xmlError = xmlError;
    	}

    	public String getFile() {
    		return file;
    	}

    	public String getXMLError() {
    		return xmlError;
    	}
    }

    private XStudioLoaderPeer peer = XStudioLoaderPeer.instance();

    public XStudioDataLoaderImpl() {}

    public void load(XModelObject _studio) {
        peer.init(_studio);
        studio = (SharableElement)_studio;
        peer.setIsLoadingOn(true);
        studio.setScope(PROJECT);
        new SharableLoaderImpl().loadSystemSharable(studio);
        
        XStudioContribution[] cs = XStudioContributions.getContributions();
        for (int i = 0; i < cs.length; i++) {
        	load(LIST[0], cs[i]);
        }
        
        boolean e = false;
        for (int i = 1; i < LIST.length; i++) {
        	File[] fs = peer.getFilesForScope(LIST[i]);
            for (int j = 0; j < fs.length; j++) e = load(LIST[i], fs[j]);
        }
        mergeGeneralToProject((SharableElementImpl)studio, !e);
        peer.setIsLoadingOn(false);
        _studio.setModified(false);
        
        File f = peer.getProjectPreferencesFile();
        if(f != null && f.exists()) {
    		PreferenceImportExport.getInstance().apply(f);
        }
    }

    private boolean load(String scopename, File file) {
    	if(file.isFile()) {
    		try {
    			check(new FileReader(file), file.toString());
    		} catch (IOException e) {
    			ModelPlugin.getDefault().logError(e);
    			return false;
    		}
    	}
        Element element = XMLUtil.getElement(file.getAbsolutePath());
        return load(scopename, element);
    }

    private boolean load(String scopename, XStudioContribution c) {
    	check(new InputStreamReader(c.getInputStream()), c.resource);
        Element element = XMLUtil.getElement(c.getInputStream());
        return load(scopename, element);
    }

    private void check(Reader r, String file) {
		String[] errors = XMLUtil.getXMLErrors(r, false);
		if(errors != null && errors.length > 0) {
			PreferencesFileException exc = new PreferencesFileException(file, errors[0]);
			ModelPlugin.getDefault().logError(exc.getMessage(), exc);
		}
    }

    private boolean load(String scopename, Element element) {
        if(element == null) return false;
        SharableLoaderImpl loader = new SharableLoaderImpl();
        loader.loadSharable(element, studio, scopename);
        return true;
    }

    public boolean save(XModelObject _studio) {
        if(!_studio.isModified()) return true;
        peer.init(_studio);
        studio = (SharableElement)_studio;
        for (int i = 0; i < LIST.length; i++) {
            if(i == 1) saveProject();
        }
        _studio.setModified(false);
        return true;
    }
    
    private void saveProject() {
		File[] fs = peer.getFilesForScope(PROJECT);
		boolean isPaletteModified = studio.getChildByPath("Palette").isModified() || studio.getChildByPath("Icons").isModified(); //$NON-NLS-1$ //$NON-NLS-2$
		boolean isPreferencesModified = studio.getChildByPath(OPTIONS).isModified();
    	if(!fs[0].exists() || isPaletteModified) savePalette(fs[0]);
    	if(!fs[1].exists() || isPreferencesModified) savePreferences(fs[1]);
    }
    
    private void savePalette(File f) {
		if(handleReadOnly(studio, PROJECT, f) != 0) return;
    	save(studio, PROJECT, f, new String[]{"Palette", "XStudioIcons"});
		studio.getChildByPath("Palette").setModified(false); //$NON-NLS-1$
		studio.getChildByPath("Icons").setModified(false); //$NON-NLS-1$
	}

	private void savePreferences(File f) {
		if(handleReadOnly(studio, LIST[1], f) != 0) return;
		save(studio, PROJECT, f, new String[]{OPTIONS});
		studio.getChildByPath(OPTIONS).setModified(false);
	}

    private void save(SharableElement q, String scopename, File f, String[] names) {
        if(f.exists() && (!f.isFile() || !f.canWrite())) return;
        try {
            if(!f.exists()) f.createNewFile();
        } catch (IOException e1) {
        	ModelPlugin.getPluginLog().logError("XStudioDataLoaderImpl:save:Cannot create file:" + e1.getMessage()); //$NON-NLS-1$
            return;
        }
        Element e = XMLUtil.createDocumentElement("dummyroot"); //$NON-NLS-1$
        new SharableLoaderImpl().saveSharable(e, q, scopename);
        NodeList x = e.getElementsByTagName(XSTUDIO);
        if(x == null || x.getLength() == 0) return;
		Element xs = (Element)x.item(0);
		check(xs.getChildNodes(), names);

		removeMobilePalette(xs);

        try {
            XModelObjectLoaderUtil.serialize(xs, f.getAbsolutePath());
        } catch (IOException e2) {
        	ModelPlugin.getPluginLog().logError(e2);
        }
    }
    
    private void check(NodeList l, String[] names) {
    	Node[] ns = new Node[l.getLength()];
    	for (int i = 0; i < ns.length; i++) ns[i] = l.item(i);
		for (int i = 0; i < ns.length; i++) check(ns[i], names);    	
    }    
    private void check(Node n, String[] names) {
    	if(n.getNodeType() != Node.ELEMENT_NODE) return;
    	for (int i = 0; i < names.length; i++)
    	  if(names[i].equals(n.getNodeName())) return;
    	 n.getParentNode().removeChild(n);
    }

    private void removeMobilePalette(Element xs) {
    	Element p = XMLUtilities.getUniqueChild(xs, SharableConstants.PALETTE_ROOT);
    	if(p != null) {
    		NodeList nl = p.getChildNodes();    		
    		for (int i = 0; i < nl.getLength(); i++) {
    			Node n = nl.item(i);
    			if(n instanceof Element && SharableConstants.MOBILE_PALETTE_ROOT.equals(((Element)n).getAttribute(XModelObjectConstants.XML_ATTR_NAME))) {
    				p.removeChild(n);
    				break;
    			}
    		}
    	}
    }

    private void mergeGeneralToProject(SharableElementImpl object, boolean merge_all) {
        object.merge(GENERAL, PROJECT, merge_all);
    }

    private int handleReadOnly(XModelObject o, String scope, File f) {
        if(!PROJECT.equals(scope)) return 1;
        int i = 0;
        while(i == 0 && f.exists() && !f.canWrite())
          i = o.getModel().getService().showDialog("Question",
                getReadOnlyMessage(f), new String[]{"Retry", "Discard"}, null,
                org.jboss.tools.common.model.ServiceDialog.QUESTION);
        return i;
    }

    private String getReadOnlyMessage(File f) {
        return MessageFormat
				.format(
						"File {0} is read-only.\nPlease make it writable to allow for saving options.",
						f.getAbsolutePath());
    }
    
    private InputStream readGeneral() {
    	try {
            Bundle b = Platform.getBundle(ModelPlugin.PLUGIN_ID);
        	URL u = b.getResource("meta/options_general.xml"); //$NON-NLS-1$
            URLConnection c = u.openConnection();
            return c.getInputStream();
    	} catch (IOException e) {
    		ModelPlugin.getPluginLog().logError(e);
    		return null;
    	}
    }

}
