import java.util.*;
import java.util.zip.*;
import java.util.List;
import java.util.regex.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.table.*;
import java.io.*;
import java.net.*;
import java.lang.reflect.*;
import java.lang.ref.*;
import java.lang.management.*;
import java.security.*;
import java.security.spec.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.imageio.*;
import java.math.*;
import javax.swing.event.AncestorListener;
import javax.swing.event.AncestorEvent;
import javax.swing.Timer;
import javax.swing.Timer;
public class main {


public static void main(String[] args) throws Exception {
  forwardClicks_windowSize = 10; // test behavior in task bar
  forwardClicks_trackingSpeed = 100;
  //forwardClicks_verbose = true;
  forwardClicks(new Object() { void get(Point from, Point to, int button) { 
    print("yo " + from + " " + to);
   }
  public String toString() { return "print(\"yo \" + from + \" \" + to);"; }});
}

static int forwardClicks_windowSize = 11; // odd should look nice. Set to 1 for an invisible window
static int forwardClicks_clickDelay = 0; // Delay in ms between closing window and forwarding click. 0 seems to work fine.
static int forwardClicks_dragDelay = 100; // Delay in ms when dragging
static int forwardClicks_trackingSpeed = 10; // How often to move the window (ms)
static int forwardClicks_restartDelay = 0; // How quickly to restart after click
static boolean forwardClicks_verbose;

// onClick: voidfunc(Point, Point, int) - press location, release location, button (1 to 3)
static void forwardClicks(final Object onClickAndRelease) {
  swingNowOrLater(new Runnable() { public void run() { try { 
    final JWindow window = new JWindow();
    final int windowSize = forwardClicks_windowSize;
    window.setSize(windowSize, windowSize);
    window.setVisible(true);
    window.setAlwaysOnTop(true);
    JPanel panel = singleColorPanel(Color.red);
    window.setContentPane(panel);
    revalidate(window);
    final Robot robot = new Robot();
    panel.addMouseListener(new MouseAdapter() {
      Point pressed;
      
      public void mousePressed(final MouseEvent e) {
        pressed = getMouseLocation();
      }
      
      public void mouseReleased(final MouseEvent e) {
        window.setVisible(false);
        final Point released = getMouseLocation();
        final Point _pressed = or(pressed, released);
        final int b = e.getButton();
        final int mod =
          b == 1 ? InputEvent.BUTTON1_DOWN_MASK
          : b == 2 ? InputEvent.BUTTON2_DOWN_MASK
          : InputEvent.BUTTON3_DOWN_MASK;
        swingLater(forwardClicks_clickDelay, new Runnable() { public void run() { try { 
          pcallF(onClickAndRelease, _pressed, released, b);
          robot.mouseMove(_pressed.x, _pressed.y);
          robot.mousePress(mod);
          swingLater(neq(_pressed, released) ? forwardClicks_dragDelay : 0, new Runnable() { public void run() { try { 
            robot.mouseMove(released.x, released.y);
            robot.mouseRelease(mod);
          
            // Start tracking again - make new JWindow otherwise we get a crazy item on task bar in Linux
            swingLater(forwardClicks_restartDelay, new Runnable() { public void run() { try { 
              forwardClicks(onClickAndRelease);
            
} catch (Exception __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); } } });
          
} catch (Exception __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); } } });
        
} catch (Exception __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); } } });
      }
    });
    
    swingEvery(window, forwardClicks_trackingSpeed, new Runnable() { public void run() { try { 
      final Point p = getMouseLocation();
      // Expand height if in task bar (crude fix for Peppermint Linux)
      int bottom = getScreenHeight()-(768-743);
      final Point loc = new Point(p.x-windowSize/2, min(bottom, p.y-windowSize/2));
      int h = max(windowSize, p.y-loc.y+windowSize/2);
      window.setBounds(loc.x, loc.y, windowSize, h);

      if (forwardClicks_verbose) {
        Point onScreen = window.getLocationOnScreen();
        print(loc + " " + onScreen);
      }
      /*if (neq(onScreen, loc))
        print("Location not honored");*/
    
} catch (Exception __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); } } });
  
} catch (Exception __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); } } });
}
static volatile StringBuffer local_log = new StringBuffer(); // not redirected
static volatile StringBuffer print_log = local_log; // might be redirected, e.g. to main bot

