/*
 * JBoss, Home of Professional Open Source
 * Copyright 2013, Red Hat, Inc. and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This 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 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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 this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.richfaces.resource.css;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import javax.faces.application.Resource;
import javax.faces.context.FacesContext;

import org.richfaces.el.util.ELUtils;
import org.richfaces.log.Logger;
import org.richfaces.log.RichfacesLogger;
import org.w3c.dom.css.CSSCharsetRule;
import org.w3c.dom.css.CSSFontFaceRule;
import org.w3c.dom.css.CSSImportRule;
import org.w3c.dom.css.CSSMediaRule;
import org.w3c.dom.css.CSSPageRule;
import org.w3c.dom.css.CSSRule;
import org.w3c.dom.css.CSSStyleDeclaration;
import org.w3c.dom.css.CSSStyleRule;
import org.w3c.dom.css.CSSStyleSheet;
import org.w3c.dom.css.CSSUnknownRule;
import org.w3c.dom.stylesheets.MediaList;

/**
 * @author Nick Belaevski
 *
 */
public final class CSSVisitorImpl extends AbstractCSSVisitor {
    private static final String RESOURCE_START_PREFIX = "resource[";
    private static final Logger LOGGER = RichfacesLogger.RESOURCE.getLogger();
    private static final String NEW_LINE = "\r\n";
    private FacesContext facesContext;
    private String encoding;
    private StringBuilder buffer = new StringBuilder();
    private List<String> prefixes = new ArrayList<String>(2);

    public CSSVisitorImpl(FacesContext facesContext) {
        super();
        this.facesContext = facesContext;
    }

    private void appendCSSText(CSSRule rule) {
        String cssText = rule.getCssText().trim();

        // TODO nick - escape values
        if (cssText.length() != 0) {
            buffer.append(cssText);
            buffer.append(NEW_LINE);
        }
    }

    /*
     * private String escape(String cssText) { cssText = cssText.replaceAll("\\\\", "\\\\\\\\"); cssText =
     * cssText.replaceAll("\"", "\\\\\""); cssText = cssText.replaceAll("'", "\\\\'"); return cssText; }
     */

    private void flushPrefixes() {
        if (!prefixes.isEmpty()) {
            for (String prefix : prefixes) {
                buffer.append(prefix);
                buffer.append(" {");
                buffer.append(NEW_LINE);
            }

            prefixes.clear();
        }
    }

    private void flushSuffix() {
        if (prefixes.isEmpty()) {
            buffer.append('}');
            buffer.append(NEW_LINE);
        } else {
            prefixes.remove(prefixes.size() - 1);
        }
    }

    @Override
    public void visitUnknownRule(CSSUnknownRule rule) {
        appendCSSText(rule);
    }

    @Override
    public void visitCharsetRule(CSSCharsetRule rule) {
        encoding = rule.getEncoding();

        appendCSSText(rule);
    }

    @Override
    public void visitImportRule(CSSImportRule rule) {
        // TODO nick - process imported stylesheet?
        String resourceName = rule.getHref();
        String libraryName = null;
        if (ELUtils.isValueReference(resourceName)) {
            if (resourceName.indexOf(RESOURCE_START_PREFIX) == -1) {
                resourceName = facesContext.getApplication().evaluateExpressionGet(facesContext, resourceName, String.class);
            } else {
                int start = resourceName.indexOf(RESOURCE_START_PREFIX) + RESOURCE_START_PREFIX.length();
                int end = resourceName.lastIndexOf("]");
                resourceName = resourceName.substring(start, end);
                resourceName = resourceName.replaceAll("\"", "").replaceAll("'", "").trim();
            }

            if (resourceName.contains(":")) {
                String[] split = resourceName.split(":", 2);
                libraryName = split[0];
                resourceName = split[1];
            }
        }
        Resource imported = facesContext.getApplication().getResourceHandler().createResource(resourceName, libraryName);
        if (imported == null) {
            LOGGER.error("Resource with name " + resourceName + " can't be found.");
            return;
        }
        String toAdd = null;
        try {
            toAdd = convertStreamToString(imported.getInputStream(), this.getEncoding());
        } catch (IOException e) {
            LOGGER.error("Error while importing nested resource with name " + resourceName);
        }
        if (toAdd != null && toAdd.length() > 0) {
            buffer.append(toAdd);
            buffer.append(NEW_LINE);
        } else {
            appendCSSText(rule);
        }
    }

