/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *                                                                         *
 *   AndroidWorld Library, Copyright 2011 Bryan Chadwick                   *
 *                                                                         *
 *   FILE: ./android/world/World.java                                      *
 *                                                                         *
 *   This file is part of AndroidWorld.                                    *
 *                                                                         *
 *   AndroidWorld is free software: you can redistribute it and/or         *
 *   modify it under the terms of the GNU General Public License           *
 *   as published by the Free Software Foundation, either version          *
 *   3 of the License, or (at your option) any later version.              *
 *                                                                         *
 *   AndroidWorld 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 General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with AndroidWorld.  If not, see <http://www.gnu.org/licenses/>. *
 *                                                                         *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

package android.world;

import android.app.Activity;
import android.image.Scene;

/**
 * <style type='text/css'><!--
 *    .def{ color: #000000; }
 *    .com{ font-style: italic; color: #880000; }
 *    .keyw{ font-weight: bold; color: #000088; }
 *    .num{ color: #00AA00; }
 *    .str{ color: #CC00AB; }
 *    .prim{ color: #0000FF; }
 *    .func{ color: #BB7733; }
 *    img.example{ padding-left: 50px; padding-bottom: 30px; }
 *    table.events td{ padding-bottom: 20px; }
 *    table.events{ padding-left: 30px; padding-bottom: 20px; }
 *  --></style>
 *  
 *  A Class representing a World and the related methods for drawing the world
 *    and handling various events.  In order to implement a functioning World you
 *    must <i>extend</i> this class, and implement an {@link android.world.World#onDraw onDraw}
 *    method.  Other handler methods ({@link android.world.World#tickRate tickRate}, {@link android.world.World#onTick onTick}, 
 *    {@link android.world.World#onMouse onMouse}, {@link android.world.World#onKey onKey},  {@link android.world.World#onRelease onRelease}, 
 *    {@link android.world.World#onOrientation onOrientation}, {@link android.world.World#stopWhen stopWhen},
 *    and {@link android.world.World#lastScene lastScene}) are optional, and can be overridden to add
 *    new functionality.
 * <p>
 *  See the individual methods for detailed documentation.
 * </p>
 * 
 * <p>
 * <h3>Extending World</h3>
 * <blockquote>
 * 
 * <p>
 *   Developing an Android application is a bit more work, since it requires the
 *   <a href='http://developer.android.com/sdk/index.html'>Android development kit</a> for Eclipse.  After that, you can create an
 *   Android Project and fill in all the parameters.  The World/VoidWorld classes
 *   only require a simple hook to be added to the application's <tt>Activity</tt>.
 * </p>
 * 
 * Below is a simple example of a <code>World</code> that adds a new point at each mouse click.  The world
 * contains a {@link android.image.Scene Scene} and a new {@link android.image.Circle Circle} is placed for each
 * <code class='str'>"button-down"</code> event received.  The World is created in the
 * class that extends <tt>Activity</tt>.
 * 
 * <pre>   
 *        <span class="keyw">import</span> android.app.Activity;
 *        <span class="keyw">import</span> android.os.Bundle;
 *        <span class="keyw">import</span> android.view.Display;
 *       
 *        <span class="keyw">import</span> android.image.*;
 *        <span class="keyw">import</span> android.world.World;
 *       
 *        <span class="keyw">public</span> <span class="keyw">class</span> MousePoints <span class="keyw">extends</span> Activity{
 *           <span class="com">// Called when the activity is first created.</span>
 *           <span class="keyw">public</span> <span class="keyw">void</span> <span class="func">onCreate</span>(Bundle savedState) {
 *              <span class="keyw">super</span>.<span class="func">onCreate</span>(savedState);
 *               
 *              Display dis = <span class="func">getWindowManager</span>().<span class="func">getDefaultDisplay</span>();       
 *               
 *              <span class="com">// Create and start the World </span>
 *              <span class="keyw">new</span> <span class="func">MousePointsWorld</span>(<span class="keyw">new</span> <span class="func">EmptyScene</span>(dis.<span class="func">getWidth</span>(), dis.<span class="func">getHeight</span>()-<span class='num'>50</span>))
 *                      .<span class="func">bigBang</span>(<span class="keyw">this</span>);        
 *           }
 *        }
 *       
 *        <span class="keyw">class</span> MousePointsWorld <span class="keyw">extends</span> World{
 *            Scene scene;
 *       
 *            <span class="func">MousePointsWorld</span>(Scene scene){
 *                <span class="keyw">this</span>.scene = scene;    
 *            }
 *       
 *            <span class="keyw">public</span> Scene <span class="func">onDraw</span>(){ <span class="keyw">return</span> <span class="keyw">this</span>.scene; }
 *       
 *            <span class="keyw">public</span> World <span class="func">onMouse</span>(<span class="keyw">int</span> x, <span class="keyw">int</span> y, <span class="prim">String</span> me){
 *                <span class="keyw">if</span>(!me.<span class="func">equals</span>(<span class="str">"button-down"</span>)){
 *                    <span class="keyw">return</span> <span class="keyw">this</span>;
 *                }<span class="keyw">else</span>{
 *                    <span class="keyw">return</span> <span class="keyw">new</span> <span class="func">MousePointsWorld</span>(
 *                            <span class="keyw">this</span>.scene.<span class="func">placeImage</span>(<span class="keyw">new</span> <span class="func">Circle</span>(<span class="num">20</span>, <span class="str">"solid"</span>, <span class="str">"red"</span>)
 *                                         .<span class="func">overlay</span>(<span class="keyw">new</span> <span class="func">Circle</span>(<span class="num">20</span>, <span class="str">"outline"</span>, <span class="str">"black"</span>)), x, y));
 *                }
 *            }
 *        }
 * </pre>
 * 
 * After a few finger-taps, the device will look something like this:<br/><br/>
 * 
 *   <img class="example" src="test/mouse-points.png" />
 * </blockquote>
 * </p>    
 */