// in bytes - will cut to half that
static volatile int print_log_max = 1024*1024;
static volatile int local_log_max = 100*1024;
//static int print_maxLineLength = 0; // 0 = unset

static boolean print_silent; // total mute if set

static void print() {
  print("");
}

// slightly overblown signature to return original object...
static <A> A print(A o) {
  ping();
  if (print_silent) return o;
  String s = String.valueOf(o) + "\n";
  print_noNewLine(s);
  return o;
}

static void print_noNewLine(String s) {
  // TODO if (print_maxLineLength != 0)
  StringBuffer loc = local_log;
  StringBuffer buf = print_log;
  int loc_max = print_log_max;
  if (buf != loc && buf != null) {
    print_append(buf, s, print_log_max);
    loc_max = local_log_max;
  }
  if (loc != null) 
    print_append(loc, s, loc_max);
  System.out.print(s);
}

static void print(long l) {
  print(String.valueOf(l));
}

static void print(char c) {
  print(String.valueOf(c));
}

static void print_append(StringBuffer buf, String s, int max) {
  synchronized(buf) {
    buf.append(s);
    max /= 2;
    if (buf.length() > max) try {
      int newLength = max/2;
      int ofs = buf.length()-newLength;
      String newString = buf.substring(ofs);
      buf.setLength(0);
      buf.append("[...] ").append(newString);
    } catch (Exception e) {
      buf.setLength(0);
    }
  }
}


static void swingEvery(JComponent component, long delay, Runnable r) {
  installTimer(component, delay, r);
}

static void swingEvery(JComponent component, long delay, long firstDelay, Runnable r) {
  installTimer(component, r, delay, firstDelay);
}

static void swingEvery(RootPaneContainer frame, long delay, Runnable r) {
  installTimer(frame, delay, r);
}
static JPanel singleColorPanel(Color color) {
  JPanel p = new JPanel();
  p.setOpaque(true);
  p.setBackground(color);
  return p;
}
static <A> A or(A a, A b) {
  return a != null ? a : b;
}
static int max(int a, int b) {
  return Math.max(a, b);
}

static int max(int a, int b, int c) {
  return max(max(a, b), c);
}

static long max(int a, long b) {
  return Math.max((long) a, b);
}

static long max(long a, long b) {
  return Math.max(a, b);
}

static double max(int a, double b) {
  return Math.max((double) a, b);
}

static float max(float a, float b) {
  return Math.max(a, b);
}

static int max(Collection<Integer> c) {
  int x = Integer.MIN_VALUE;
  for (int i : c) x = max(x, i);
  return x;
}

static double max(double[] c) {
  if (c.length == 0) return Double.MIN_VALUE;
  double x = c[0];
  for (int i = 1; i < c.length; i++) x = Math.max(x, c[i]);
  return x;
}

static byte max(byte[] c) {
  byte x = -128;
  for (byte d : c) if (d > x) x = d;
  return x;
}
static volatile boolean ping_pauseAll;
static int ping_sleep = 100; // poll pauseAll flag every 100
static volatile boolean ping_anyActions;
static Map<Thread, Object> ping_actions = synchroMap(new WeakHashMap());

// returns true if it did anything
static boolean ping() { try {
 
  if (ping_pauseAll && !isAWTThread()) {
    do
      Thread.sleep(ping_sleep);
    while (ping_pauseAll);
    return true;
  }
  
  if (ping_anyActions) {
    Object action;
    synchronized(mc()) {
      action = ping_actions.get(currentThread());
      if (action instanceof Runnable)
        ping_actions.remove(currentThread());
      if (ping_actions.isEmpty()) ping_anyActions = false;
    }
    
    if (action instanceof Runnable)
      ((Runnable) action).run();
    else if (eq(action, "cancelled"))
      throw fail("Thread cancelled.");
  }
  
  return false;

} catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }}
static Object pcallF(Object f, Object... args) {
  return pcallFunction(f, args);
}
static int getScreenHeight() {
  return getScreenSize().height;
}
static int min(int a, int b) {
  return Math.min(a, b);
}

static long min(long a, long b) {
  return Math.min(a, b);
}

static double min(double a, double b) {
  return Math.min(a, b);
}

static double min(double[] c) {
  double x = Double.MAX_VALUE;
  for (double d : c) x = Math.min(x, d);
  return x;
}

