/*
 * Copyright 2003-2014 JetBrains s.r.o.
 *
 * 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 jetbrains.mps.smodel.persistence.def.v9;

import jetbrains.mps.persistence.IndexAwareModelFactory.Callback;
import jetbrains.mps.persistence.xml.XMLPersistence.Indexer;
import jetbrains.mps.smodel.adapter.ids.SLanguageId;
import jetbrains.mps.smodel.persistence.def.XmlFastScanner;
import jetbrains.mps.util.JDOMUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.model.SNodeId;

import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.Reader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Indexer9 implements Indexer{
  private final Callback myConsumer;
  private final IdEncoder myIdEncoder = new IdEncoder();


  public Indexer9(@NotNull Callback consumer) {
    myConsumer = consumer;
  }

  public void index(@NotNull Reader input) throws IOException {
    char[] data = getChars(input);

    XmlFastScanner s = new XmlFastScanner(data);
    int token;
    boolean insideRegistry = false, insideImports = false, underNode = false;
    // <language id="">
    // <concept id="">
    // <import ref="">
    // <ref to="external node" | node="local node">
    final Matcher attrMatcher = Pattern.compile(String.format("\\s(%s|%s|%s|%s)\\s*=\\s*\"([^\"]+)\"", ModelPersistence9.ID, ModelPersistence9.REF, ModelPersistence9.TO, ModelPersistence9.NODE)).matcher("");
    SLanguageId currentLanguage = null;
    while ((token = s.next()) != XmlFastScanner.EOI) {
      if (token != XmlFastScanner.OPEN_TAG && token != XmlFastScanner.SIMPLE_TAG) {
        continue;
      }
      final String tokenName = s.getName();
      if (insideRegistry) {
        if (ModelPersistence9.REGISTRY_LANGUAGE.equals(tokenName) && attrMatcher.reset(s.token()).find()) {
          currentLanguage = myIdEncoder.parseLanguageId(attrMatcher.group(2));
        } else if (ModelPersistence9.REGISTRY_CONCEPT.equals(tokenName) && attrMatcher.reset(s.token()).find()) {
          handleConceptId(currentLanguage, attrMatcher.group(2));
        }
      } else if (insideImports) {
        if (ModelPersistence9.IMPORT.equals(tokenName) && attrMatcher.reset(s.token()).find()) {
          handleModelImportRef(JDOMUtil.unescapeText(attrMatcher.group(2)));
        }
      } else if (underNode) {
        if (ModelPersistence9.NODE_REFERENCE.equals(tokenName) && attrMatcher.reset(s.token()).find()) {
          final String attrValue = JDOMUtil.unescapeText(attrMatcher.group(2));
          final String attr = attrMatcher.group(1);
          if (ModelPersistence9.TO.equals(attr)) {
            handleExternalReference(attrValue);
          } else {
            assert ModelPersistence9.NODE.equals(attr);
            handleLocalReference(attrValue);
          }
        }
      }
      if (s.tagDepth() == 1) {
        if (ModelPersistence9.REGISTRY.equals(tokenName)) {
          insideRegistry = true;
          insideImports = underNode = false;
        } else if (ModelPersistence9.IMPORTS.equals(tokenName)) {
          insideImports = true;
          insideRegistry = underNode = false;
        } else if (ModelPersistence9.NODE.equals(tokenName)) {
          underNode = true;
          insideRegistry = insideImports = false;
        }
      }
    }
  }

  public static char[] getChars(@NotNull Reader input) throws IOException {
    char buf[] = new char[8196];
    CharArrayWriter w = new CharArrayWriter(buf.length * 10);
    int x;
    while ((x = input.read(buf, 0, buf.length)) != -1) {
      w.write(buf, 0, x);
    }
    w.flush();
    w.close();
    return w.toCharArray();
  }

  private void handleConceptId(SLanguageId lang, String conceptId) {
    myConsumer.instances(myIdEncoder.parseConceptId(lang, conceptId));
  }

  private void handleModelImportRef(String modelRef) {
    myConsumer.imports(myIdEncoder.parseModelReference(modelRef));
  }

  private void handleExternalReference(String outerRef) {
    SNodeId nodeId = myIdEncoder.parseExternalNodeReference(outerRef);
    if (nodeId != null) {
      myConsumer.externalNodeRef(nodeId);
    }
  }

  private void handleLocalReference(String localRef) {
    SNodeId nodeId = myIdEncoder.parseLocalNodeReference(localRef);
    if (nodeId != null) {
      myConsumer.localNodeRef(nodeId);
    }
  }
}
