var WHITE = 'w';
var BLACK = 'b';

var PHASE_III_LEN = 4;

function GetDigit(ch)
{
  switch(ch)
  {
    case '0': return 0;
    case '1': return 1;
    case '2': return 2;
    case '3': return 3;
    case '4': return 4;
    case '5': return 5;
    case '6': return 6;
    case '7': return 7;
    case '8': return 8;
    case '9': return 9;
    default: return -1;
  }
  
}

function GetColor(piece)
{
  switch(piece)
  {
    case 'k':
    case 's':
    case 'q':
    case 't':
    case 'r':
    case 'c':
    case 'd':
    case 'e':
    case 'h':
    case 'b':
    case 'n':
    case 'f':
    case 'v':
    case 'p':
      return BLACK;
    case 'K':
    case 'S':
    case 'Q':
    case 'T':
    case 'R':
    case 'C':
    case 'D':
    case 'E':
    case 'H':
    case 'B':
    case 'N':
    case 'F':
    case 'V':
    case 'P':
      return WHITE;
  }
  return '';
}


function BuildPosition(fen)
{
  var ch;

  var retValue = new Object();

  var fenIndex = 0;
  var wasSpace;
  var file;
  var rank;

  for(;;)
  {
    if (fenIndex >= fen.length) return 'Empty fen!';
    ch = fen.charAt(fenIndex);
    ++fenIndex;

    if (ch != ' ') break;
  }

  switch(ch)
  {
    case '1':
      retValue.phase = 1;
      break;
    case '2':
      retValue.phase = 2;
      break;
    case '3':
      retValue.phase = 3;
      break;
    case '4':
      retValue.phase = 4;
      break;
    default:
      return 'Unknown PHASE :(';
  }

  wasSpace = false;
  for(;;)
  {
    if (fenIndex >= fen.length) return 'No PHASE ARG!';
    ch = fen.charAt(fenIndex);
    ++fenIndex;

    if (ch == ' ') 
      wasSpace = true;
    else {
      if (wasSpace) break;
      return 'The space must be placed betweeen PHASE ARG and PHASE.';
    } 
  }

  if (ch == '-')
  {
    retValue.phaseArg = -1;
    if (fenIndex >= fen.length) return 'No POSITION!';
    ch = fen.charAt(fenIndex);
    ++fenIndex;
  }
  else if (ch >= '0' && ch <= '9')
  {
    retValue.phaseArg = 0;
    for(;;)
    {
      if (ch == ' ') break;
      if (ch < '0' || ch > '9') return 'Error in PHASE ARG!';
      retValue.phaseArg = 10*retValue.phaseArg + GetDigit(ch);
      if (fenIndex >= fen.length) return 'No POSITION!';
      ch = fen.charAt(fenIndex);
      ++fenIndex;
    }
  }
  else
    return 'That PHASE ARG expected (minus or integer value)';

  function ReadPosition()
  {
    var retValue = new Array(0x88);

    for(;;)
    {
      if (fenIndex >= fen.length) return 'No POSITION!';
      ch = fen.charAt(fenIndex);
      ++fenIndex;
      if (ch != ' ') break;
    }

    file = 0;
    rank = 7;
    for(;;)
    {
      if (ch == ' ') break;
      switch(ch)
      {
        case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': 
          for (counterCh = '1'; counterCh <= ch; ++counterCh)
          {
            if (file >= 8) 
            {
              return 'Rank overflow in pFEN! = ' + fenIndex;
            }
            retValue[file + 16*rank] = '.';
            file = file + 1;
          }  
          break;
        case '$':
        case 'k': case 'K':
        case 's': case 'S':
        case 'q': case 'Q':
        case 't': case 'T':
        case 'r': case 'R':
        case 'c': case 'C':
        case 'd': case 'D':
        case 'e': case 'E':
        case 'h': case 'H':
        case 'b': case 'B':
        case 'n': case 'N':
        case 'f': case 'F':
        case 'v': case 'V':
        case 'p': case 'P':
          if (file >= 8) return 'Rank overflow in rFEN string, position = ' + fenIndex;
          retValue[file + 16*rank] = ch;
          file = file + 1;
          break;
        case '/':
          if (file != 8) return 'End rank symbol was appeared, but rank is not complitly defined in pFEN, position = ' + fenIndex;
          if (rank == 0) return 'End rank symbol was appeared, but all position was read in pFEN, position = ' + fenIndex;
          file = 0;
          rank = rank - 1;
          break;
        default: 
          return 'Invalid character "' + ch + '" in FEN, position = ' + fenIndex;
      }
      
      if (fenIndex >= fen.length)  return 'Unexpected end of FEN!';
      ch = fen.charAt(fenIndex);
      ++fenIndex;
    }

    if (file != 8 || rank != 0) return 'Fen is not complete read.'

    return retValue;
  }

  retValue.board = ReadPosition();
  if (typeof(retValue.board) == 'string') return retValue.board;
  if (fenIndex >= fen.length)  return 'No RESERVE FEN';
  retValue.reserv = ReadPosition();
  if (typeof(retValue.reserv) == 'string') return retValue.reserv;

  for(;;)
  {
    if (fenIndex >= fen.length) return 'No active side';
    ch = fen.charAt(fenIndex);
    ++fenIndex;

    if (ch == WHITE || ch == BLACK) break;
    if (ch != ' ')
      return 'Invalid character "' + ch + '" in FEN string, position = ' + fenIndex;
  }
  
  retValue.activeSide = ch;
  retValue.result = '*';

  return retValue;  
}

