package lambdas;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import javax.swing.JButton;
import static java.lang.System.*;

/**
 * coin, lambda, and mlvm -dev examples. Examples often taken from Oracle's work-in-progress
 * examples.
 *
 * @author Howard Lovatt
 */
public class Main {
  public static void main( final String... notUsed ) throws Throwable {
    final Main m = new Main();
    // out.println( "\n*** coin-dev ***" );
    // TODO
    out.println( "\n*** lambda-dev ***" );
    m.lambdas();
    m.methodReferences();
    //m.extensionMethods(); - no runtime at present
    m.exceptionTransparency();
    // out.println( "\n*** mlvm-dev ***" );
    // methodHandles()
    // bootstrapMethods()
  }

  private void lambdas() throws Throwable {
    // Syntax #{ 'vars' -> 'body' }, probably final syntax
    // SAM conversion; interface, not abstract class, with a Single Abstract Method
    final Runnable r1 = #{ out.println( toString() ) };
    r1.run();

    // Can have complex bodies as well as 'expressions', seperated by ; and ; on last line
    // See below for alternative
    final Runnable r2 = #{
      int sum = 0;
      for ( int i = 1; i <= 4; i++) { sum += i; }
      out.println( "Sum 4 = " + sum );
    };
    r2.run();

    // Arguments and Target Typing
    // Argument type is ActionEvent
    final ActionListener al1 = #{ notUsed -> out.println( "ActionListener lambda 1 called" ) };
    final JButton b1 = new JButton();
    b1.addActionListener( al1 );
    b1.doClick();
    b1.removeActionListener( al1 );