static byte min(byte[] c) {
  byte x = 127;
  for (byte d : c) if (d < x) x = d;
  return x;
}
static void revalidate(Component c) {
  if (c == null || !c.isShowing()) return;
  // magic combo to actually relayout and repaint
  c.revalidate();
  c.repaint();
}
static void swingLater(int delay, final Runnable r) {
  javax.swing.Timer timer = new javax.swing.Timer(delay, new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent _evt) {
    r.run();
  }});
  timer.setRepeats(false);
  timer.start();
}

static void swingLater(Runnable r) {
  SwingUtilities.invokeLater(r);
}

static Point getMouseLocation() {
  return MouseInfo.getPointerInfo().getLocation();
}

static boolean neq(Object a, Object b) {
  return !eq(a, b);
}


static Map synchroMap() {
  return synchroHashMap();
}

static <A, B> Map<A, B> synchroMap(Map<A, B> map) {
  return Collections.synchronizedMap(map);
}
static Thread currentThread() {
  return Thread.currentThread();
}
static boolean eq(Object a, Object b) {
  if (a == null) return b == null;
  if (a.equals(b)) return true;
  if (a instanceof BigInteger) {
    if (b instanceof Integer) return a.equals(BigInteger.valueOf((Integer) b));
    if (b instanceof Long) return a.equals(BigInteger.valueOf((Long) b));
  }
  return false;
}
  static RuntimeException fail() {
    throw new RuntimeException("fail");
  }
  
  static RuntimeException fail(Object msg) {
    throw new RuntimeException(String.valueOf(msg));
  }
  
  static RuntimeException fail(String msg) {
    throw new RuntimeException(unnull(msg));
  }
   
  // disabled for now to shorten some programs 
  /*static RuntimeException fail(S msg, O... args) {
    throw new RuntimeException(format(msg, args));
  }*/
static Object mc() {
  return getMainClass();
}
static Object pcallFunction(Object f, Object... args) {
  try { /* pcall 1*/  return callFunction(f, args); /* pcall 2 */ } catch (Throwable __e) { printStackTrace(__e); } return null;
}

// first delay = delay
static void installTimer(JComponent component, Runnable r, long delay) {
  installTimer(component, r, delay, delay);
}

// first delay = delay
static void installTimer(RootPaneContainer frame, long delay, Runnable r) {
  installTimer(frame.getRootPane(), r, delay, delay);
}

// first delay = delay
static void installTimer(JComponent component, long delay, Runnable r) {
  installTimer(component, r, delay, delay);
}

static void installTimer(JComponent component, long delay, long firstDelay, Runnable r) {
  installTimer(component, r, delay, firstDelay);
}

static void installTimer(final JComponent component, final Runnable r, final long delay, final long firstDelay) {
  installTimer(component, r, delay, firstDelay, true);
}

static void installTimer(final JComponent component, final Runnable r, final long delay, final long firstDelay, final boolean repeats) {
  swingLater(new Runnable() { public void run() { try { 
    final Timer timer = new Timer(toInt(delay), new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent _evt) {
      try { /* pcall 1*/ 
        if (!allPaused())
          r.run();
      /* pcall 2 */ } catch (Throwable __e) { printStackTrace(__e); }
    }});
    timer.setInitialDelay(toInt(firstDelay));
    timer.setRepeats(repeats);
    bindTimerToComponent(timer, component);
  
} catch (Exception __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); } } });
}

static void installTimer(JFrame frame, long delay, long firstDelay, Runnable r) {
  installTimer(frame.getRootPane(), r, delay, firstDelay);
}


static boolean isAWTThread() {
  return isTrue(callOpt(getClass("javax.swing.SwingUtilities"), "isEventDispatchThread"));
}
static Dimension getScreenSize() {
  return Toolkit.getDefaultToolkit().getScreenSize();
}



static void bindTimerToComponent(final Timer timer, JFrame f) {
  bindTimerToComponent(timer, f.getRootPane());
}

static void bindTimerToComponent(final Timer timer, JComponent c) {
  if (c.isShowing())
    timer.start();
  
  c.addAncestorListener(new AncestorListener() {
    public void ancestorAdded(AncestorEvent event) {
      timer.start();
    }

    public void ancestorRemoved(AncestorEvent event) {
      timer.stop();
    }

    public void ancestorMoved(AncestorEvent event) {
    }
  });
}
static Class<?> getClass(String name) {
  try {
    return Class.forName(name);
  } catch (ClassNotFoundException e) {
    return null;
  }
}

