final int HEIGHT_SIZE = 8;
final int WIDTH_SIZE  = 8;
final int FIELD_SIZE  = (HEIGHT_SIZE *WIDTH_SIZE);
final int HEIGHT_MIN  = 0;
final int HEIGHT_MAX  = (HEIGHT_SIZE-1);
final int WIDTH_MIN   = 0;
final int WIDTH_MAX   = (WIDTH_SIZE-1);

final int HEAD        = 0;

final int OBSTACLE    = 255;
final int UNDEFINED   = 99;
final int COOKIE      = 0;

final int LEFT        = (-1);
final int RIGHT       = 1;
final int UP          = (-WIDTH_SIZE);
final int DOWN        = WIDTH_SIZE;
final int DONT_MOVE   = 0;

final int ERR = (-111);

int [] wave;
int [] snake;
int snakeSize = 1;

int [] tempWave;
int [] tempSnake;
int tempSnakeSize;

int cookie;
int bestMove;
int speed = 128;
int showArray =0;


int [][]mov = {   { LEFT, RIGHT, UP, DOWN },
                  { RIGHT, UP, DOWN, LEFT },
                  { UP, DOWN, LEFT, RIGHT },
                  { DOWN, LEFT, RIGHT, UP }   };

void setup()
{
  size(400, 400);
  smooth();
  
  background(128,128,255);
  fill(255,255,0);
  stroke(128,128,255);
  strokeWeight(2);
  textSize(16);
  textAlign(LEFT, TOP); 

  wave       = new int[FIELD_SIZE];
  snake      = new int[FIELD_SIZE+1];
  tempWave   = new int[FIELD_SIZE];
  tempSnake  = new int[FIELD_SIZE+1];
  
  randomSeed(second());
   
  snake[HEAD] = 0;
  drawRect(0,0, 255, 255, 0);
  newCookie();
    
  println("begin");
  println("Cookie" + cookie);
  
  delay(1000);
}



void draw()
{
    waveMatrixReset(snake, snakeSize, wave);

   

    if ( waveMatrixRefresh_full(cookie,snake,wave) )// = true if way to cookie found
    {   bestMove = findSafeWay();  } // check is this way is safe ?, if it is trap - follow the tail.  
    else
    {   bestMove = followTail();  }  // directly follow the tail

    if (bestMove == ERR ) bestMove = anyPossibleMove(); // if all moves is bad, do any possible move


    if (bestMove != ERR)
    {   makeMove(bestMove);  }
    else
    {   gameOver(); }

    drawGameScreen();
    gameControl();
    
    delay(speed);
    
}






int findSafeWay()
{
    int safeMove    = ERR;


    for (int comb_N = 0; comb_N < 1 ; ++comb_N)
    {
        virtual_shortestMove_to_cookie(comb_N);
        if ( isTailInside() )
        {   return chooseShortestSafeMove(comb_N, snake, wave); }
    }

    safeMove = followTail();

    return safeMove;
}


int virtual_shortestMove_to_cookie(int mov_comb_N)
{
    tempSnakeSize = snakeSize;
    for (int i=0; i<snakeSize; ++i) tempSnake[i] = snake[i];
    for (int i=0; i<FIELD_SIZE; ++i) tempWave[i]   = wave[i];
    waveMatrixReset(tempSnake, tempSnakeSize, tempWave);

    boolean isCookieEated = false;
    do
    {
        waveMatrixRefresh_full(cookie, tempSnake, tempWave);

        int move = chooseShortestSafeMove(mov_comb_N, tempSnake, tempWave);
        println (tempSnake[HEAD] + " " + move + " " + tempSnake[tempSnakeSize-1]);
        shiftArrayRight(tempSnake, tempSnakeSize);
        tempSnake[HEAD] += move;

        if (tempSnake[HEAD] == cookie)
        {
            ++tempSnakeSize;
            waveMatrixReset(tempSnake, tempSnakeSize, tempWave);
            tempWave[cookie] = OBSTACLE;
            isCookieEated = true;
            //waveMatrixRefresh_full(cookie,tempSnake,tempWave);
        }
        else // instead "waveMatrixReset" - fill wave's cells manually
        {
            tempWave[tempSnake[HEAD]] = OBSTACLE;
            tempWave[tempSnake[tempSnakeSize]] =  UNDEFINED;
        }
    }
    while ( !isCookieEated ) ;

    return 0;
}

