package convert.relaxng.output.dtd;

import convert.relaxng.edit.AbstractPatternVisitor;
import convert.relaxng.edit.Annotated;
import convert.relaxng.edit.ChoicePattern;
import convert.relaxng.edit.Component;
import convert.relaxng.edit.ComponentVisitor;
import convert.relaxng.edit.CompositePattern;
import convert.relaxng.edit.Container;
import convert.relaxng.edit.DefineComponent;
import convert.relaxng.edit.DivComponent;
import convert.relaxng.edit.EmptyPattern;
import convert.relaxng.edit.GrammarPattern;
import convert.relaxng.edit.IncludeComponent;
import convert.relaxng.edit.InterleavePattern;
import convert.relaxng.edit.MixedPattern;
import convert.relaxng.edit.NotAllowedPattern;
import convert.relaxng.edit.OneOrMorePattern;
import convert.relaxng.edit.OptionalPattern;
import convert.relaxng.edit.Pattern;
import convert.relaxng.edit.SchemaCollection;
import convert.relaxng.edit.SchemaDocument;
import convert.relaxng.edit.TextPattern;
import convert.relaxng.edit.UnaryPattern;
import convert.util.VoidValue;
import convert.relaxng.edit.ZeroOrMorePattern;

import java.util.Iterator;
import java.util.List;

class Simplifier extends AbstractPatternVisitor<Pattern> implements ComponentVisitor<VoidValue> {
  public static void simplify(SchemaCollection sc) {
    Simplifier simplifier = new Simplifier();
    for (SchemaDocument sd : sc.getSchemaDocumentMap().values())
      sd.setPattern(sd.getPattern().accept(simplifier));
  }

  private Simplifier() {
  }

  public Pattern visitGrammar(GrammarPattern p) {
    visitContainer(p);
    return p;
  }

  public VoidValue visitContainer(Container c) {
    for (Component component : c.getComponents())
      component.accept(this);
    return VoidValue.VOID;
  }


  public VoidValue visitInclude(IncludeComponent c) {
    return visitContainer(c);
  }

  public VoidValue visitDiv(DivComponent c) {
    return visitContainer(c);
  }

  public VoidValue visitDefine(DefineComponent c) {
    c.setBody(c.getBody().accept(this));
    return VoidValue.VOID;
  }

  public Pattern visitChoice(ChoicePattern p) {
    boolean hadEmpty = false;
    List<Pattern> list = p.getChildren();
    for (int i = 0, len = list.size(); i < len; i++)
      list.set(i, list.get(i).accept(this));
    for (Iterator<Pattern> iter = list.iterator(); iter.hasNext();) {
      Pattern child = iter.next();
      if (child instanceof NotAllowedPattern)
        iter.remove();
      else if (child instanceof EmptyPattern) {
        hadEmpty = true;
        iter.remove();
      }
    }
    if (list.size() == 0)
      return copy(new NotAllowedPattern(), p);
    Pattern tem;
    if (list.size() == 1)
      tem = list.get(0);
    else
      tem = p;
    if (hadEmpty && !(tem instanceof OptionalPattern) && !(tem instanceof ZeroOrMorePattern)) {
      if (tem instanceof OneOrMorePattern)
        tem = new ZeroOrMorePattern(((OneOrMorePattern)tem).getChild());
      else
        tem = new OptionalPattern(tem);
      copy(tem, p);
    }
    return tem;
  }

  public Pattern visitComposite(CompositePattern p) {
    List<Pattern> list = p.getChildren();
    for (int i = 0, len = list.size(); i < len; i++)
      list.set(i, list.get(i).accept(this));
    for (Iterator<Pattern> iter = list.iterator(); iter.hasNext();) {
      Pattern child = iter.next();
      if (child instanceof EmptyPattern)
        iter.remove();
    }
    if (list.size() == 0)
      return copy(new EmptyPattern(), p);
    if (list.size() == 1)
      return p.getChildren().get(0);
    return p;
  }


  public Pattern visitInterleave(InterleavePattern p) {
    boolean hadText = false;
    for (Iterator<Pattern> iter = p.getChildren().iterator(); iter.hasNext();) {
      Pattern child = iter.next();
      if (child instanceof TextPattern) {
        iter.remove();
        hadText = true;
      }
    }
    if (!hadText)
      return visitComposite(p);
    return copy(new MixedPattern(visitComposite(p)), p);
  }

  public Pattern visitUnary(UnaryPattern p) {
    p.setChild(p.getChild().accept(this));
    return p;
  }

  private static <T extends Annotated> T copy(T to, T from) {
    to.setSourceLocation(from.getSourceLocation());
    return to;
  }

  public Pattern visitPattern(Pattern p) {
    return p;
  }
}
