package com.gameproject.multiplayerpaddle;



import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;


import android.annotation.TargetApi;
import android.bluetooth.*;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.SoundPool;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;


@TargetApi(11)
public class GameView extends SurfaceView implements
		SurfaceHolder.Callback {

	private SpriteObject paddle;
	private SpriteObject paddle_other;
	private SpriteObject[] block;
	private SpriteObject ball;
	
	private GameLogic mGameLogic;
	private ArrayBlockingQueue<InputObject> inputObjectPool;
	private int game_width;
	private int game_height;
	
	private Resources res;
	private int[] x_coords;
	private int[] y_coords;
	private int block_count;
	
	private byte[] latest_input;
	
	private Context context;

	private MediaPlayer mp;
	
	
    // Debugowanie
    private static final String TAG = "BluetoothChatService";
    private static final boolean D = true;

    // Nazwa rekordu SDP używana podczas tworzenia gniazda serwera
    private static final String NAME = "BluetoothChat";

    // Niepowtarzalny identyfikator UUID tej aplikacji
    private static final UUID MY_UUID = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");

    // Pola klasy
    private final BluetoothAdapter mAdapter;
    private final Handler mHandler;
    private AcceptThread mAcceptThread;
    private ConnectThread mConnectThread;
    private ConnectedThread mConnectedThread;
    private int mState;

    // Stałe wskazujące bieżący stan połączenia
    public static final int STATE_NONE = 0;       // nic nie robimy
    public static final int STATE_LISTEN = 1;     // słuchamy nadchodzących połączeń
    public static final int STATE_CONNECTING = 2; // tworzymy połączenie wychodzące
    public static final int STATE_CONNECTED = 3;  // połączony z urządzeniem zdalnym


    
    
	
	
	public GameView(Context con, Handler hand) {
		super(con);
		Log.e("GAMEVIEW","metoda konstruktora GameView");
		context = con;
		getHolder().addCallback(this);
		paddle = new SpriteObject(BitmapFactory.decodeResource(getResources(), R.drawable.paddle), 600, 600);
		paddle_other = new SpriteObject(BitmapFactory.decodeResource(getResources(), R.drawable.paddle), 600, 100);
		ball = new SpriteObject(BitmapFactory.decodeResource(getResources(), R.drawable.ball), 600, 300);
		mGameLogic = new GameLogic(getHolder(), this);
		Log.e("GAMEVIEW","zaincjalizowano logikę gry");
		createInputObjectPool();
		
		
		res = getResources();
		block_count = res.getInteger(R.integer.blocknumber);
		x_coords = res.getIntArray(R.array.x);
		y_coords = res.getIntArray(R.array.y);
		block = new SpriteObject[block_count];
		for(int i = 0; i < block_count; i++){
			block[i] = new SpriteObject(BitmapFactory.decodeResource(getResources(), R.drawable.block), x_coords[i], y_coords[i]);
		}
		
		
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mState = STATE_NONE;
        mHandler = hand;
        
		mp = MediaPlayer.create(context, R.raw.bounce);
		
		setFocusable(true);
	}

	private void createInputObjectPool() {
		inputObjectPool = new ArrayBlockingQueue<InputObject>(20);
		for (int i = 0; i < 20; i++) {
			inputObjectPool.add(new InputObject(inputObjectPool));
		}
	}

	

		@Override
		public boolean onTouchEvent(MotionEvent event) {
			try {
				int hist = event.getHistorySize();
				if (hist > 0) {
					for (int i = 0; i < hist; i++) {
						InputObject input = inputObjectPool.take();
						input.useEventHistory(event, i);
						mGameLogic.feedInput(input);
					}
				}
				InputObject input = inputObjectPool.take();
				input.useEvent(event);
				mGameLogic.feedInput(input);
			} catch (InterruptedException e) {
			}
			try {
				Thread.sleep(16);
			} catch (InterruptedException e) {
			}
			return true;
		}
		
		
	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		mGameLogic.setGameState(mGameLogic.RUNNING);
		mGameLogic.start();
		ball.setMoveY(-10);
		ball.setMoveX(10);


	}
	
	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		mp.release();
	}
	
	
	@Override
	public void onDraw(Canvas canvas) {
		canvas.drawColor(Color.WHITE);
		ball.draw(canvas);
		paddle.draw(canvas);
		paddle_other.draw(canvas);
		for(int i = 0; i < block_count; i++){
			block[i].draw(canvas);
		}
		game_width = canvas.getWidth();
		game_height = canvas.getHeight();
	}

	public void update(int adj_mov) {
		
		int ball_bottom = (int)(ball.getY() + ball.getBitmap().getHeight());
		int ball_right = (int)(ball.getX() + ball.getBitmap().getWidth());
		int ball_y = (int) ball.getY();
		int ball_x = (int) ball.getX();
		
		//Zderzenie od dołu
		if(ball_bottom > game_height){
			ball.setMoveY(-ball.getMoveY());
			//gracz przegrywa
		}
		
		//zderzenie od góry 
		if(ball_y < 0){
			ball.setMoveY(-ball.getMoveY());
		}
		
		//zderzenie z prawej strony 
		if(ball_right > game_width){
			ball.setMoveX(-ball.getMoveX());
		}
		
		//zderzenie z lewej strony 
		if(ball_x < 0){
			ball.setMoveX(-ball.getMoveX());
		}
		
		
		//zderzenie z rakietką 
		if(paddle.collide(ball)){
			if(ball_bottom > paddle.getY() && ball_bottom < paddle.getY() + 20){
				ball.setMoveY(-ball.getMoveY());
			}
		}
		
		//dane wejściowe dla rakietki
		int val=0;
		for (int i=latest_input.length-1, j = 0; i >= 0; i--,j++)
			{
				val += (latest_input[i] & 0xff) << (8*j);
			}
		paddle_other.setX(val);
		//obsługa kolizji obiektu paddle_other
		int paddle_other_bottom = paddle_other.getBitmap().getHeight();
		if(paddle_other.collide(ball)){
			if(ball_y < paddle_other_bottom && ball_y < paddle_other_bottom + 20){
				ball.setMoveY(-ball.getMoveY());
			}
		}
		
		//sprawdzenie zderzenia z blokami
		for(int i = 0; i < block_count; i++){
			if(ball.collide(block[i])){
				block[i].setstate(block[i].DEAD);
				mp.start();
				int block_bottom = (int)(block[i].getY() + block[i].getBitmap().getHeight());
				int block_right =(int)(block[i].getX() + block[i].getBitmap().getWidth());

				
				//uderzenie w dół bloku 
				if(ball_y > block_bottom - 10){
					ball.setMoveY(ball.getMoveY());
				}
				//uderzenie w górę bloku 
				else if(ball_bottom < block[i].getY() + 10){
					ball.setMoveY(-ball.getMoveY());
				}
				//uderzenie z prawej strony 
				else if(ball_x > block_right - 10){
					ball.setMoveX(ball.getMoveX());
				}
				//uderzenie z lewej strony 
				else if(ball_right < block[i].getX() + 10){
					ball.setMoveX(-ball.getMoveX());
				}

			}
		}
	
		//dane wyjściowe dla rakietki 
		byte[] paddle_output;
	    ByteBuffer bb = ByteBuffer.allocate(4); 
	    bb.putInt((int)paddle.getX()); 
	    paddle_output = bb.array();
		write(paddle_output);

		//wykonanie określonych uaktualnień 
		for(int i = 0; i < block_count; i++){
			block[i].update(adj_mov);
		}		
		paddle.update(adj_mov);
		paddle_other.update(adj_mov);
		ball.update(adj_mov);
		

	}
	
	
	
	public void processMotionEvent(InputObject input){
		paddle.setX(input.x);
		paddle.setY(input.y);

	}
	
	public void processKeyEvent(InputObject input){

	}

	public void processOrientationEvent(float orientation[]){
		
		float roll = orientation[2];
		if (roll < -40) {
			//sprite.setMoveX(2);
		} else if (roll > 40) {
			//sprite.setMoveX(-2);
		}
		
	}


	







	    /**
	     * Ustawia bieżący stan połączenia
	     * @param state  Wartość całkowitoliczbowa określająca stan połączenia
	     */
	    private synchronized void setState(int state) {
	        if (D) Log.d(TAG, "setState() " + mState + " -> " + state);
	        mState = state;

	        // Przekaż bieżąc stan do metody obsługi
	        mHandler.obtainMessage(BluetoothChat.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
	    }

	    /**
	     * Zwraca bieżący stan połączenia. */
	    public synchronized int getState() {
	        return mState;
	    }

	    /**
	     * Uruchamia usługę komunikacji. W szczególności uruchamia wątek AcceptThread aby rozpocząć
	     * sesję w trybie nasłuchowania. Wywoływana przez metodę onResume() klasy Activity*/
	    public synchronized void start() {
	        if (D) Log.d(TAG, "start");

	     // Usuń dowolny wątek próbujący nawiązać połączenie 
	        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}

	     // Usuń dowolny wątek obsługujący połączenie
	        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}

	        // Uruchom wątek nasłuchujący na gnieździe Bluetooth 
	        if (mAcceptThread == null) {
	            mAcceptThread = new AcceptThread();
	            mAcceptThread.start();
	        }
	        setState(STATE_LISTEN);
	    }

	    /**
	     * Uruchom wątek ConnectThread w celu rozpocząecia połączenia ze zdalnym urządzeniem.
	     * @param device  Urządzenie BluetoothDevice z którym należy nawiązać połączenie
	     */
	    public synchronized void connect(BluetoothDevice device) {
	        if (D) Log.d(TAG, "połącz z: " + device);

	     // Usuń dowolny wątek próbujący nawiązać połączenie
	        if (mState == STATE_CONNECTING) {
	            if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
	        }

	     // Usuń dowolny wątek obsługujący połączenie
	        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}

	     // Uruchom wątek w celu nawiązania połączenia z danym urządzeniem
	        mConnectThread = new ConnectThread(device);
	        mConnectThread.start();
	        setState(STATE_CONNECTING);
	    }

	    /**
	     * Uruchamia wątek ConnectedThread w celu nadzorowania bieżącego połączenia Bluetooth
	     * @param socket  Obiekt BluetoothSocket użyty do nawiązania połączenia
	     * @param device  Obiekt BluetoothDevice z którym urządzenie jest połączone
	     */
	    public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) {
	        if (D) Log.d(TAG, "połączony");

	     // Usuń wątek, który ukończył połączenie 
	        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}

	     // Usuń dowolny wątek obsługujący połączenie 
	        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}

	     // Usuń wątek AcceptThread, ponieważ chcemy połączyć się tylko z jednym urządzeniem 
	        if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;}

	     // Uruchom wątek w celu zarządzania połączeniem i transmisją 
	        mConnectedThread = new ConnectedThread(socket);
	        mConnectedThread.start();

	        // Wyślij nawę połączonego urządzenia z powrotem do Aktywności
	        Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_DEVICE_NAME);
	        Bundle bundle = new Bundle();
	        bundle.putString(BluetoothChat.DEVICE_NAME, device.getName());
	        msg.setData(bundle);
	        mHandler.sendMessage(msg);

	        setState(STATE_CONNECTED);
	    }

	    /**
	     * Zatrzymuje wszystkie wątki
	     */
	    public synchronized void stop() {
	        if (D) Log.d(TAG, "stop");
	        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
	        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
	        if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;}
	        setState(STATE_NONE);
	    }

	    /**
	     * Zapisuje do wątku ConnectedThread w sposób niesynchronizowany
	     * @param out bajty do zapisu
	     * @see ConnectedThread#write(byte[])
	     */
	    public void write(byte[] out) {
	    	// Utwórz obiekt tymczasowy 
	        ConnectedThread r;
	     // Synchronizuj kopię wątku ConnectedThread 
	        synchronized (this) {
	            if (mState != STATE_CONNECTED) return;
	            r = mConnectedThread;
	        }
	     // Wykonaj niesynchronizowany zapis
	        r.write(out);
	    }

	    /**
	     * Wskazuje błąd połączenia.
	     */
	    private void connectionFailed() {
	        setState(STATE_LISTEN);

	     // Wyślij komunikat o błędzie z powrotem do aktywności 
	        Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);
	        Bundle bundle = new Bundle();
	        bundle.putString(BluetoothChat.TOAST, "Błąd połączenia z urządzeniem");
	        msg.setData(bundle);
	        mHandler.sendMessage(msg);
	    }

	    /**
	     * Wskazuje utratę połączenia.
	     */
	    private void connectionLost() {
	        setState(STATE_LISTEN);

	     // Wyślij komunikat o błędzie z powrotem do aktywności
	        Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);
	        Bundle bundle = new Bundle();
	        bundle.putString(BluetoothChat.TOAST, "Utracono połączenie z urządzeniem");
	        msg.setData(bundle);
	        mHandler.sendMessage(msg);
	    }

	    /**
	     * Ten wątek działa w trakcie nasłuchiwania nadchodzących połączeń. Działa on jako klient po stronie serwea
	     * Wątek działa do czasu nawiązania połączenia (lub jego anulowania)
	     */
	    private class AcceptThread extends Thread {
	    	// Gniazdo serwera lokalnego 
	        private final BluetoothServerSocket mmServerSocket;

	        public AcceptThread() {
	            BluetoothServerSocket tmp = null;

	         // Utwórz nowe nasłuchujące gniazdo serwera
	            try {
	                tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
	            } catch (IOException e) {
	                Log.e(TAG, "błąd funkcji listen()", e);
	            }
	            mmServerSocket = tmp;
	        }

	        public void run() {
	            if (D) Log.d(TAG, "START mAcceptThread" + this);
	            setName("AcceptThread");
	            BluetoothSocket socket = null;

	         // Dopóki brak jest połączenia, nasłuchuj na gnieździe sieciowym 
	            while (mState != STATE_CONNECTED) {
	                try {
	                	// To jest wywołanie blokujące, które zwróci wartość jedynie w przypadku
	                	// nawiązania poprawnego połączenia lub wystąpienia wyjątku 
	                    socket = mmServerSocket.accept();
	                } catch (IOException e) {
	                    Log.e(TAG, "błąd funkcji accept()", e);
	                    break;
	                }

	             // Po zaakceptowaniu połączenia
	                if (socket != null) {
	                    synchronized (GameView.this) {
	                        switch (mState) {
	                        case STATE_LISTEN:
	                        case STATE_CONNECTING:
	                            // Sytuacja poprawna. Uruchom wątek połączenia
	                            connected(socket, socket.getRemoteDevice());
	                            break;
	                        case STATE_NONE:
	                        case STATE_CONNECTED:
	                        	// Połączenie aktywne lub brak gotowości. Usuń nowe gniazdo 
	                            try {
	                                socket.close();
	                            } catch (IOException e) {
	                                Log.e(TAG, "Błąd zamykania zbędnego gniazda ", e);
	                            }
	                            break;
	                        }
	                    }
	                }
	            }
	            if (D) Log.i(TAG, "KONIEC mAcceptThread");
	        }

	        public void cancel() {
	            if (D) Log.d(TAG, "anulowanie " + this);
	            try {
	                mmServerSocket.close();
	            } catch (IOException e) {
	                Log.e(TAG, "błąd funkcji close()", e);
	            }
	        }
	    }


	    /**
	     * Ten wątek działa w trakcie próby nawiązania połączenia wychodzącego
	     * Działa on bardzo prosto - połączenie kończy się sukcesem lub porażką
	     */
	    private class ConnectThread extends Thread {
	        private final BluetoothSocket mmSocket;
	        private final BluetoothDevice mmDevice;

	        public ConnectThread(BluetoothDevice device) {
	            mmDevice = device;
	            BluetoothSocket tmp = null;

	            // Pobierz gniazdo Bluetooth dla połączenia
	            // z danym urządzeniem Bluetooth 
	            try {
	                tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
	            } catch (IOException e) {
	                Log.e(TAG, "błąd funkcji create()", e);
	            }
	            mmSocket = tmp;
	        }

	        public void run() {
	            Log.i(TAG, "START mConnectThread");
	            setName("ConnectThread");

	         // Należy zawsze unikać fazy discovery, ponieważ zdecydowanie spowalnia ona połączenie 
	            mAdapter.cancelDiscovery();

	         // Nawiąż połączenie z gniazdem Bluetooth 
	            try {
	            	// To jest wywołanie blokujące, które zwróci wartość jedynie w przypadku
	            	// nawiązania poprawnego połączenia lub wystąpienia wyjątku 
	                mmSocket.connect();
	            } catch (IOException e) {
	                connectionFailed();
	             // Zamknij gniazdo 
	                try {
	                    mmSocket.close();
	                } catch (IOException e2) {
	                    Log.e(TAG, "błąd zamykania gniazda spowodowany błędem połączenia", e2);
	                }
	             // Ponownie uruchom usługę w trybie nasłuchu 
	                GameView.this.start();
	                return;
	            }

	         // Wyzeruj zmienną mConnectThread 
	            synchronized (GameView.this) {
	                mConnectThread = null;
	            }

	         // Uruchom wątek połączenia 
	            connected(mmSocket, mmDevice);
	        }

	        public void cancel() {
	            try {
	                mmSocket.close();
	            } catch (IOException e) {
	                Log.e(TAG, "błąd funkcji close()", e);
	            }
	        }
	    }

	    /**
	     * Ten wątek działa podczas połączenia ze zdalnym urządzeniem.
	     * obsługuje on cała transmisję wychodzącą oraz przychodzącą
	     */
	    private class ConnectedThread extends Thread {
	        private final BluetoothSocket mmSocket;
	        private final InputStream mmInStream;
	        private final OutputStream mmOutStream;

	        public ConnectedThread(BluetoothSocket socket) {
	            Log.d(TAG, "utwórz ConnectedThread");
	            mmSocket = socket;
	            InputStream tmpIn = null;
	            OutputStream tmpOut = null;

	         // Pobierz strumienie wejściowe i wyjściowe gniazda
	            try {
	                tmpIn = socket.getInputStream();
	                tmpOut = socket.getOutputStream();
	            } catch (IOException e) {
	                Log.e(TAG, "błąd tworzenia gniazd tymczasowych", e);
	            }

	            mmInStream = tmpIn;
	            mmOutStream = tmpOut;
	        }

	        public void run() {
	            Log.i(TAG, "START mConnectedThread");
	            //byte[] buffer = new byte[1024];
	            int bytes;

	         // Dopóki jest połączenie, nasłuchuj strumień wejściowy
	            while (true) {
	                try {
	                	// Czytaj ze strumienia wejściowego 
	                    bytes = mmInStream.read(latest_input);

	                 // Wyślij otrzymane bajty do głównej aktywności
	                    mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, latest_input)
	                            .sendToTarget();
	                } catch (IOException e) {
	                    Log.e(TAG, "rozłączony", e);
	                    connectionLost();
	                    break;
	                }
	            }
	        }

	        /** 
	         * Zapisz do podłączonego strumienia OutStream. 
	         * @param buffer  Bajty do zapisu 
	         */ 

	        public void write(byte[] buffer) {
	            try {
	                mmOutStream.write(buffer);

	             // Udostępnij wysłany komunikat głównej aktywności 
	                mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1, buffer)
	                        .sendToTarget();
	            } catch (IOException e) {
	                Log.e(TAG, "Wyjątek podczas zapisu ", e);
	            }
	        }

	        public void cancel() {
	            try {
	                mmSocket.close();
	            } catch (IOException e) {
	                Log.e(TAG, "błąd funkcji close()", e);
	            }
	        }
	    }
	

	

	
}
