/*
    Moenagade
    °°°°°°°°°

    Moenagade is a graphical programming tool for beginners. For this
    reason it is rather limited in its functionality. It aims to help
    students to gain knowledge about the programming structures. Due
    to the explicit conversion to real and executable Java code, it 
    should help to make an easy from block programming to coding.

    Copyright (C) 2009  Bob Fisch

    This program 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 any
    later version.

    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package moenagade.base;

import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.LineEvent;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.swing.Timer;


/**
 *
 * @author robert.fisch
 */
public abstract class World extends javax.swing.JPanel implements KeyListener {

    private String background = "";
    private Image backgroundImage = null;

    private Point location = new Point(0,0);
    private Dimension dimension = new Dimension(0, 0);
    
    private ArrayList<Entity> entities = new ArrayList<Entity>();

    private int mouseX = 0;
    private int mouseY = 0;

    private int gravity = 1;
    private int maxVelocity = 10;
    javax.swing.Timer gravityTimer;

    private String filename;

    private MainFrame main;

    private ArrayList<Clip> clips = new ArrayList<Clip>();
    private ArrayList<Timer> timers = new ArrayList<Timer>();
    private ArrayList<java.util.Timer> scheduled = new ArrayList<java.util.Timer>();

    /**
     * Creates new form World
     */
    public World() {
        initComponents();

        gravityTimer = new javax.swing.Timer(20,new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                boolean goOn = false;
                for (int i = 0; i < entities.size(); i++) {
                    Entity check  = entities.get(i);
                    goOn = check.processTimer() || goOn;
                }
                if(goOn)
                    checkCollisions();
            }
        });
        gravityTimer.start();
        
        //onCreate();
    }
    
    protected void onCreate()
    {
    }
    
    public void loadImage(String filename)
    {
        background = "/moenagade/images/"+filename;
        // load image
        try 
        {
            backgroundImage = ImageIO.read(this.getClass().getResource(background));
            setDimension(backgroundImage.getWidth(null), backgroundImage.getHeight(null));
            this.filename=filename;
        } 
        catch (IOException ex) 
        {
            ex.printStackTrace();
        }
    }

    public String getFilename()
    {
        return filename;
    }

    public void setDimension(int width, int height)
    {
        dimension = new Dimension(width,height);
        setSize(dimension);
        setPreferredSize(dimension);
        setMinimumSize(dimension);
        revalidate();
    }
    
    public void setWidth(int width)
    {
        setDimension(width, getHeight());
    }
    
    public void setHeight(int height)
    {
        setDimension(getWidth(), height);
    }

    public void setMain(MainFrame main)
    {
        this.main=main;

        // we need to know when the window closes in order to stop sounds
        main.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                stopClips();
                stopTimers();
                for (int i = 0; i < entities.size(); i++) {
                    entities.get(i).delete();
                }
            }
        });
    }

    public void stopClips()
    {
        for(int i=0; i<clips.size(); i++)
        {
            Clip clip = clips.get(i);
            if(clip.isActive())
                clip.stop();
        }
    }

    public void stopTimers()
    {
        gravityTimer.stop();
        for(int i=0; i<timers.size(); i++)
            if(timers.get(i).isRunning())
                timers.get(i).stop();
        
        for(int i=0; i<scheduled.size(); i++)
        {
            scheduled.get(i).cancel();
            scheduled.get(i).purge();
        }
    }

    public MainFrame getMain()
    {
        return main;
    }

    public void deleteAll(Class c)
    {
        if(entities!=null)
            for (int i = entities.size()-1; i>=0; i--) {
                Entity e = entities.get(i);
                try
                {
                    c.cast(e);
                    entities.remove(e);
                }
                catch(Exception ex)
                {
                    // ignore
                }
            }
    }

    public int countAll(Class c)
    {
        int count = 0;
        if(entities!=null)
            for (int i = entities.size()-1; i>=0; i--) {
                Entity e = entities.get(i);
                try
                {
                    c.cast(e);
                    count++;
                }
                catch(Exception ex)
                {
                    // ignore
                }
            }
        return count;
    }

    public void bringToFront(Entity e)
    {
        if(entities.indexOf(e)>=0)
        {
            entities.remove(e);
            entities.add(e);
        }
    }

    /**
     *
     * @param g
     */
    @Override
    public void paintComponent(Graphics g)
    {
        super.paintComponents(g);

        Graphics2D g2 = (Graphics2D)g;
        g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);

        setDoubleBuffered(true);
        
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, getWidth(), getHeight());
        
        onDraw(g);

        if(backgroundImage!=null)
        {
            g.drawImage(backgroundImage, location.x, location.y, null);

            if(backgroundImage.getWidth(null)+location.x<getWidth())
                g.drawImage(backgroundImage, location.x+backgroundImage.getWidth(null), location.y, null);
            if(backgroundImage.getHeight(null)+location.y<getHeight())
                g.drawImage(backgroundImage, location.x, location.y+backgroundImage.getHeight(null), null);
            if(backgroundImage.getWidth(null)+location.x<getWidth() && backgroundImage.getHeight(null)+location.y<getHeight())
                g.drawImage(backgroundImage, location.x+backgroundImage.getWidth(null), location.y+backgroundImage.getHeight(null), null);
        }
        
        if(entities!=null)
            for (int i = 0; i < entities.size(); i++) {
                Entity e = entities.get(i);
                double x = e.getX();
                double y = e.getY();
                g.translate((int)x,(int)y);
                e.draw(g);
                g.translate((int)-x,(int)-y);
            }
    }

    public void setEntities(ArrayList<Entity> entities) {
        this.entities = entities;
    }

    public ArrayList<Entity> getEntities() {
        return entities;
    }
    
    @SuppressWarnings("unchecked")
    public <T extends Entity> T addEntity(Entity e) 
    {
        e.setWorld(this);
        if (entities.add(e))
        {
            repaint();
            return (T)e;
        }
        else
            return null;
    }    

    @SuppressWarnings("unchecked")
    public <T extends Entity> T removeEntity(Entity e)
    {
        //e.setWorld(null);
        entities.remove(e);
        repaint();
        return (T)e;
    }
    
    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        addMouseMotionListener(new java.awt.event.MouseMotionAdapter() {
            public void mouseMoved(java.awt.event.MouseEvent evt) {
                onMouseMoved(evt);
            }
        });

        addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                onMousePressed(evt);
            }
        });

        addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseReleased(java.awt.event.MouseEvent evt) {
                onMouseReleased(evt);
            }
        });

        addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseDragged(java.awt.event.MouseEvent evt) {
                onMouseDragged(evt);
            }
        });

        setLayout(new java.awt.BorderLayout());
    }// </editor-fold>//GEN-END:initComponents

    protected void onMouseMoved(java.awt.event.MouseEvent evt) {                                
        mouseX=evt.getX();
        mouseY=evt.getY();
   
        if(entities!=null)
            for (int i = 0; i < entities.size(); i++) {
                if(entities.get(i).mouseInside())
                    entities.get(i).onMouseMoved(evt);
            }
    }  

    protected void onMousePressed(java.awt.event.MouseEvent evt) {                                  
        mouseX=evt.getX();
        mouseY=evt.getY();
        
        if(entities!=null)
            for (int i = 0; i < entities.size(); i++) {
                if(entities.get(i).mouseInside())
                    entities.get(i).onMousePressed(evt);
            }
    }  

    protected void onMouseReleased(java.awt.event.MouseEvent evt) {                                  
        mouseX=evt.getX();
        mouseY=evt.getY();
        
        if(entities!=null)
            for (int i = 0; i < entities.size(); i++) {
                if(entities.get(i).mouseInside())
                    entities.get(i).onMouseReleased(evt);
            }
    } 

    protected void onMouseDragged(java.awt.event.MouseEvent evt) {                                  
        mouseX=evt.getX();
        mouseY=evt.getY();
        
        if(entities!=null)
            for (int i = 0; i < entities.size(); i++) {
                if(entities.get(i).mouseInside())
                    entities.get(i).onMouseDragged(evt);
            }
    } 
    
    @Override
    public void keyTyped(KeyEvent ke) {
        repaint();
    }

    @Override
    public void keyPressed(KeyEvent ke) {
        repaint();
    }

    @Override
    public void keyReleased(KeyEvent ke) {
        repaint();
    }

    public int getMouseX()
    {
        return mouseX;
    }

    public int getMouseY()
    {
        return mouseY;
    }

    @Override
    public int getWidth()
    {
        if(backgroundImage!=null)
            return dimension.width;
        else 
            return super.getWidth();
    }
    
    @Override
    public int getHeight()
    {
        if(backgroundImage!=null)
            return dimension.height;
        else
            return super.getHeight();
    }

    public World getWorld()
    {
        return this;
    }

    public Entity isTouching(Entity other)
    {
        if(entities!=null)
            for (int i = 0; i < entities.size(); i++) {
                Entity e = entities.get(i);
                if(e.getRectangle().intersects(other.getRectangle()))
                    return e;
            }
        return null;
    }
    
    public Entity isTouching(Entity other, String classname)
    {
        if(entities!=null)
            for (int i = 0; i < entities.size(); i++) {
                Entity e = entities.get(i);
                if(e.getClass().getSimpleName().equals(classname) && e.getRectangle().intersects(other.getRectangle()))
                    return e;
            }
        return null;
    }
    
    public void checkCollisions() {
        if(entities!=null) {
            //for (int i = 0; i < entities.size(); i++) {
            for (int i = entities.size()-1; i>=0; i--) {
                // it seams as if this may happen if the list changes during the 
                // execution of the loop (--> threads!), so check again ...
                if(i>=entities.size()) return;

                Entity check = entities.get(i);
                
                if(entities.contains(check))
                    if(check.getX()<0 || check.getX()+check.getWidth()>getWidth() ||
                       check.getY()<0 || check.getY()+check.getHeight()>getHeight())
                    {
                        check.onTouchBoundary();
                    }
                
                if(entities.contains(check))
                    if(check.getX()+check.getWidth()<0  || check.getX()>getWidth() ||
                       check.getY()+check.getHeight()<0 || check.getY()>getHeight())
                    {
                        check.onOutOfWorld();
                    }
                
                if(entities.contains(check))
                    //for (int j = i+1; j < entities.size(); j++) {
                    for (int j = i-1; j >= 0; j--) {
                        Entity other = entities.get(j);

                        if(check.isTouching(other) && getMain()!=null)
                        {
                            check.onTouched(other);
                            other.onTouched(check);
                        }
                    }
            }
        }
    }

    public void moveLeft(int pixels)
    {
        location.x-=pixels;
        location.x = location.x % backgroundImage.getWidth(null);
        for (int i = 0; i < entities.size(); i++) 
            entities.get(i).moveLeft(pixels);
        repaint();  
    }
    
    public void moveRight(int pixels)
    {
        location.x+=pixels;
        location.x = location.x % backgroundImage.getWidth(null);
        for (int i = 0; i < entities.size(); i++) 
            entities.get(i).moveRight(pixels);
        repaint();  
    }

    public void moveUp(int pixels)
    {
        location.y-=pixels;
        location.y = location.y % backgroundImage.getHeight(null);
        for (int i = 0; i < entities.size(); i++) 
            entities.get(i).moveUp(pixels);
        repaint();  
    }
    
    public void moveDown(int pixels)
    {
        location.y+=pixels;
        location.y = location.y % backgroundImage.getHeight(null);
        for (int i = 0; i < entities.size(); i++) 
            entities.get(i).moveDown(pixels);
        repaint();  
    }

    public World setWorldX(int x)
    {
        location.x=x;
        location.x = location.x % backgroundImage.getWidth(null);
        repaint();  
        return this;
    }
    
    public World setWorldY(int y)
    {
        location.y=y;
        location.y = location.y % backgroundImage.getHeight(null);
        repaint();  
        return this;
    }

    public int getWorldX()
    {
        return location.x;
    }
    
    public int getWorldY()
    {
        return location.y;
    }
    
    public int countEntities()
    {
        return entities.size();
    }
    
    public int countEntities(String classname)
    {
        int count = 0;
        for (int i = 0; i < entities.size(); i++) {
            Entity entity = entities.get(i);
            if(entity.getClass().getSimpleName().equals(classname))
                count++;
        }
        return count;
    }
    
    protected void onDraw(Graphics g)
    {        
    }

    public Clip playSound(String filename)
    {
        try {
            filename = "/moenagade/sounds/"+filename;
            AudioInputStream audioIn = AudioSystem.getAudioInputStream(MainFrame.class.getResource(filename));
            final Clip clip = AudioSystem.getClip();
            clip.open(audioIn);
            clip.start();
            clips.add(clip);
            clip.addLineListener(new LineListener() {
                @Override
                public void update(LineEvent le) {
                    if(le.getType()==LineEvent.Type.CLOSE)
                    {
                        clips.remove(clip);
                    }
                }
            });
            return clip;
        }
        catch(Exception ex)         
        {
            ex.printStackTrace();
        }
        return null;
    }

    public void hideCursor()
    {
        // Transparent 16 x 16 pixel cursor image.
        BufferedImage cursorImg = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);

        // Create a new blank cursor.
        Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImg, new Point(0, 0), "blank cursor");

        // Set the blank cursor to the JFrame.
        setCursor(blankCursor);
    }

    public void showCursor()
    {
        setCursor(Cursor.getDefaultCursor());
    }

    public void addTimer(Timer timer)
    {
        timers.add(timer);
    }

    public void addSchedule(java.util.Timer timer)
    {
        scheduled.add(timer);
    }

    public int getGravity()
    {
        return gravity;
    }

    public int getMaxVelocity()
    {
        return maxVelocity;
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    // End of variables declaration//GEN-END:variables
}