int chooseShortestSafeMove(int comb_N, int [] snakeArray, int [] waveArray)
{
    int bestMove = ERR;
    int min  = OBSTACLE;
        
    for (int i = 0; i < 4  ; ++i)
    {
        if ( isMoveIsPossible(snakeArray[HEAD], mov[comb_N][i]) 
             && waveArray[ snakeArray[HEAD]+mov[comb_N][i] ] < min )
        {
            min = waveArray[ snakeArray[HEAD]+mov[comb_N][i] ];
            bestMove = mov[comb_N][i];
        }
    }
    
    return bestMove;
}


int chooseLongestSafeMove(int comb_N, int [] snakeArray, int [] waveArray)
{
    int bestMove = ERR;
    int max  = -1;

    for (int i = 0; i < 4  ; ++i)
    {
        if ( isMoveIsPossible(snakeArray[HEAD], mov[comb_N][i])
             && waveArray[snakeArray[HEAD]+mov[comb_N][i] ] < UNDEFINED
             && waveArray[snakeArray[HEAD]+mov[comb_N][i] ] > max )
        {
            max = waveArray[snakeArray[HEAD]+mov[comb_N][i]];
            bestMove = mov[comb_N][i];
        }
    }

    return bestMove;
}

boolean isMoveIsPossible(int cell_index, int move)
{
    boolean flag = false;
    switch (move)
    {
    case LEFT:
        flag = (cell_index % WIDTH_SIZE > 0) ? true : false;
        break;
    case RIGHT:
        flag = (cell_index % WIDTH_SIZE < WIDTH_MAX) ? true : false;
        break;
    case (-8):
        flag = (cell_index > WIDTH_MAX) ? true : false;
        break;
    case 8:
        flag = (cell_index < (FIELD_SIZE - WIDTH_SIZE) ) ? true : false;
        break;
    default:
        flag = false;
        break;
    }

    return flag;
}

boolean isTailInside()//(int tail)
{
    //waveMatrixReset(tempSnake, tempSnakeSize, tempWave);
    tempWave[ tempSnake[tempSnakeSize-1] ] = 0;
    tempWave[ cookie ] = OBSTACLE;
    boolean result = waveMatrixRefresh_full(tempSnake[tempSnakeSize-1], tempSnake, tempWave);

    for (int i = 0; i < 4 ; ++i)
    {
        if( isMoveIsPossible(tempSnake[HEAD], mov[0][i])
         && tempSnake[HEAD]+mov[0][i] == tempSnake[(tempSnakeSize-1)]
         && tempSnakeSize>3 )
            result = false;
    }

    return result;
}



int followTail()
{
    println("followTail");
    tempSnakeSize = snakeSize;
    for (int i=0; i<snakeSize; ++i) tempSnake[i] = snake[i];
    waveMatrixReset(tempSnake, tempSnakeSize, tempWave);
    tempWave[ tempSnake[tempSnakeSize-1] ] = 0;
    tempWave[ cookie ] = 255;
    waveMatrixRefresh_full(tempSnake[tempSnakeSize-1], tempSnake, tempWave);
    tempWave[ tempSnake[tempSnakeSize-1] ] = 255;

    return chooseLongestSafeMove(0, tempSnake, tempWave);
}



int anyPossibleMove()
{
    int bestMove = ERR;

    waveMatrixReset(snake,snakeSize,wave);
    waveMatrixRefresh_full(cookie,snake,wave);

    int min  = OBSTACLE;

    for (int i = 0; i < 4  ; ++i)
    {
        if ( isMoveIsPossible(snake[HEAD], mov[0][i])
             && wave[ snake[HEAD]+mov[0][i] ] < min )
        {
            min = wave[ snake[HEAD]+mov[0][i] ];
            bestMove = mov[0][i];
        }
    }

    return bestMove;
}


boolean makeMove(int bestMove)
{
    //Shift snake array to the right
    //In snake[snakeSize] we store a previous Tail coord,
    //  to delete it in future if snake doesn't eated a cookie.
    //snake[HEAD] is ready for storing new HEAD coord.

    for (int i = snakeSize; i>0 ; --i)
    {   snake[i] = snake[i-1];    }
    snake[HEAD] += bestMove;
    drawRect(snake[HEAD]%8, snake[HEAD]/8, 255, 255, 0);
        
    if (snake[HEAD] == cookie)
    {
        ++snakeSize;
        if (snakeSize<FIELD_SIZE) newCookie();
        //waveMatrixReset(snake, snakeSize, wave);
        
        return true;
    }
    else // instead "waveMatrixReset" - fill wave's cells manually
    {
        wave[snake[HEAD]] = OBSTACLE;
        wave[snake[snakeSize]] =  UNDEFINED;
        drawRect(snake[snakeSize]%8, snake[snakeSize]/8, 128, 128, 255);   
        return false;
    }
}