public abstract class World{
    /** The BigBang/Handler */
    private BigBang bigbang;
    
    
    /** Default Tick rate for the world: ~33 frames per second */
    public static double DEFAULT_TICK_RATE = 0.03;
    
    /** Mouse down (button-down) event String */
    public static String MOUSE_DOWN = BigBang.MOUSE_DOWN;
    /** Mouse up (button-up) event String */
    public static String MOUSE_UP = BigBang.MOUSE_UP;
    /** Mouse motion (move) event String */
    public static String MOUSE_MOVE = BigBang.MOUSE_MOVE;
    /** Mouse down & move (drag) event String */
    public static String MOUSE_DRAG = BigBang.MOUSE_DRAG;   

    /** Key arrow-up event String */
    public static String KEY_ARROW_UP = BigBang.KEY_ARROW_UP;
    /** Key arrow-down event String */
    public static String KEY_ARROW_DOWN = BigBang.KEY_ARROW_RIGHT;
    /** Key arrow-left event String */
    public static String KEY_ARROW_LEFT = BigBang.KEY_ARROW_LEFT;
    /** Key arrow-right event String */
    public static String KEY_ARROW_RIGHT = BigBang.KEY_ARROW_RIGHT;
    /** Menu Key event String.  The menu key will usually be intercepted to open a save dialog
     *    to enable the capture of application/game screen-shots. */
    public static String KEY_MENU = BigBang.KEY_MENU;
    /** Search Key event String */
    public static String KEY_SEARCH = BigBang.KEY_SEARCH;

    /** Return a visualization of this <tt>World</tt> as a {@link android.image.Scene Scene}.
     *    See {@link android.image.EmptyScene}, {@link android.image.Scene#placeImage(Image, int, int)}, and
     *    {@link android.image.Scene#addLine(int, int, int, int, String)} for documentation on
     *    constructing <tt>Scene</tt>s */
    public abstract Scene onDraw();
    
    /** Return the tick rate for this World in <i>seconds</i>.  For example,
     *  <span class='num'>0.5</span> means two <i>tick</i>. The rate is only accessed when
     *  bigBang() is initially called and the window is created. */
    public double tickRate(){ return DEFAULT_TICK_RATE; }
    
    /** Produce a (possibly) new World based on the Tick of the clock.  This
     *  method is called to get the <i>next world</i> on each clock tick.*/
    public World onTick(){ return this; }
    
