/**
 * @license
 * Blockly Tests
 *
 * Copyright 2016 Google Inc.
 * https://developers.google.com/blockly/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Tests for connection logic.
 * @author fenichel@google.com (Rachel Fenichel)
 */
'use strict';

var input;
var output;
var previous;
var next;

var dummyWorkspace;

function connectionTest_setUp() {
  dummyWorkspace = {};
  function createDummyBlock() {
    return {
      workspace: dummyWorkspace,
      isShadow: function() {return false;}
    };
  }
  input = new Blockly.Connection(createDummyBlock(),
      Blockly.INPUT_VALUE);
  output = new Blockly.Connection(createDummyBlock(),
      Blockly.OUTPUT_VALUE);
  previous = new Blockly.Connection(createDummyBlock(),
      Blockly.PREVIOUS_STATEMENT);
  next = new Blockly.Connection(createDummyBlock(),
      Blockly.NEXT_STATEMENT);
}

function connectionTest_tearDown() {
  input = null;
  output = null;
  previous = null;
  next = null;
  dummyWorkspace = null;
}

var isMovableFn = function() { return true; };
/**
 * These tests check that the reasons for failures to connect are consistent
 * (internal view of error states).
 */
function testCanConnectWithReason_TargetNull() {
  connectionTest_setUp();

  assertEquals(Blockly.Connection.REASON_TARGET_NULL,
      input.canConnectWithReason_(null));

  connectionTest_tearDown();
}

function testCanConnectWithReason_Disconnect() {
  connectionTest_setUp();

  var tempConnection = new Blockly.Connection({workspace: dummyWorkspace, isMovable: isMovableFn},
      Blockly.OUTPUT_VALUE);
  Blockly.Connection.connectReciprocally_(input, tempConnection);
  assertEquals(Blockly.Connection.CAN_CONNECT,
      input.canConnectWithReason_(output));

  connectionTest_tearDown();
}

function testCanConnectWithReason_DifferentWorkspaces() {
  connectionTest_setUp();

  input = new Blockly.Connection({workspace: {}}, Blockly.INPUT_VALUE);
  output = new Blockly.Connection({workspace: dummyWorkspace},
      Blockly.OUTPUT_VALUE);

  assertEquals(Blockly.Connection.REASON_DIFFERENT_WORKSPACES,
      input.canConnectWithReason_(output));

  connectionTest_tearDown();
}


function testCanConnectWithReason_Self() {
  connectionTest_setUp();

  var block = {type_: "test block"};
  input.sourceBlock_ = block;
  assertEquals(Blockly.Connection.REASON_SELF_CONNECTION,
      input.canConnectWithReason_(input));

  connectionTest_tearDown();
}

function testCanConnectWithReason_Type() {
  connectionTest_setUp();

  assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
      input.canConnectWithReason_(previous));
  assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
      input.canConnectWithReason_(next));

  assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
      output.canConnectWithReason_(previous));
  assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
      output.canConnectWithReason_(next));

  assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
      previous.canConnectWithReason_(input));
  assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
      previous.canConnectWithReason_(output));

  assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
      next.canConnectWithReason_(input));
  assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
      next.canConnectWithReason_(output));

  connectionTest_tearDown();
}

function testCanConnectWithReason_CanConnect() {
  connectionTest_setUp();

  assertEquals(Blockly.Connection.CAN_CONNECT,
      previous.canConnectWithReason_(next));
  assertEquals(Blockly.Connection.CAN_CONNECT,
      next.canConnectWithReason_(previous));
  assertEquals(Blockly.Connection.CAN_CONNECT,
      input.canConnectWithReason_(output));
  assertEquals(Blockly.Connection.CAN_CONNECT,
      output.canConnectWithReason_(input));

  connectionTest_tearDown();
}

/**
 * The next set of tests checks that exceptions are being thrown at the correct
 * times (external view of errors).
 */
function testCheckConnection_Self() {
  connectionTest_setUp();
  var block = {type_: "test block"};
  input.sourceBlock_ = block;
  try {
    input.checkConnection_(input);
    fail();
  } catch (e) {
    // expected
  }

  connectionTest_tearDown();
}

function testCheckConnection_TypeInputPrev() {
  connectionTest_setUp();
  try {
    input.checkConnection_(previous);
    fail();
  } catch (e) {
    // expected
  }

  connectionTest_tearDown();
}

function testCheckConnection_TypeInputNext() {
  connectionTest_setUp();
  try {
    input.checkConnection_(next);
    fail();
  } catch (e) {
    // expected
  }

  connectionTest_tearDown();
}

function testCheckConnection_TypeOutputPrev() {
  connectionTest_setUp();
  try {
    output.checkConnection_(previous);
    fail();
  } catch (e) {
    // expected
  }

  connectionTest_tearDown();
}

function testCheckConnection_TypePrevInput() {
  connectionTest_setUp();
  try {
    previous.checkConnection_(input);
    fail();
  } catch (e) {
    // expected
  }

  connectionTest_tearDown();
}

function testCheckConnection_TypePrevOutput() {
  connectionTest_setUp();
  try {
    previous.checkConnection_(output);
    fail();
  } catch (e) {
    // expected
  }

  connectionTest_tearDown();
}

function testCheckConnection_TypeNextInput() {
  connectionTest_setUp();
  try {
    next.checkConnection_(input);
    fail();
  } catch (e) {
    // expected
  }

  connectionTest_tearDown();
}