function ArrayToFen(array)
{
  var fen = '';
  var skipped = 0;
  for (rank=7; rank>=0; --rank)
  {
    for (file=0; file<8; ++file)
    {
      var piece = array[file + 16*rank];
      if (piece == '.')
      {
        ++skipped;
      }
      else {
        if (skipped > 0) fen = fen + skipped;
        skipped = 0;
        fen = fen + piece;
      }
    }
    if (skipped > 0) fen = fen + skipped;
    skipped = 0;
    if (rank != 0) fen = fen + '/';
  }

  return fen;
}


function BuildFen(position)
{
  var retValue = '';

  retValue += position.phase + ' ';
  retValue += (position.phaseArg >= 0 ? position.phaseArg : '-' ) + ' ';

  retValue += ArrayToFen(position.board) + ' ';
  retValue += ArrayToFen(position.reserv) + ' ';

  retValue += position.activeSide;
  return retValue;
}

function DoMove1(position, drag, fileFrom, rankFrom, fileTo, rankTo)
{
  if (drag != 'reserve') return false;
  if (position.reserv[fileFrom + 16*rankFrom] != '$') return false;
  if (position.board[fileTo + 16*rankTo] != '.') return false;

  if (position.activeSide == 'w')
  {
    if (rankFrom != 3) return false;
    if (rankTo > 3) return false;
  }
  else {
    if (rankFrom != 4) return false;
    if (rankTo < 4) return false;
  }

  position.board[fileTo + 16*rankTo] = position.reserv[fileFrom + 16*rankFrom];
  position.reserv[fileFrom + 16*rankFrom] = '.';

  --position.phaseArg;
  if (position.phaseArg == 0) 
  {
    ++position.phase;
    position.phaseArg = 2;
  }

  position.activeSide = position.activeSide == WHITE ? BLACK : WHITE;

  return true;
}

function ClearObstacle(position, a, b)
{
  if (position.reserv[a] == '$')
    position.reserv[a] = '.';
  else
    position.reserv[b] = '.';
}

function DoPass1(position)
{
  if (position.activeSide == WHITE)
    ClearObstacle(position, 0x33, 0x34);
  else 
    ClearObstacle(position, 0x43, 0x44);

  --position.phaseArg;
  if (position.phaseArg == 0) 
  {
    ++position.phase;
    position.phaseArg = 2;
  }

  position.activeSide = position.activeSide == WHITE ? BLACK : WHITE;
  return true;
}

function CalculateAvailable1(position, drag, file, rank)
{
  if (drag != 'reserve') return null;
  if (position.reserv[file + 16*rank] != '$') return null;

  if (position.activeSide == 'w')
  {
    if (rank != 3) return null;
    if (rank > 3) return null;
  }
  else {
    if (rank != 4) return null;
    if (rank < 4) return null;
  }

  var retValue = new Array(0x88);

  for (f=0; f<8; ++f)
    for (r=0; r<8; ++r)
  {
    var i = f + 16*r;
    if (position.activeSide == WHITE)
      retValue[i] = r <= 3 && position.board[i] == '.';
    else
      retValue[i] = r >= 4 && position.board[i] == '.';
  }

  return retValue;
}

