/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.netbeans.modules.javascript2.editor.parser;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.modules.javascript2.editor.Utils;
import org.netbeans.modules.javascript2.json.api.JsonOptionsQuery;
import org.netbeans.modules.javascript2.json.parser.JsonLexer;
import org.netbeans.modules.javascript2.lexer.api.JsTokenId;
import org.netbeans.modules.parsing.api.Snapshot;
import org.netbeans.spi.lexer.MutableTextInput;
import org.netbeans.spi.lexer.TokenHierarchyControl;
import org.openide.cookies.EditorCookie;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.text.NbDocument;
import org.openide.util.Pair;
import org.openide.util.RequestProcessor;
import org.openide.util.WeakListeners;

/**
 *
 * @author Petr Hejl
 */
public class JsonParser extends SanitizingParser<JsonParserResult> implements PropertyChangeListener {

    private static final RequestProcessor LEXER_RP = new RequestProcessor(JsonParser.class);
    private final AtomicReference<Pair<FileObject,JsonOptionsQuery.Result>> options;
    private final Boolean enforcedAllowComments;

    public JsonParser() {
        this(null);
    }

    /*test*/
    JsonParser(Boolean enforcedAllowComments) {
        super(JsTokenId.jsonLanguage());
        this.options = new AtomicReference<>();
        this.enforcedAllowComments = enforcedAllowComments;
    }

    @Override
    protected String getDefaultScriptName() {
        return "json.json"; // NOI18N
    }

    @Override
    protected JsonParserResult parseSource(SanitizingParser.Context ctx, JsErrorManager errorManager) throws Exception {
        final Snapshot snapshot = ctx.getSnapshot();
        final String text = ctx.getSource();
        final FileObject fo = snapshot.getSource().getFileObject();
        final boolean allowComments;
        if (enforcedAllowComments != null) {
            allowComments = enforcedAllowComments;
        } else if (fo != null) {
            JsonOptionsQuery.Result opts = Optional.ofNullable(options.get())
                    .map((p) -> p.second())
                    .orElse(null);
            if (opts == null) {
                opts = JsonOptionsQuery.getOptions(fo);
                if (options.compareAndSet(null, Pair.<FileObject,JsonOptionsQuery.Result>of(fo, opts))) {
                    opts.addPropertyChangeListener(WeakListeners.propertyChange(this, opts));
                }
            }
            allowComments = opts.isCommentSupported();
        } else {
            allowComments = false;
        }
        final JsonLexer lex = new JsonLexer(new ANTLRInputStream(text), allowComments);
        lex.removeErrorListeners(); //Remove default console log listener
        lex.addErrorListener(errorManager);
        final CommonTokenStream tokens = new CommonTokenStream(lex);
        org.netbeans.modules.javascript2.json.parser.JsonParser parser =
                new org.netbeans.modules.javascript2.json.parser.JsonParser(tokens);
        parser.removeErrorListeners();  //Remove default console log listener
        parser.addErrorListener(errorManager);
        return new JsonParserResult(
                snapshot,
                parser.json());
    }

    @NonNull
    @Override
    protected JsonParserResult createErrorResult(Snapshot snapshot) {
        return new JsonParserResult(snapshot, null);
    }

    @Override
    protected String getMimeType() {
        return JsTokenId.JSON_MIME_TYPE;
    }

    @Override
    protected Sanitize getSanitizeStrategy() {
        return Sanitize.NEVER;
    }

    @Override
    public void propertyChange(@NonNull final PropertyChangeEvent evt) {
        if (JsonOptionsQuery.Result.PROP_COMMENT_SUPPORTED.equals(evt.getPropertyName())) {
            //Refresh parser
            fireChange();
            //Refresh lexer.
            //Is there any better way?
            final Optional<FileObject> maybeFile = Optional.ofNullable(options.get())
                    .map((p) -> p.first());
            maybeFile.map((fo) -> {
                        try {
                            return DataObject.find(fo).getLookup().lookup(EditorCookie.class);
                        } catch (DataObjectNotFoundException e)  {
                            return null;
                        }
                    })
                    .map((ec) -> ec.getDocument())
                    .ifPresent((doc) -> {
                        Optional.ofNullable((MutableTextInput) doc.getProperty(MutableTextInput.class))
                                .ifPresent((mti) -> {
                                    final TokenHierarchyControl control = mti.tokenHierarchyControl();
                                    //For sure render in worker to prevent deadlocks
                                    LEXER_RP.execute(()->NbDocument.runAtomic(doc,()->{control.rebuild();}));
                                });
                    });
            //Refresh tasklist
            Utils.refreshTaskListIndex(maybeFile.orElse(null));
        }
    }

}