function testCheckConnection_TypeNextOutput() {
  connectionTest_setUp();
  try {
    next.checkConnection_(output);
    fail();
  } catch (e) {
    // expected
  }

  connectionTest_tearDown();
}

function test_isConnectionAllowed_Distance() {
  var sharedWorkspace = {};
  // Two connections of opposite types near each other.
  var one = helper_createConnection(5 /* x */, 10 /* y */,
      Blockly.INPUT_VALUE, null, true);
  one.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);

  var two = helper_createConnection(10 /* x */, 15 /* y */,
      Blockly.OUTPUT_VALUE, null, true);
  two.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);

  assertTrue(two.isConnectionAllowed(one, 20.0));
  // Move connections farther apart.
  two.x_ = 100;
  two.y_ = 100;
  assertFalse(two.isConnectionAllowed(one, 20.0));
}

function test_isConnectionAllowed_Unrendered() {
  var sharedWorkspace = {};

  var one = helper_createConnection(5 /* x */, 10 /* y */,
      Blockly.INPUT_VALUE);
  one.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);

  // Don't offer to connect a left (male) value plug to
  // an available right (female) value plug.
  // Unlike in Blockly, you can't do this even if the left value plug isn't
  // already connected.
  var two = helper_createConnection(0, 0, Blockly.OUTPUT_VALUE);
  two.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);

  assertFalse(one.isConnectionAllowed(two));
  var three = helper_createConnection(0, 0, Blockly.INPUT_VALUE);
  three.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);

  Blockly.Connection.connectReciprocally_(two, three);
  assertFalse(one.isConnectionAllowed(two));

  // Don't connect two connections on the same block.
  two.sourceBlock_ = one.sourceBlock_;
  assertFalse(one.isConnectionAllowed(two));
}

function test_isConnectionAllowed_NoNext() {
  var sharedWorkspace = {};
  var one = helper_createConnection(0, 0, Blockly.NEXT_STATEMENT);
  one.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
  one.sourceBlock_.nextConnection = one;

  var two = helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT);
  two.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
  two.sourceBlock_.previousConnection = two;

  assertTrue(two.isConnectionAllowed(one));

  var three = helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT);
  three.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
  three.sourceBlock_.previousConnection = three;
  Blockly.Connection.connectReciprocally_(one, three);

  // A terminal block is allowed to replace another terminal block.
  assertTrue(two.isConnectionAllowed(one));
}

function test_isConnectionAllowed_InsertionMarker() {
  var sharedWorkspace = {};
  // Two connections of opposite types near each other.
  var one = helper_createConnection(5 /* x */, 10 /* y */,
      Blockly.INPUT_VALUE);
  one.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);

  // The second one is an insertion marker.
  var two = helper_createConnection(10 /* x */, 15 /* y */,
      Blockly.OUTPUT_VALUE);
  two.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
  two.sourceBlock_.isInsertionMarker = function() {
      return true;
    };

  assertFalse(one.isConnectionAllowed(two, 20.0));
}

function testCheckConnection_Okay() {
  connectionTest_setUp();
  previous.checkConnection_(next);
  next.checkConnection_(previous);
  input.checkConnection_(output);
  output.checkConnection_(input);

  connectionTest_tearDown();
}

function test_canConnectWithReason_Procedures_WrongBlockType() {
  var sharedWorkspace = {};
  var one = helper_createConnection(0, 0, Blockly.NEXT_STATEMENT);
  one.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
  one.sourceBlock_.type = Blockly.PROCEDURES_DEFINITION_BLOCK_TYPE;
  // Make one be the connection on its source block's input.
  one.sourceBlock_.getInput = function() {
    return {
      connection: one
    };
  };

  var two = helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT);
  two.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
  // Fail because two's source block is the wrong type.
  two.sourceBlock_.type = 'wrong_type';
  assertEquals(Blockly.Connection.REASON_CUSTOM_PROCEDURE,
      one.canConnectWithReason_(two));
}

function test_canConnectWithReason_Procedures_Pass() {
  var sharedWorkspace = {};
  var one = helper_createConnection(0, 0, Blockly.NEXT_STATEMENT);
  one.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
  one.sourceBlock_.type = Blockly.PROCEDURES_DEFINITION_BLOCK_TYPE;
  // Make one be the connection on its source block's input.
  one.sourceBlock_.getInput = function() {
    return {
      connection: one
    };
  };
  var two = helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT);
  two.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
  two.sourceBlock_.type = Blockly.PROCEDURES_PROTOTYPE_BLOCK_TYPE;
  assertEquals(Blockly.Connection.CAN_CONNECT,
      one.canConnectWithReason_(two));
}

function test_canConnectWithReason_Procedures_NextConnection() {
  var sharedWorkspace = {};
  var one = helper_createConnection(0, 0, Blockly.NEXT_STATEMENT);
  one.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
  one.sourceBlock_.type = Blockly.PROCEDURES_DEFINITION_BLOCK_TYPE;
  // One is the next connection, not an input connection
  one.sourceBlock_.nextConnection = one;
  one.sourceBlock_.getInput = function() {
    return {
      connection: null
    };
  };
  var two = helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT);
  two.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
  // It should be okay, even if two's source block has the wrong type, because
  // it's not trying to connect to the input.
  two.sourceBlock_.type = 'wrong_type';
  assertEquals(Blockly.Connection.CAN_CONNECT,
      one.canConnectWithReason_(two));
}