    @Override
    protected void startFontRule(CSSFontFaceRule rule) {
        prefixes.add("@font-face");
    }

    @Override
    protected void endFontRule(CSSFontFaceRule rule) {
        flushSuffix();
    }

    @Override
    protected void startMediaRule(CSSMediaRule rule) {
        MediaList mediaList = rule.getMedia();
        // String mediaText = escape(mediaList.getMediaText());
        String mediaText = mediaList.getMediaText();
        prefixes.add("@media " + mediaText);
    }

    @Override
    protected void endMediaRule(CSSMediaRule rule) {
        flushSuffix();
    }

    @Override
    protected void startPageRule(CSSPageRule rule) {
        // String selectorText = escape(rule.getSelectorText());
        String selectorText = rule.getSelectorText();
        // TODO nick - multiple selectors?
        prefixes.add("@page " + selectorText);
    }

    @Override
    protected void endPageRule(CSSPageRule rule) {
        flushSuffix();
    }

    @Override
    protected void startStyleRule(CSSStyleRule rule) {
        // String selectorText = escape(rule.getSelectorText());
        String selectorText = rule.getSelectorText();
        prefixes.add(selectorText);
    }

    @Override
    protected void endStyleRule(CSSStyleRule rule) {
        flushSuffix();
    }

    @Override
    protected void startStyleSheet(CSSStyleSheet styleSheet) {
    }

    @Override
    protected void endStyleSheet(CSSStyleSheet styleSheet) {
    }

    @Override
    public void visitStyleDeclaration(CSSStyleDeclaration styleDeclaration) {
        for (int j = 0; j < styleDeclaration.getLength(); j++) {
            String propertyName = styleDeclaration.item(j);

            String value = styleDeclaration.getPropertyValue(propertyName).trim();
            String priority = styleDeclaration.getPropertyPriority(propertyName);
            if (ELUtils.isValueReference(value)) {
                value = facesContext.getApplication().evaluateExpressionGet(facesContext, value, String.class);
            }
            if (value.startsWith("\"") && value.endsWith("\"")) {
                value = value.substring(1, value.length() - 1);
            }
            if (value.startsWith("'") && value.endsWith("'")) {
                value = value.substring(1, value.length() - 1);
            }
            if (value.length() != 0 && !value.equals("\"\"") && !value.equals("''")) {
                flushPrefixes();

                // One of properties of selector is not empty
                buffer.append('\t');
                // buffer.append(escape(propertyName));
                buffer.append(propertyName);
                buffer.append(": ");

                // buffer.append(escape(value));
                buffer.append(value);

                if (priority != null && priority.length() != 0) {
                    buffer.append(" !");
                    buffer.append(priority);
                }

                buffer.append(";");
                buffer.append(NEW_LINE);
            }
        }
    }

    public String getEncoding() {
        return encoding;
    }

    public String getCSSText() {
        return buffer.toString();
    }

    public void setEncoding(String encoding) {
        this.encoding = encoding;
    }

    public String convertStreamToString(InputStream is, String encoding) throws IOException {

        if (is != null) {
            StringBuilder sb = new StringBuilder();
            String line;

            try {
                BufferedReader reader = new BufferedReader(new InputStreamReader(is, encoding));
                while ((line = reader.readLine()) != null) {
                    sb.append(line).append(NEW_LINE);
                }
            } finally {
                is.close();
            }
            return sb.toString();
        } else {
            return "";
        }
    }
}