/*
 * xtc - The eXTensible Compiler
 * Copyright (C) 2007 Robert Grimm
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
 * USA.
 */
package xtc.tree;

import java.util.Collection;
import java.util.ArrayList;

import xtc.util.Pair;

/**
 * An annotation capturing source code formatting.
 *
 * @author Robert Grimm
 * @version $Revision: 1.2 $
 */
public abstract class Formatting extends Annotation {

  /** A formatting annotation with one node before the annotated node. */
  static class Before1 extends Formatting {

    Object b1;

    Before1(Object b1, Node node) {
      super(node);
      this.b1 = b1;
    }

    public int size() {
      return 2;
    }

    public Object get(int index) {
      switch (index) {
      case 0:
        return b1;
      case 1:
        return node;
      default:
        throw new IndexOutOfBoundsException("Index: "+index+", Size: 2");
      }
    }

    public Object set(int index, Object value) {
      Object old;

      switch (index) {
      case 0:
        old  = b1;
        b1   = value;
        return old;
      case 1:
        old  = node;
        node = (Node)value;
        return old;
      default:
        throw new IndexOutOfBoundsException("Index: "+index+", Size: 2");
      }
    }

  }

  // =======================================================================

  /** A formatting annotation with one node after the annotated node. */
  static class After1 extends Formatting {

    Object a1;

    After1(Node node, Object a1) {
      super(node);
      this.a1 = a1;
    }

    public int size() {
      return 2;
    }

    public Object get(int index) {
      switch (index) {
      case 0:
        return node;
      case 1:
        return a1;
      default:
        throw new IndexOutOfBoundsException("Index: "+index+", Size: 2");
      }
    }

    public Object set(int index, Object value) {
      Object old;

      switch (index) {
      case 0:
        old  = node;
        node = (Node)value;
        return old;
      case 1:
        old  = a1;
        a1   = value;
        return old;
      default:
        throw new IndexOutOfBoundsException("Index: "+index+", Size: 2");
      }
    }

  }

  // =======================================================================

  /**
   * A formatting annotation with one node before and after the
   * annotated node.
   */
  static class Round1 extends Formatting {

    Object b1;
    Object a1;

    Round1(Object b1, Node node, Object a1) {
      super(node);
      this.b1 = b1;
      this.a1 = a1;
    }

    public int size() {
      return 3;
    }

    public Object get(int index) {
      switch (index) {
      case 0:
        return b1;
      case 1:
        return node;
      case 2:
        return a1;
      default:
        throw new IndexOutOfBoundsException("Index: "+index+", Size: 3");
      }
    }

    public Object set(int index, Object value) {
      Object old;

      switch (index) {
      case 0:
        old  = b1;
        b1   = value;
        return old;
      case 1:
        old  = node;
        node = (Node)value;
        return old;
      case 2:
        old  = a1;
        a1   = value;
        return old;
      default:
        throw new IndexOutOfBoundsException("Index: "+index+", Size: 3");
      }
    }

  }

  // =======================================================================

  /** A generic formatting annotation. */
  static class RoundN extends Formatting {

    private boolean hasNode;
    private ArrayList<Object> before;
    private ArrayList<Object> after;

    public RoundN() {
      hasNode = false;
      before  = new ArrayList<Object>();
      after   = new ArrayList<Object>();
    }

    public boolean hasVariable() {
      return true;
    }

    public void setNode(Node node) {
      this.node = node;
      hasNode   = true;
    }

    public int size() {
      return hasNode ? before.size() + 1 + after.size() : before.size();
    }

    public Object get(int index) {
      if (! hasNode) {
        return before.get(index);
      } else {
        final int size1 = before.size();
        final int size2 = after.size();
        final int total = size1 + 1 + size2;
        
        if (0 <= index) {
          if (index < size1) {
            return before.get(index);
          } else if (index == size1) {
            return node;
          } else if (index < total) {
            return after.get(index - size1 - 1);
          }
        }

        throw new IndexOutOfBoundsException("Index: "+index+", Size: "+total);
      }
    }