static Class getClass(Object o) {
  return o instanceof Class ? (Class) o : o.getClass();
}

static Class getClass(Object realm, String name) { try {
 
  try {
    return getClass(realm).getClassLoader().loadClass(classNameToVM(name));
  } catch (ClassNotFoundException e) {
    return null;
  }

} catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }}
static Object callOpt(Object o) {
  if (o == null) return null;
  return callF(o);
}

static Object callOpt(Object o, String method, Object... args) {
  try {
    if (o == null) return null;
    if (o instanceof Class) {
      Method m = callOpt_findStaticMethod((Class) o, method, args, false);
      if (m == null) return null;
      m.setAccessible(true);
      return m.invoke(null, args);
    } else {
      Method m = callOpt_findMethod(o, method, args, false);
      if (m == null) return null;
      m.setAccessible(true);
      return m.invoke(o, args);
    }
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}

static Method callOpt_findStaticMethod(Class c, String method, Object[] args, boolean debug) {
  Class _c = c;
  while (c != null) {
    for (Method m : c.getDeclaredMethods()) {
      if (debug)
        System.out.println("Checking method " + m.getName() + " with " + m.getParameterTypes().length + " parameters");;
      if (!m.getName().equals(method)) {
        if (debug) System.out.println("Method name mismatch: " + method);
        continue;
      }

      if ((m.getModifiers() & Modifier.STATIC) == 0 || !callOpt_checkArgs(m, args, debug))
        continue;

      return m;
    }
    c = c.getSuperclass();
  }
  return null;
}

static Method callOpt_findMethod(Object o, String method, Object[] args, boolean debug) {
  Class c = o.getClass();
  while (c != null) {
    for (Method m : c.getDeclaredMethods()) {
      if (debug)
        System.out.println("Checking method " + m.getName() + " with " + m.getParameterTypes().length + " parameters");;
      if (m.getName().equals(method) && callOpt_checkArgs(m, args, debug))
        return m;
    }
    c = c.getSuperclass();
  }
  return null;
}

private static boolean callOpt_checkArgs(Method m, Object[] args, boolean debug) {
  Class<?>[] types = m.getParameterTypes();
  if (types.length != args.length) {
    if (debug)
      System.out.println("Bad parameter length: " + args.length + " vs " + types.length);
    return false;
  }
  for (int i = 0; i < types.length; i++)
    if (!(args[i] == null || isInstanceX(types[i], args[i]))) {
      if (debug)
        System.out.println("Bad parameter " + i + ": " + args[i] + " vs " + types[i]);
      return false;
    }
  return true;
}


static String unnull(String s) {
  return s == null ? "" : s;
}

static <A> List<A> unnull(List<A> l) {
  return l == null ? emptyList() : l;
}

static <A> Iterable<A> unnull(Iterable<A> i) {
  return i == null ? emptyList() : i;
}

static Object[] unnull(Object[] a) {
  return a == null ? new Object[0] : a;
}

static BitSet unnull(BitSet b) {
  return b == null ? new BitSet() : b;
}
static Object callFunction(Object f, Object... args) {
  if (f == null) return null;
  if (f instanceof Runnable) {
    ((Runnable) f).run();
    return null;
  } else if (f instanceof String)
    return call(mc(), (String) f, args);
  else
    return call(f, "get", args);
  //else throw fail("Can't call a " + getClassName(f));
}
static Class getMainClass() {
  return main.class;
}

static Class getMainClass(Object o) { try {
 
  return (o instanceof Class ? (Class) o : o.getClass()).getClassLoader().loadClass("main");

} catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }}
static int toInt(Object o) {
  if (o == null) return 0;
  if (o instanceof Number)
    return ((Number) o).intValue();
  if (o instanceof String)
    return parseInt((String) o);
  throw fail("woot not int: " + getClassName(o));
}

static int toInt(long l) {
  if (l != (int) l) throw fail("Too large for int: " + l);
  return (int) l;
}
static boolean allPaused() {
  return ping_pauseAll;
}
// hmm, this shouldn't call functions really. That was just
// for coroutines.
static boolean isTrue(Object o) {
  if (o instanceof Boolean)
    return ((Boolean) o).booleanValue();
  if (o == null) return false;
  return ((Boolean) callF(o)).booleanValue();
}