    /** Produce a (possibly) new World when a touch event is triggered.
     * <tt>x</tt> and <tt>y</tt> are the location of the event on the device screen,
     * and <tt>event</tt> is a <tt>String</tt> that describes what kind of event
     * occurred.
     * 
     * <p>
     *    In addition to the normal World events (button-down/up) Android devices
     *    include a <tt class='str'>"long-button-down"</tt> event, for when the
     *    the user does a long press (touch and hold).  
     * </p>
     * <p>
     *   <b>Possible Mouse Events</b>
     * <table class='events'>
     *    <tr><td style="text-align:right"><tt class='str'>"button-down"</tt> : </td>
     *        <td>The user <i>presses</i> the touch screen of the device</td></tr>
     *    <tr><td style="text-align:right"><tt class='str'>"long-button-down"</tt> : </td>
     *        <td>The user <i>presses and holds</i> on the touch screen of the device</td></tr>
     *    <tr><td style="text-align:right"><tt class='str'>"button-up"</tt> : </td>
     *        <td>The user <i>releases</i> the touch screen of the device</td></tr>
     *    <tr><td style="text-align:right"><tt class='str'>"move"</tt> : </td>
     *        <td>The user <i>moves</i> the mouse in the World window</td></tr>
     *    <tr><td style="text-align:right"><tt class='str'>"drag"</tt> : </td>
     *        <td>The user <i>touches</i> and <i>moves</i> on the touch screen of the device</td></tr>
     * </table>
     * </p>
     */
    public World onMouse(int x, int y, String event){ return this; }
    
    /** Produce a (possibly) new World when a key is
     * pressed. The given <tt>event</tt> is a <tt>String</tt> that
     * describes what key was pressed.
     *
     * <p>
     *   <b>Special Keys</b>
     * <table class='events'>
     *    <tr><td style="text-align:right"><tt class='str'>"up"</tt> : </td>
     *        <td>The user presses the <i>up-arrow</i> key</td></tr>
     *    <tr><td style="text-align:right"><tt class='str'>"down"</tt> : </td>
     *        <td>The user presses the <i>down-arrow</i> key</td></tr>
     *    <tr><td style="text-align:right"><tt class='str'>"left"</tt> : </td>
     *        <td>The user presses the <i>left-arrow</i> key</td></tr>
     *    <tr><td style="text-align:right"><tt class='str'>"right"</tt> : </td>
     *        <td>The user presses the <i>right-arrow</i> key</td></tr>
     * </table>
     *
     * Other keys generate a single character <tt>String</tt> that
     * represents the key pressed. For example, Pressing the <i>B</i> key on
     * the keyboard generates <tt class='str'>"b"</tt> as an event.
     * If the shift key is held while pressing <i>B</i> then <tt
     * class='str'>"B"</tt> is generated.
     * </p>
     */
    public World onKey(String event){ return this; }
    
    /** Produce a (possibly) new World when a key is released. The given <tt>event</tt>
     * is a <tt>String</tt> that describes which key was released.
     *
     * <p>
     *   <b>Special Keys</b>
     * <table class='events'>
     *    <tr><td style="text-align:right"><tt class='str'>"up"</tt> : </td>
     *        <td>The user presses the <i>up-arrow</i> key</td></tr>
     *    <tr><td style="text-align:right"><tt class='str'>"down"</tt> : </td>
     *        <td>The user presses the <i>down-arrow</i> key</td></tr>
     *    <tr><td style="text-align:right"><tt class='str'>"left"</tt> : </td>
     *        <td>The user presses the <i>left-arrow</i> key</td></tr>
     *    <tr><td style="text-align:right"><tt class='str'>"right"</tt> : </td>
     *        <td>The user presses the <i>right-arrow</i> key</td></tr>
     * </table>
     *
     * Other keys generate a single character <tt>String</tt> that
     * represents the key released. For example, Pressing then releasing the <i>B</i> key on
     * the keyboard generates <tt class='str'>"b"</tt> as an <tt>onKey</tt> event and again
     * as an <tt>onRelease</tt> event.  If the shift key is held while pressing/releasing <i>B</i> then <tt
     * class='str'>"B"</tt> is generated.
     * </p>
     */
    public World onRelease(String event){ return this; }

    /** Determine if the World/interaction/animation should be
     * stopped.  Returning a value of <tt class='keyw'>true</tt>
     * discontinues all events (mouse, key, ticks) and causes {@link
     * android.world.World#lastScene} to be used to draw the final
     * <tt>Scene</tt>.
     */
    public boolean stopWhen(){ return false; }
    
    /** Returns the <tt>Scene</tt> that should be displayed when the
     * interaction/animation completes ({@link android.world.World#stopWhen}
     * returns <tt class='keyw'>true</tt>). */
    public Scene lastScene(){ return this.onDraw(); }
    