function DoMove2(position, drag, fileFrom, rankFrom, fileTo, rankTo)
{
  if (drag != 'reserve') return false;
  var what = position.reserv[fileFrom + 16*rankFrom];
  if (position.activeSide == 'w' && what != 'K') return false;
  if (position.activeSide == 'b' && what != 'k') return false;
  if (position.board[fileTo + 16*rankTo] != '.') return false;

  if (position.activeSide == 'w' && rankTo >= 3) return false;
  if (position.activeSide == 'b' && rankTo <= 4) return false;

  position.board[fileTo + 16*rankTo] = position.reserv[fileFrom + 16*rankFrom];
  position.reserv[fileFrom + 16*rankFrom] = '.';

  --position.phaseArg;
  if (position.phaseArg == 0) 
  {
    ++position.phase;
    position.phaseArg = PHASE_III_LEN;
  }

  position.activeSide = position.activeSide == WHITE ? BLACK : WHITE;

  return true;
}


function CalculateAvailable2(position, drag, file, rank)
{
  if (drag != 'reserve') return null;
  var what = position.reserv[file + 16*rank];
  if (position.activeSide == 'w' && what != 'K') return null;
  if (position.activeSide == 'b' && what != 'k') return null;

  var retValue = new Array(0x88);

  for (f=0; f<8; ++f)
    for (r=0; r<8; ++r)
  {
    var i = f + 16*r;
    if (position.activeSide == WHITE)
      retValue[i] = r <= 2 && position.board[i] == '.';
    else
      retValue[i] = r >= 5 && position.board[i] == '.';
  }

  return retValue;
}

function DoMove3(position, drag, fileFrom, rankFrom, fileTo, rankTo)
{
  if (drag != 'reserve') return false;
  var what = position.reserv[fileFrom + 16*rankFrom];
  if (position.activeSide != GetColor(what)) return false;
  if (position.board[fileTo + 16*rankTo] != '.') return false;

  if (position.activeSide == 'w' && rankTo >= 3) return false;
  if (position.activeSide == 'b' && rankTo <= 4) return false;

  position.board[fileTo + 16*rankTo] = position.reserv[fileFrom + 16*rankFrom];
  position.reserv[fileFrom + 16*rankFrom] = '.';

  --position.phaseArg;
  if (position.phaseArg == 0) 
  {
    ++position.phase;
    position.phaseArg = -1;
  }

  position.activeSide = position.activeSide == WHITE ? BLACK : WHITE;

  return true;
}

function CalculateAvailable3(position, drag, file, rank)
{
  if (drag != 'reserve') return null;
  var what = position.reserv[file + 16*rank];
  if (position.activeSide != GetColor(what)) return null;

  var retValue = new Array(0x88);

  for (f=0; f<8; ++f)
    for (r=0; r<8; ++r)
  {
    var i = f + 16*r;
    if (position.activeSide == WHITE)
      retValue[i] = r <= 2 && position.board[i] == '.';
    else
      retValue[i] = r >= 5 && position.board[i] == '.';
  }

  return retValue;
}

function DoPass3(position)
{
  --position.phaseArg;
  if (position.phaseArg == 0) 
  {
    ++position.phase;
    position.phaseArg = -1;
  }

  position.activeSide = position.activeSide == WHITE ? BLACK : WHITE;
  return true;
}