static boolean isTrue(Object pred, Object arg) {
  return booleanValue(callF(pred, arg));
}
static Map synchroHashMap() {
  return Collections.synchronizedMap(new HashMap());
}



static Object callF(Object f, Object... args) {
  return callFunction(f, args);
}
static List emptyList() {
  return new ArrayList();
  //ret Collections.emptyList();
}
// extended over Class.isInstance() to handle primitive types
static boolean isInstanceX(Class type, Object arg) {
  if (type == boolean.class) return arg instanceof Boolean;
  if (type == int.class) return arg instanceof Integer;
  if (type == long.class) return arg instanceof Long;
  if (type == float.class) return arg instanceof Float;
  if (type == short.class) return arg instanceof Short;
  if (type == char.class) return arg instanceof Character;
  if (type == byte.class) return arg instanceof Byte;
  if (type == double.class) return arg instanceof Double;
  return type.isInstance(arg);
}
static String classNameToVM(String name) {
  return name.replace(".", "$");
}
  static Object call(Object o) {
    return callFunction(o);
  }
  
  // varargs assignment fixer for a single string array argument
  static Object call(Object o, String method, String[] arg) {
    return call(o, method, new Object[] {arg});
  }
  
  static Object call(Object o, String method, Object... args) {
    try {
      if (o instanceof Class) {
        Method m = call_findStaticMethod((Class) o, method, args, false);
        m.setAccessible(true);
        return m.invoke(null, args);
      } else {
        Method m = call_findMethod(o, method, args, false);
        m.setAccessible(true);
        return m.invoke(o, args);
      }
    } catch (Exception e) {
      throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e);
    }
  }

  static Method call_findStaticMethod(Class c, String method, Object[] args, boolean debug) {
    Class _c = c;
    while (c != null) {
      for (Method m : c.getDeclaredMethods()) {
        if (debug)
          System.out.println("Checking method " + m.getName() + " with " + m.getParameterTypes().length + " parameters");;
        if (!m.getName().equals(method)) {
          if (debug) System.out.println("Method name mismatch: " + method);
          continue;
        }

        if ((m.getModifiers() & Modifier.STATIC) == 0 || !call_checkArgs(m, args, debug))
          continue;

        return m;
      }
      c = c.getSuperclass();
    }
    throw new RuntimeException("Method '" + method + "' (static) with " + args.length + " parameter(s) not found in " + _c.getName());
  }

  static Method call_findMethod(Object o, String method, Object[] args, boolean debug) {
    Class c = o.getClass();
    while (c != null) {
      for (Method m : c.getDeclaredMethods()) {
        if (debug)
          System.out.println("Checking method " + m.getName() + " with " + m.getParameterTypes().length + " parameters");;
        if (m.getName().equals(method) && call_checkArgs(m, args, debug))
          return m;
      }
      c = c.getSuperclass();
    }
    throw new RuntimeException("Method '" + method + "' (non-static) with " + args.length + " parameter(s) not found in " + o.getClass().getName());
  }

  private static boolean call_checkArgs(Method m, Object[] args, boolean debug) {
    Class<?>[] types = m.getParameterTypes();
    if (types.length != args.length) {
      if (debug)
        System.out.println("Bad parameter length: " + args.length + " vs " + types.length);
      return false;
    }
    for (int i = 0; i < types.length; i++)
      if (!(args[i] == null || isInstanceX(types[i], args[i]))) {
        if (debug)
          System.out.println("Bad parameter " + i + ": " + args[i] + " vs " + types[i]);
        return false;
      }
    return true;
  }


static int parseInt(String s) {
  return empty(s) ? 0 : Integer.parseInt(s);
}
static boolean booleanValue(Object o) {
  return eq(true, o);
}
static String getClassName(Object o) {
  return o == null ? "null" : o.getClass().getName();
}


static boolean empty(Collection c) {
  return isEmpty(c);
}

static boolean empty(String s) {
  return isEmpty(s);
}

static boolean empty(Map map) {
  return map == null || map.isEmpty();
}

static boolean empty(Object[] o) {
  return o == null || o.length == 0;
}