    /** Produce a (possibly) new World when a device orientation event is triggered.
     * <tt>x</tt>, <tt>y</tt>, and <tt>z</tt> make-up the new orientation vector 
     * for the device.  When the device is resting on a flat, level surface the
     * orientation should be entirly in the <tt>z</tt> direction (typically
     * straight out the the back of the device).
     * 
     * <p>
     *   If you are only concerned with the "<i>down</i>" angle (direction of down
     *   with respect to the screen) you can calculate it with a method as follows:
     *   <pre>
     *      <span class='keyw'>double</span> down(<span class='keyw'>double</span> x, <span class='keyw'>double</span> y, <span class='keyw'>double</span> z){
     *         <span class='keyw'>double</span> ang = Math.acos(x/Math.sqrt(x*x + y*y))+Math.PI;
     *         <span class='keyw'>if</span>(y < <span class='num'>0</span>)<span class='keyw'>return</span> <span class='num'>2</span>*Math.PI-ang;
     *         <span class='keyw'>return</span> ang;
     *      }
     *   </pre>
     * </p>
     */
    public World onOrientation(double x, double y, double z){ return this; }
    
    /** Kick off the interaction/animation.  This method returns the final
     *    state of the world after the user closes the World window. */
    public World bigBang(Activity act){
        return (World)this.makeBigBang().bigBang(act);
    }
    /** Kick off the interaction/animation in LANDSCAPE mode. */
    public World bigBangLandscape(Activity act){
        return (World)this.makeBigBang().bigBangLandscape(act);
    }
    /** Kick off the interaction/animation FULLSCREEN mode.  This method returns the final
     *    state of the world after the user closes the World window. */
    public World bigBangFullscreen(Activity act){
        return (World)this.makeBigBang().bigBangFullscreen(act);
    }
    /** Kick off the interaction/animation in FULLSCREEN/LANDSCAPE mode. */
    public World bigBangLandscapeFullscreen(Activity act){
        return (World)this.makeBigBang().bigBangLandscapeFullscreen(act);
    }
    /** Get a bigbang instance for this World */
    private BigBang makeBigBang(){
        this.bigbang = new BigBang(this)
            .onDraw(new WorldDraw())
            .onTick(new WorldTick(), tickRate())
            .onMouse(new WorldMouse())
            .onKey(new WorldKey())
            .onRelease(new WorldRelease())
            .stopWhen(new WorldStop())
            .lastScene(new WorldLast())
            .orientation(new WorldOrient());
        return this.bigbang;
    }

    /** Use to Disable Screen Shots */
    public static void noScreenShots(){
        BigBang.Handler.screenShots = false;
    }
    
    /** Use to temperarily disable onTick/interactions */
    public void pause(){
        this.bigbang.pause();
    }

    /** Use to renable onTick/interactions */
    public void unpause(){
        this.bigbang.unpause();
    }
    
    /** Wrapper for OnDraw callback */
    private static class WorldDraw{
        @SuppressWarnings("unused")
        Scene apply(World w){ return w.onDraw(); }
    }
    /** Wrapper for OnTick callback */
    private static class WorldTick{
        @SuppressWarnings("unused")
        World apply(World w){ return w.onTick(); }
    }
    /** Wrapper for OnMouse callback */
    private static class WorldMouse{
        @SuppressWarnings("unused")
        World apply(World w, int x, int y, String me)
        { return w.onMouse(x,y,me); }
    }
    /** Wrapper for OnKey callback */
    private static class WorldKey{
        @SuppressWarnings("unused")
        World apply(World w, String ke){ return w.onKey(ke); }
    }
    /** Wrapper for OnRelease callback */
    private static class WorldRelease{
        @SuppressWarnings("unused")
        World apply(World w, String ke){ return w.onRelease(ke); }
    }
    /** Wrapper for StopWhen callback */
    private static class WorldStop{
        @SuppressWarnings("unused")
        boolean apply(World w){ return w.stopWhen(); }
    }
    /** Wrapper for LastScene callback */
    private static class WorldLast{
        @SuppressWarnings("unused")
        Scene apply(World w){ return w.lastScene(); }
    }
    /** Wrapper for LastScene callback */
    private static class WorldOrient{
        @SuppressWarnings("unused")
        World apply(World w, float x, float y, float z){ return w.onOrientation(x,y,z); }
    }
}