/*
  CLASS FunkHOrdnung
  Author : Eshref Januzaj, Bernhard Rumpe
  
  Die folgenden Klassen zeigen exemplarisch die Umsetzung der 
  Gofer-Ausdrücke und Funktionen

        double n = 2*n
        mult a b = a*b
        
        li = [1,2,3,4,5,6,7,8,9,10]
        
        li1 =     map double li
        li2 =     map (double  . double) li
        li3 =     filter odd li
        
        listOfFunctions = [double, mult 4, 
                double . double . double,  (+5), (\x -> 0) ]
        applyListToSingleValue x = map (\f -> f x) listOfFunctions
        
        testApplyListToSingleValue =
            applyListToSingleValue 4 == [8,16,32,9,0]

  nach Java.
  Da beide Sprachen genau Turing-mächtig sind muss das
  prinzipiell möglich sein. Die Umsetzung zeigt, welche Stärken
  von Gofer sich in Java durch komplexere Programme umsetzen lassen,
  wobei in der Umkehrung auch die Stärken von Java hervortreten
  würden.
  
  Zugunsten der Vorführbarkeit in der Vorlesung wird auf die sonst 
  üblichen Codierungsstandards verzichtet.
*/
import java.io.*;
import java.util.*;


  /****************
     Eine Funktion (Object -> Object) wird so "objektifiziert"
   */
  interface Function {
      public Object f(Object i);
  }


  /****************
     Gofer: double n = 2*n
   */
  class Double implements Function {
      public Object f(Object o) {
          return (new Integer(((Integer)o).intValue() * 2));
      }
  }


  /****************
    Gofer: map :: (a-> b) -> [a] -> [b]
    Allerdings etwas vereinfacht, es ist zB nicht möglich 
        nur das erste Argument einzusetzen.
   */
  class Map {      
      public LinkedList map(Function of, LinkedList l){
          
          LinkedList newList = new LinkedList();

	  // Vorsicht: Elemente werden nicht kopiert!
	  for (Iterator it=l.iterator(); it.hasNext(); ) {
	    Object element = it.next();
	    newList.add(of.f(element));
	  }
          return newList;
      }
  }
     
  /****************
    Gofer: (.) die Funktionskomposition
   */
  class Compose implements Function {
      Function fa, fb;
      
      public Compose(Function a, Function b) {
          fa = a; 
          fb = b; 
      }
      
      public Object f (Object o) {            
          return( fb.f( fa.f( o )));
      }
      
  }
        
     
  /****************
    Gofer: Prädikat (a -> Bool)
   */
  interface Predicate {        
      public boolean p (Object o);    
  }  

  /****************
    Gofer: odd
   */
  class Odd implements Predicate{
      public boolean p (Object o) {
          return (((Integer)o).intValue()%2 == 0);
      }
  }  
     
  /****************
    Gofer: filter :: (a -> Bool) -> [a] -> [a]
   */
  class Filter {

      LinkedList filter(Predicate op, List l) {
          LinkedList newList = new LinkedList();
          
	  // Vorsicht: Elemente werden nicht kopiert!
	  for (Iterator it=l.iterator(); it.hasNext(); ) {
	    Object element = it.next();
	    if(op.p(element))
		newList.add(element);
	  }
          return newList;
      }
  }
     

  /****************
     Gofer: (+x) wobei x bei Erzeugung festgelegt wird
   */
  class Add implements Function{
      int value;
      
      public Add(int i) {
          value = i;
      }
      public Object f (Object o) {
          return (new Integer(((Integer)o).intValue() + value));
      }
  }
    
    
  /****************
    Gofer: (*x) wobei x bei Erzeugung festgelegt wird
   */
  class Mult implements Function {
      
      int value;
      
      public Mult(int i) {
          value = i;
      }
      
      public Object f (Object o) {
          return (new Integer(((Integer)o).intValue() * value));
      }
  }
    

  /****************
    Gofer: (\x -> c): Konstante funktion, 
    wobei c bei Erzeugung festgelegt wird
   */
  class Const implements Function {
      
      int value;
      
      public Const(int i) {
          value = i;
      }
      
      public Object f (Object o) {    
          return(new Integer(value));
      }
  }
    
    
  /****************
     Gofer: (\f -> f x) wobei 
     das Argument(!) x bei Erzeugung festgelegt wird
   */
  class ApplyFtoO implements Function {
      
      Object value;
      
      public ApplyFtoO(int x) {
          value = new Integer(x);
      }
      
      public Object f (Object o) {
          return( ((Function)o).f(value) );
      }           
  }


// ##################################

/****************
  Hauptklasse demonstriert Berechnung der Listen:
 */
public class FunkHOrdnung
{
  public static void main( String args[] ){

      // li = [1,2,3,4,5,6,7,8,9,10]
      LinkedList li = new LinkedList();
      for (int i = 1; i <= 10; i++)
	  li.add(new Integer(i));
      
      // li1 =     map double li
      LinkedList li1 = new Map().map(new Double(), li);
 
      // li2 =     map (double . double) li
      LinkedList li2 = new Map().map(
		    new Compose(new Double(), new Double()),li);

      // li3 =     filter odd li
      LinkedList li3 = new Filter().filter(new Odd(),li);

      // listOfFunctions = 
      //   [double, mult 4, double . double.double, (+5), (\x -> 0) ]
      LinkedList listOfFunctions = new LinkedList();
      
      listOfFunctions.add(new Double());
      listOfFunctions.add(new Mult(4));
      listOfFunctions.add(new Compose(new Double(),
	                   new Compose(new Double(), new Double())));
      listOfFunctions.add(new Add(5));
      listOfFunctions.add(new Const(0));
      
      LinkedList li4 = new Map().map(new ApplyFtoO(3),listOfFunctions );
      
      
      System.out.println();                       
      System.out.println("------------------------------------- ");                       
      System.out.println("li  = " + li);
      System.out.println("li1 = " + li1);
      System.out.println("li2 = " + li2);
      System.out.println("li3 = " + li3);
      System.out.println("li4 = " + li4);
      System.out.println("------------------------------------- ");                       
              
      System.out.println();                       
  
    }
      
}
