/*
 * 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.apache.uima.ruta.ide.ui.text;

import java.util.ArrayList;
import java.util.List;

import org.apache.uima.ruta.ide.core.RutaNature;
import org.eclipse.dltk.ast.ASTNode;
import org.eclipse.dltk.ast.ASTVisitor;
import org.eclipse.dltk.ast.declarations.ModuleDeclaration;
import org.eclipse.dltk.ast.expressions.StringLiteral;
import org.eclipse.dltk.ast.parser.ISourceParser;
import org.eclipse.dltk.ast.statements.Block;
import org.eclipse.dltk.compiler.env.ModuleSource;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.DLTKLanguageManager;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.SourceParserUtil;
import org.eclipse.dltk.internal.ui.editor.ScriptEditor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.source.ICharacterPairMatcher;

/**
 * Helper class for match pairs of characters.
 */
public final class RutaPairMatcher implements ICharacterPairMatcher {

  // private char[] fPairs;

  private IDocument fDocument;

  private int fOffset;

  private int fStartPos;

  private int fEndPos;

  private int fAnchor;

  private ScriptEditor editor;

  private class PairBlock {
    public PairBlock(int start, int end, char c) {
      this.start = start;
      this.end = end;
      this.c = c;
    }

    int start;

    int end;

    char c;
  }

  private PairBlock[] cachedPairs;

  private long cachedStamp = -1;

  private long cachedHash = -1;

  public RutaPairMatcher(char[] pairs, ScriptEditor editor) {
    // if (pairs == null) {
    // throw new IllegalArgumentException();
    // }

    // fPairs = pairs;
    this.editor = editor;
  }

  private PairBlock[] computePairRanges(final int offset, String contents) {
    ISourceParser pp = null;
    pp = DLTKLanguageManager.getSourceParser(RutaNature.NATURE_ID);
    ModuleDeclaration md = null;// pp.parse(null, contents.toCharArray(),
    // null);
    IModelElement el = this.editor.getInputModelElement();
    if (el != null && el instanceof ISourceModule) {
      md = SourceParserUtil.getModuleDeclaration((ISourceModule) el, null);
    }
    if (md == null) {
      md = (ModuleDeclaration) pp.parse(new ModuleSource(contents), null);
    }
    if (md == null) {
      return new PairBlock[0];
    }
    final List result = new ArrayList();
    try {
      md.traverse(new ASTVisitor() {
        @Override
        public boolean visitGeneral(ASTNode node) throws Exception {
          if (node instanceof StringLiteral) {
            StringLiteral be = (StringLiteral) node;
            result.add(new PairBlock(offset + be.sourceStart(), offset + be.sourceEnd() - 1, '\"'));
            // } else if (node instanceof RutaExecuteExpression) {
            // RutaExecuteExpression be = (RutaExecuteExpression) node;
            // result.add(new PairBlock(offset + be.sourceStart(),
            // offset + be.sourceEnd() - 1, '['));
          } else if (node instanceof Block) {
            Block be = (Block) node;
            result.add(new PairBlock(offset + be.sourceStart(), offset + be.sourceEnd() - 1, '{'));
          }
          return super.visitGeneral(node);
        }
      });
    } catch (Exception e) {
      if (DLTKCore.DEBUG) {
        e.printStackTrace();
      }
    }

    // Iterator i = statements.iterator();
    // while (i.hasNext()) {
    // Statement sst = (Statement) i.next();
    // if (sst instanceof RutaStatement) {
    // RutaStatement statement = (RutaStatement) sst;
    // /*
    // * result.add(new CodeBlock(statement, new Region(offset +
    // * statement.sourceStart(), statement.sourceEnd() -
    // * statement.sourceStart())));
    // */
    // Iterator si = statement.getExpressions().iterator();
    // while (si.hasNext()) {
    // Expression ex = (Expression) si.next();
    // if (ex instanceof RutaBlockExpression) {
    // RutaBlockExpression be = (RutaBlockExpression) ex;
    // try {
    // String newContents = contents.substring(be
    // .sourceStart() + 1, be.sourceEnd() - 1);
    // result.add(new PairBlock(offset + be.sourceStart(),
    // offset + be.sourceEnd() - 1, '{'));
    // PairBlock[] cb = computePairRanges(offset
    // + be.sourceStart() + 1, newContents);
    // for (int j = 0; j < cb.length; j++) {
    // result.add(cb[j]);
    // }
    // } catch (StringIndexOutOfBoundsException e) {
    // }
    // } else if (ex instanceof StringLiteral) {
    // StringLiteral be = (StringLiteral) ex;
    // result.add(new PairBlock(offset + be.sourceStart(),
    // offset + be.sourceEnd() - 1, '\"'));
    // } else if (ex instanceof RutaExecuteExpression) {
    // RutaExecuteExpression be = (RutaExecuteExpression) ex;
    // result.add(new PairBlock(offset + be.sourceStart(),
    // offset + be.sourceEnd() - 1, '['));
    // }
    // }
    // }
    // }
    return (PairBlock[]) result.toArray(new PairBlock[result.size()]);
  }

  /**
   * Fully recalcs pairs for document
   * 
   * @param doc
   * @throws BadLocationException
   */
  private void recalc() throws BadLocationException {
    String content = fDocument.get(0, fDocument.getLength());
    cachedPairs = computePairRanges(0, content);

    if (fDocument instanceof IDocumentExtension4) {
      cachedStamp = ((IDocumentExtension4) fDocument).getModificationStamp();
    } else {
      cachedHash = content.hashCode();
    }
  }

  /**
   * Recalcs pairs for the document, only if it is required
   */
  private void updatePairs() throws BadLocationException {
    if (fDocument instanceof IDocumentExtension4) {
      IDocumentExtension4 document = (IDocumentExtension4) fDocument;

      if (document.getModificationStamp() == cachedStamp) {
        return;
      }

    } else {
      String content = fDocument.get(0, fDocument.getLength());

      if (content.hashCode() == cachedHash) {
        return;
      }
    }

    recalc();
  }

  private static boolean isBrace(char c) {
    return (c == '{' || c == '}' || c == '\"' || c == '[' || c == ']');
  }

  public IRegion match(IDocument document, int offset) {
    if (document == null || offset < 0) {
      throw new IllegalArgumentException();
    }

    try {
      fOffset = offset;
      fDocument = document;

      if (!isBrace(fDocument.getChar(offset))
              && (offset == 0 || !isBrace(fDocument.getChar(offset - 1)))) {
        return null;
      }

      updatePairs();

      if (matchPairsAt() && fStartPos != fEndPos)
        return new Region(fStartPos, fEndPos - fStartPos + 1);
    } catch (BadLocationException e) {
      if (DLTKCore.DEBUG_PARSER)
        e.printStackTrace();
    }

    return null;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.eclipse.jface.text.source.ICharacterPairMatcher#getAnchor()
   */
  public int getAnchor() {
    return fAnchor;
  }

  public void dispose() {
    clear();
    fDocument = null;
  }

  public void clear() {
  }

  private boolean matchPairsAt() {

    fStartPos = -1;
    fEndPos = -1;

    for (int i = 0; i < cachedPairs.length; i++) {
      PairBlock block = cachedPairs[i];

      if (fOffset == block.end + 1) {
        fStartPos = block.start - 1;
        fEndPos = block.start;
        fAnchor = LEFT;
        return true;
      }
      if (fOffset == block.start + 1) {
        fStartPos = block.end - 1;
        fEndPos = block.end;
        fAnchor = LEFT;
        return true;
      }

    }

    return false;
  }
}