static boolean empty(Object o) {
  if (o instanceof Collection) return empty((Collection) o);
  if (o instanceof String) return empty((String) o);
  if (o instanceof Map) return empty((Map) o);
  if (o instanceof Object[]) return empty((Object[]) o);
  throw fail("unknown type for 'empty': " + getType(o));
}


static boolean isEmpty(Collection c) {
  return c == null || c.isEmpty();
}

static boolean isEmpty(CharSequence s) {
  return s == null || s.length() == 0;
}

static boolean isEmpty(Object[] a) {
  return a == null || a.length == 0;
}

static boolean isEmpty(Map map) {
  return map == null || map.isEmpty();
}
static String getType(Object o) {
  return getClassName(o);
}


static void printStackTrace(Throwable e) {
  // we go to system.out now - system.err is nonsense
  print(getStackTrace(e));
}

static void printStackTrace() {
  printStackTrace(new Throwable());
}

static void printStackTrace(String indent, Throwable e) {
  if (endsWithLetter(indent)) indent += " ";
  printIndent(indent, getStackTrace(e));
}
static void swingNowOrLater(Runnable r) {
  if (isAWTThread())
    r.run();
  else
    swingLater(r);
}


static void printIndent(Object o) {
  print(indentx(str(o)));
}

static void printIndent(String indent, Object o) {
  print(indentx(indent, str(o)));
}

static void printIndent(int indent, Object o) {
  print(indentx(indent, str(o)));
}
static String getStackTrace(Throwable throwable) {
  StringWriter writer = new StringWriter();
  throwable.printStackTrace(new PrintWriter(writer));
  return writer.toString();
}
static boolean endsWithLetter(String s) {
  return nempty(s) && isLetter(last(s));
}


static String indentx(String s) {
  return indentx(indent_default, s);
}

static String indentx(int n, String s) {
  return dropSuffix(repeat(' ', n), indent(n, s));
}

static String indentx(String indent, String s) {
  return dropSuffix(indent, indent(indent, s));
}
static <A> A last(List<A> l) {
  return l.isEmpty() ? null : l.get(l.size()-1);
}

static char last(String s) {
  return empty(s) ? '#' : s.charAt(l(s)-1);
}
static String str(Object o) {
  return String.valueOf(o);
}
static boolean isLetter(char c) {
  return Character.isLetter(c);
}
static boolean nempty(Collection c) {
  return !isEmpty(c);
}

static boolean nempty(CharSequence s) {
  return !isEmpty(s);
}

static boolean nempty(Object[] o) {
  return !isEmpty(o);
}

static boolean nempty(Map m) {
  return !isEmpty(m);
}

static boolean nempty(Iterator i) {
  return i != null && i.hasNext();
}


static int indent_default = 2;

static String indent(int indent) {
  return repeat(' ', indent);
}

static String indent(int indent, String s) {
  return indent(repeat(' ', indent), s);
}

static String indent(String indent, String s) {
  return indent + s.replace("\n", "\n" + indent);
}

static String indent(String s) {
  return indent(indent_default, s);
}

static List<String> indent(String indent, List<String> lines) {
  List<String> l = new ArrayList<String>();
  for (String s : lines)
    l.add(indent + s);
  return l;
}
  static String repeat(char c, int n) {
    n = max(n, 0);
    char[] chars = new char[n];
    for (int i = 0; i < n; i++)
      chars[i] = c;
    return new String(chars);
  }

  static <A> List<A> repeat(A a, int n) {
    List<A> l = new ArrayList<A>();
    for (int i = 0; i < n; i++)
      l.add(a);
    return l;
  }

static String dropSuffix(String suffix, String s) {
  return s.endsWith(suffix) ? s.substring(0, l(s)-l(suffix)) : s;
}
static int l(Object[] a) { return a == null ? 0 : a.length; }
static int l(boolean[] a) { return a == null ? 0 : a.length; }
static int l(byte[] a) { return a == null ? 0 : a.length; }
static int l(int[] a) { return a == null ? 0 : a.length; }
static int l(float[] a) { return a == null ? 0 : a.length; }
static int l(char[] a) { return a == null ? 0 : a.length; }
static int l(Collection c) { return c == null ? 0 : c.size(); }
static int l(Map m) { return m == null ? 0 : m.size(); }
static int l(CharSequence s) { return s == null ? 0 : s.length(); } 

static int l(Object o) {
  return l((List) o); // incomplete
}


}