    public Object set(int index, Object value) {
      if (! hasNode) {
        return before.set(index, value);
      } else {
        final int size1 = before.size();
        final int size2 = after.size();
        final int total = size1 + 1 + size2;
        
        if (0 <= index) {
          if (index < size1) {
            return before.set(index, value);
          } else if (index == size1) {
            Node old = node;
            node     = (Node)value;
            return old;
          } else if (index < total) {
            return after.set(index - size1 - 1, value);
          }
        }

        throw new IndexOutOfBoundsException("Index: "+index+", Size: "+total);
      }
    }

    public Node add(Object o) {
      if (! hasNode) {
        before.add(o);
      } else {
        after.add(o);
      }
      return this;
    }

    public Node addNode(Node node) {
      if (hasNode) {
        throw new IllegalStateException("Already has annotated node");
      }

      this.node = node;
      hasNode   = true;
      return this;
    }

    public Node add(int index, Object o) {
      if (! hasNode) {
        before.add(index, o);
        return this;

      } else {
        final int size1 = before.size();
        final int size2 = after.size();
        final int total = size1 + 1 + size2;

        if (0 <= index) {
          if (index < size1) {
            before.add(index, o);
            return this;
          } else if (index == size1) {
            throw new IllegalArgumentException("Can't add to annotated node");
          } else if (index < total) {
            after.add(index - size1 - 1, o);
            return this;
          }
        }

        throw new IndexOutOfBoundsException("Index: "+index+", Size: "+total);
      }
    }

    public Node addAll(Pair<?> p) {
      if (! hasNode) {
        p.addTo(before);
      } else {
        p.addTo(after);
      }
      return this;
    }

    public Node addAll(Collection<?> c) {
      if (! hasNode) {
        before.addAll(c);
      } else {
        after.addAll(c);
      }
      return this;
    }

    public Object remove(int index) {
      if (! hasNode) {
        return before.remove(index);

      } else {
        final int size1 = before.size();
        final int size2 = after.size();
        final int total = size1 + 1 + size2;

        if (0 <= index) {
          if (index < size1) {
            return before.remove(index);
          } else if (size1 == index) {
            throw new IllegalArgumentException("Can't remove annotated node");
          } else if (index < total) {
            return after.remove(index - size1 - 1);
          }
        }

        throw new IndexOutOfBoundsException("Index: "+index+", Size: "+total);
      }
    }

  }

  // =======================================================================

  /** Create a new, empty formatting annotation. */
  Formatting() { /* Nothing to do. */ }

  /**
   * Create a new formatting annotation for the specified node.
   *
   * @param node The node.
   */
  Formatting(Node node) {
    this.node = node;
  }

  public boolean hasTraversal() {
    return true;
  }

  // =======================================================================

  /**
   * Create a formatting annotation.  This method returns an
   * annotation supporting generic traversal.
   *
   * @param before The object before the node.
   * @param node The annotated node.
   * @return The formatting annotation.
   */
  public static Formatting before1(Object before, Node node) {
    return new Before1(before, node);
  }

  /**
   * Create a formatting annotation.  This method returns an
   * annotation supporting generic traversal.
   *
   * @param node The annotated node.
   * @param after The object after the node.
   * @return The formatting annotation.
   */
  public static Formatting after1(Node node, Object after) {
    return new After1(node, after);
  }

  /**
   * Create a formatting annotation.  This method returns an
   * annotation supporting generic traversal.
   *
   * @param before The object before the node.
   * @param node The annotated node.
   * @param after The object after the node.
   * @return The formatting annotation.
   */
  public static Formatting round1(Object before, Node node, Object after) {
    return new Round1(before, node, after);
  }

  /**
   * Create a formatting annotation.  This method returns an
   * annotation supporting generic traversal and adding/removing
   * children.  All children added before a call to {@link
   * #addNode(Node)} or {@link #setNode(Node)} are treated as nodes
   * preceding the annotated node, and all children added after such a
   * call are treated as nodes succeeding the annotated node.
   *
   * @return The formatting annotation.
   */
  public static Formatting variable() {
    return new RoundN();
  }

}