function DoMove4(position, drag, fileFrom, rankFrom, fileTo, rankTo)
{
  if (drag == 'reserve') 
    return DoMove3(position, drag, fileFrom, rankFrom, fileTo, rankTo)

  var available = CalculateAvailable4(position, 'board', fileFrom, rankFrom);
  if (available == null) return false;
  if (available[fileTo + 16*rankTo] == 0) return false;

  var killed = position.board[fileTo + 16*rankTo];
  var whoMove = position.board[fileFrom + 16*rankFrom];

  if ((whoMove == 'c' || whoMove == 'C') && (killed != '.'))
  {
    position.board[fileTo + 16*rankTo] = '.';
  }
  else if ((whoMove == 'd' || whoMove == 'D') && (killed != '.'))
  {
    var pFrom = fileFrom + 16*rankFrom;
    var pTo = fileTo + 16*rankTo;
    position.board[pTo] = '.';

    var delta;
    if (pFrom > pTo)
    {
      delta = pFrom - pTo;
      if (delta % 2 == 0)
        delta /= 2;
      else 
        delta *= 2;
      delta = -delta;
    }
    else {
      delta = pTo - pFrom;
      if (delta % 2 == 0)
        delta /= 2;
      else 
        delta *= 2;
    }

    if (((pFrom + delta) & 0x88) == 0)
      if (position.board[pFrom + delta] != '$')
        position.board[pFrom + delta] = '.';
        
  }
  else {
    position.board[fileTo + 16*rankTo] = whoMove;
    position.board[fileFrom + 16*rankFrom] = '.';
  }

  if (killed == 'F' || killed == 'f')
    position.board[fileTo + 16*rankTo] = '.';

  if (killed != '.' && (whoMove == 'F'|| whoMove == 'f'))
    FanaticBoom(position, fileTo + 16*rankTo);

  position.activeSide = position.activeSide == WHITE ? BLACK : WHITE;
  CheckPromotion(position);
  CheckResult(position);
  return true;
}

function CalculateAvailable4(position, drag, file, rank)
{
  if (drag == 'reserve') 
    return CalculateAvailable3(position, drag, file, rank);

  var piece = position.board[file + 16*rank];
  if (GetColor(piece) != position.activeSide)
    return null;

  switch(piece)
  {
    case 'k':
    case 'K':
      return KingAvailable(position, file, rank);
    case 's':
    case 'S':
      return SuccessorAvailable(position, file, rank);
    case 'q':
    case 'Q':
      return QueenAvailable(position, file, rank);
    case 't':
    case 'T':
      return TurkaAvailable(position, file, rank);
    case 'r':
    case 'R':
      return RookAvailable(position, file, rank);
    case 'c':
    case 'C':
      return CannonAvailable(position, file, rank);
    case 'd':
    case 'D':
      return DragonAvailable(position, file, rank);
    case 'e':
    case 'E':
      return ElephantAvailable(position, file, rank);
    case 'h':
    case 'H':
      return HorsemanAvailable(position, file, rank);
    case 'b':
    case 'B':
      return BishopAvailable(position, file, rank);
    case 'n':
    case 'N':
      return KnightAvailable(position, file, rank);
    case 'f':
    case 'F':
      return FanaticAvailable(position, file, rank);
    case 'v':
    case 'V':
      return VeteranAvailable(position, file, rank);
    case 'p':
    case 'P':
      return PawnAvailable(position, file, rank);
    default:
      return null;
  }
}

function DoPass4(position)
{
  position.activeSide = position.activeSide == WHITE ? BLACK : WHITE;
  return true;
}

function DoPass(position)
{
  switch(position.phase)
  {
    case 1:
      return DoPass1(position);
    case 2:
      return false;
    case 3:
      return DoPass3(position);
    case 4:
      return DoPass4(position);
    default:
      return false;
  }
}

function DoMove(position, drag, fileFrom, rankFrom, fileTo, rankTo)
{
  switch(position.phase)
  {
    case 1:
      return DoMove1(position, drag, fileFrom, rankFrom, fileTo, rankTo);
    case 2:
      return DoMove2(position, drag, fileFrom, rankFrom, fileTo, rankTo);
    case 3:
      return DoMove3(position, drag, fileFrom, rankFrom, fileTo, rankTo);
    case 4:
      return DoMove4(position, drag, fileFrom, rankFrom, fileTo, rankTo);
    default:
      return false;
  }
}

function CalculateAvailable(position, drag, file, rank)
{
  switch(position.phase)
  {
    case 1:
      return CalculateAvailable1(position, drag, file, rank)
    case 2:
      return CalculateAvailable2(position, drag, file, rank)
    case 3:
      return CalculateAvailable3(position, drag, file, rank)
    case 4:
      return CalculateAvailable4(position, drag, file, rank)
    default:
      return null;
  }
}