/*
 * Copyright 2005 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.drools.template.parser;

import org.drools.util.IoUtils;

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

/**
 * Container for a set of templates (residing in one file). This class will
 * parse the template file.
 */
public class DefaultTemplateContainer implements TemplateContainer {
    private String header;

    private Map<String, Column> columnMap = new HashMap<>();

    private List<Column> columns = new ArrayList<>();

    private Map<String, RuleTemplate> templates = new HashMap<>();

    private boolean replaceOptionals;

    public DefaultTemplateContainer(final String template) {
        this(DefaultTemplateContainer.class.getResourceAsStream(template), true);
    }

    public DefaultTemplateContainer(final InputStream templateStream) {
        this(templateStream, true);
    }

    public DefaultTemplateContainer(final String template, boolean replaceOptionals) {
        this(DefaultTemplateContainer.class.getResourceAsStream(template), replaceOptionals);
    }

    public DefaultTemplateContainer(final InputStream templateStream, boolean replaceOptionals) {
        this.replaceOptionals = replaceOptionals;
        parseTemplate(templateStream);
        validateTemplate();
    }

    private void validateTemplate() {
        if (columns.size() == 0) {
            throw new DecisionTableParseException("Missing header columns");
        }
        if (templates.size() == 0) {
            throw new DecisionTableParseException("Missing templates");
        }

    }

    private void parseTemplate(final InputStream templateStream) {
        try {
            final ColumnFactory cf = new ColumnFactory();
            final BufferedReader templateReader = new BufferedReader(
                    new InputStreamReader(templateStream, IoUtils.UTF8_CHARSET));
            String line;

            boolean inTemplate = false;
            boolean inHeader = false;
            boolean inContents = false;
            boolean inMultiLineComment = false;

            RuleTemplate template = null;
            StringBuilder header = new StringBuilder();
            StringBuilder contents = new StringBuilder();

            while ((line = templateReader.readLine()) != null) {

                if (inMultiLineComment) {
                    int commentEnd = line.indexOf( "*/" );
                    if (commentEnd >= 0) {
                        line = line.substring( commentEnd+2 );
                        inMultiLineComment = false;
                    } else {
                        line = "";
                    }
                } else {
                    int commentStart = line.indexOf( "/*" );
                    if (commentStart >= 0) {
                        int commentEnd = line.indexOf( "*/" );
                        if (commentEnd > commentStart) {
                            line = line.substring( 0, commentStart ) + line.substring( commentEnd+2 );
                        } else {
                            line = line.substring( 0, commentStart );
                            inMultiLineComment = true;
                        }
                    }
                }

                String trimmed = line.trim();
                if (trimmed.length() > 0) {
                    if (trimmed.startsWith("template header")) {
                        inHeader = true;

                    } else if (trimmed.startsWith("template ")) {
                        inTemplate = true;
                        inHeader = false;
                        String quotedName = trimmed.substring(8).trim();
                        quotedName = quotedName.substring(1, quotedName.length() - 1);
                        template = new RuleTemplate(quotedName, this, replaceOptionals );
                        addTemplate(template);

                    } else if (trimmed.startsWith("package ")) {
                        if ( !inHeader ) {
                            throw new DecisionTableParseException(
                                    "Missing header");
                        }
                        inHeader = false;
                        header.append(line).append("\n");

                    } else if (trimmed.startsWith("import ")) {
                        inHeader = false;
                        header.append(line).append("\n");

                    } else if (inHeader) {
                        addColumn(cf.getColumn(trimmed));

                    } else if (!inTemplate) {
                        header.append(line).append("\n");

                    } else if (!inContents && trimmed.startsWith("rule ")) {
                        inContents = true;
                        contents.append(line).append("\n");

                    } else if (trimmed.equals("end template")) {
                        template.setContents(contents.toString());
                        contents.setLength(0);
                        inTemplate = false;
                        inContents = false;

                    } else if (inContents) {
                        contents.append(removeSingleLineComment(line)).append( "\n");

                    } else {
                        template.addColumn(trimmed);
                    }
                }

            }
            if (inTemplate) {
                throw new DecisionTableParseException("Missing end template");
            }
            this.header = header.toString();

        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (templateStream != null) { closeStream(templateStream); }
        }
    }

    private String removeSingleLineComment(String line) {
        int commentStart = line.indexOf( "//" );
        return commentStart < 0 ? line : line.substring( 0, commentStart );
    }

    private void addTemplate(RuleTemplate template) {
        templates.put(template.getName(), template);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.kie.decisiontable.parser.TemplateContainer#getTemplates()
     */
    public Map<String, RuleTemplate> getTemplates() {
        return templates;
    }

    private void addColumn(Column c) {
        columns.add(c);
        columnMap.put(c.getName(), c);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.kie.decisiontable.parser.TemplateContainer#getColumns()
     */
    public Column[] getColumns() {
        return columns.toArray(new Column[columns.size()]);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.kie.decisiontable.parser.TemplateContainer#getHeader()
     */
    public String getHeader() {
        return header;
    }

    private void closeStream(final InputStream stream) {
        try {
            stream.close();
        } catch (final Exception e) {
            System.err.print("WARNING: Wasn't able to "
                             + "correctly close stream for decision table. "
                             + e.getMessage());
        }
    }

    public Column getColumn(final String name) {
        return columnMap.get(name);
    }
}