    // Can type arguments but not return type - see below for alternative and when needed
    final ActionListener al2 = #{ final ActionEvent notUsed ->
      out.println( "ActionListener lambda 2 called" )
    };
    b1.addActionListener( al2 );
    b1.doClick();
    b1.removeActionListener( al2 );

    // More usefully (for me) you can add final.
    final ActionListener al3 = #{ final notUsed ->
      out.println( "ActionListener lambda 3 called" )
    };
    b1.addActionListener( al3 );
    b1.doClick();
    b1.removeActionListener( al3 );

    // Qualify type for overloaded methods with a cast (like normal arguments)
    // submit( Callable ) and submit( Runnable ) exist
    final ExecutorService pool = Executors.newFixedThreadPool( 1 );
    pool.submit( (Callable<Void>) #{ out.println( "Run in background" ) } );
    pool.shutdown();

    // Not a shorthand for an instance of an anonymous inner class, 'this' behaves differently
    // final Runnable r1 = #{ out.println( toString() ) };
    r1.run();
    final Runnable r4 = new Runnable() {
      public void run() { out.println( toString() ); }
    };
    r4.run();

    // Recursive calls, can't directly call itself because of meaning of 'this'
    // You have to explicitly name the inner scope,
    // whereas for inner classes you explicitly name the outer scope.
    // interface IntCallInt { int call( int x ); }
    final IntCallInt fact = #{ int x -> x < 2 ? x : x * fact.call( x - 1 ) };
    out.println( "Factorial 3 = " + fact.call( 3 ) );
    
    // Needs a name
    // new Thread( #{ out.println( NAME.toString() ) } ).start();
    final Runnable r6 = #{ out.println( r6.toString() ) };
    new Thread( r6 ).start();

    // Special case of scoping introduced for lambdas
    // final IntCallInt fact = #{ int x -> x < 2 ? x : x * fact.call( x - 1 ) }; OK
    // int x = 2 * x;
    // lambdas/Main.java:111: variable x might not have been initialized
    //   int x = 2 * x;
    //               ^
    //
    // final IntCallInt fact2 = new IntCallInt() {
    //   int call( int x ) { return x < 2 ? x : x * fact2.call( x - 1 ); }
    // };
    // lambdas/Main.java:112: variable fact2 might not have been initialized
    //   int call( int x ) { return x < 2 ? x : x * fact2.call( x - 1 ); }
    //                                              ^
    

    // Effectively final
    // No use to me, but others like it!
    String s2 = "Effectively final"; // not *declared* final, but effectively final
    final Callable<String> cs = #{ s2 };
    out.println( cs.call() );
  }
  
  private interface IntCallInt { int call( int x ); }
  
  private void methodReferences() {
    // Method References, syntax like Javadoc
    //  private static void print( final ActionEvent notUsed ) {
    //    out.println( "Main.print method called" );
    //  }
    final JButton b1 = new JButton();
    final ActionListener al4 = Main#print( ActionEvent );
    b1.addActionListener( al4 );
    b1.doClick();
    
    // Check identity OK (wasn't in early builds!)
    b1.removeActionListener( al4 );
    b1.doClick();

    // Reciever can ban be bound to an object (references to instance methods)
    class Print {
      void print( final ActionEvent notUsed ) { out.println( "Main.Print.print method called" ); }
    }
    final Print print = new Print();
    b1.addActionListener( print#print( ActionEvent ) );
    b1.doClick();

    // Method References generate a new Object
    b1.removeActionListener( print#print( ActionEvent ) );
    b1.doClick();
    
    // Can also bind this
    //  private void printI( final ActionEvent notUsed ) {
    //    out.println( "Main.printI method called" );
    //  }
    final JButton b2 = new JButton();
    final ActionListener al5 = this#printI( ActionEvent ); // eventually no need for this
    b2.addActionListener( al5 );
    b2.doClick();
    b2.removeActionListener( al5 );
    
    // Grants access to private methods, checks when method handle made.
    // Reflection checks when Method object used.
    //  private void printI( final ActionEvent notUsed ) {
    //    out.println( "Main.printI method called" );
    //  }
    // final ActionListener al5 = this#printI( ActionEvent );
    b2.addActionListener( al5 );
    b2.doClick();
    b2.removeActionListener( al5 );

    // Reference to pre made static objects (not a good example!)
    // Currently a compiler bug prevents reference to static objects
    // out.println( "A - B = " + String.CASE_INSENSITIVE_ORDER#compare( String, String ).compare( "A", "B" ) );

    // this
    final ActionListener al1 = #{ notUsed -> out.println( "ActionListener lambda 1 called" ) };
    out.println( "Class = " + al1.getClass() +
            ", instanceof ActionListener = " + (al1 instanceof ActionListener) +
            ", instance of enclosing class (Main) = " + (al1 instanceof Main) );
    final ActionListener al2 = Main#print( ActionEvent );
    out.println( "Class = " + al2.getClass() +
            ", instanceof ActionListener = " + (al2 instanceof ActionListener) +
            ", instance of enclosing class (Main) = " + (al2 instanceof Main) );
  }

  private static void print( final ActionEvent notUsed ) {
    out.println( "Main.print method called" );
  }

  private void printI( final ActionEvent notUsed ) {
    out.println( "Main.printI method called" );
  }

  private void extensionMethods() {
    final IntList11 il = new IntList11();
    out.println( il );
    il.sort();
    out.println( il );
  }

  private void exceptionTransparency() {
    // No exception
    final IntList11 il = new IntList11();
    out.println( il );
    il.forEach( #{ Integer i -> 2 * i } ); // No exception; no try block - needs type parameter
    out.println( il );

    // Unchecked exception
    il.forEach( #{ Integer i ->
      if ( i < 0 ) { throw new RuntimeException(); }
      return i * i;
    } );
    out.println( il );

    // Exception
    try {
      il.forEach( #{ Integer i ->
        if ( i <= 4 ) { throw new Exception(); }
        return i * i;
      } ); // Need to catch the checked exception!
    } catch ( final Throwable notUsed ) {
      out.println( "Exception caught 1" );
    }
    out.println( il );

    // Can just throw an exception:
    try {
      il.forEach( #{ notUsed -> throw new Exception(); } ); // Need to catch the checked exception!
    } catch (final Throwable notUsed) {
      out.println( "Exception caught 2" );
    }
  }
}