int newCookie()
{
    do
    {
        cookie=int(random(FIELD_SIZE));
    }
    while (!isCellIsFree(cookie, snake, snakeSize));
    
    drawRect(cookie%8, cookie/8, 0, 255, 0); 

    return 0;
}

int shiftArrayRight(int [] array, int size)
{
    for (int i = size; i>0 ; --i)
    {   array[i] = array[i-1];    }
    return 0;
}










boolean isCellIsFree(int cell_index, int [] snakeArray, int snakeArraySize)
{
    for (int i = 0; i < snakeArraySize ; ++i)
    {
        if (cell_index == snakeArray[i]) return false;
    }
    return true;
}

//Erase and apply snake & cookie coordinates on waveMap
int waveMatrixReset(int [] snakeArray, int snakeArraySize, int [] waveArray)
{
    for (int i = 0; i < FIELD_SIZE; ++i)
    {
        if ( i == cookie)
        {   waveArray[i]= COOKIE;   }
        else if ( isCellIsFree(i, snakeArray, snakeArraySize) )
        {   waveArray[i]= UNDEFINED; }
        else
        {   waveArray[i]= OBSTACLE; }
    }
    return 0;
}


boolean waveMatrixRefresh_full(int target, int[] snake, int [] wave)
{
    boolean isWaveChanged;
    boolean flag = false;

    do //while(isWaveChanged)
    {
        isWaveChanged = false;

        for (int i = 0; i < FIELD_SIZE ; ++i)
        {
            // if cell is UNDEFINED
            //   if move is possible from this position
            //   and target cell have a less value
            //     then put this value in "min"
            if (wave[i] < OBSTACLE)
            {
                int min = wave[i];

                for (int j = 0; j < 4  ; ++j)
                {
                    if ( isMoveIsPossible(i, mov[0][j]) &&  wave[ i+mov[0][j] ] < min  )
                        min = wave[ i+mov[0][j] ];
                }

                if ( wave[i] > min+1)
                {
                    wave[i] = min+1;
                    isWaveChanged = true;
                }
            }
        }


        // Optimization - wave starts from bottom
        if (!isWaveChanged) break;
        isWaveChanged = false;

        for (int i = FIELD_SIZE-1; i >= 0 ; --i)
        {
            if (wave[i] < OBSTACLE)
            {
                int min = wave[i];

                for (int j = 0; j < 4  ; ++j)
                {
                    if ( isMoveIsPossible(i, mov[0][j]) &&  wave[ i+mov[0][j] ] < min  )
                        min = wave[ i+mov[0][j] ];
                }

                if ( wave[i] > min+1)
                {
                    wave[i] = min+1;
                    isWaveChanged = true;
                }
            }
        }
    }
    while (isWaveChanged);
    
    for (int i = 0; i < 4  ; ++i)
    {
        if ( isMoveIsPossible(snake[HEAD], mov[0][i])
             && wave[snake[HEAD]+mov[0][i] ] < UNDEFINED )
        {
            flag=true;
        }
    }
    return flag;
}



void gameOver()
{
  drawRect(7, 7, 255, 0, 0);   
  delay(2000);
}

void drawRect(int x, int y, int R, int G, int B)
{
  fill(R,G,B);
  rect(x*width/8,y*height/8,width/8,height/8);
}

void printWaveMatrix(int [] wave)
{
    for (int i = 0; i < FIELD_SIZE ; ++i)
    {      
        fill(255, 255, 255);
        text(wave[i], i%8*width/8, i/8*height/8);
        fill(128,128,255);   
    }
}

void drawGameScreen()
{
    background(128, 128, 255);
    drawRect(cookie%8, cookie/8,   0, 255, 0);
    for (int i = 0; i < snakeSize ; ++i)
    {
      drawRect(snake[i]%8,snake[i]/8,   255,255-i,0);
    }
    
    if (showArray==1) printWaveMatrix(wave);
}

void gameControl()
{
  if (keyPressed) 
  {
    if (key == '+')
    {
      if (speed>2) speed/=2;
    } 
    else if (key == '-') 
    {
      speed*=2; 
    }
    else if (key == '*') 
    {
      showArray = (showArray+1)%2; 
    }
  }
}