context = describe;

describe('the JavaScript language', () => {
  describe('has different types and operators', () => {
    it('considers numbers to be equal to their string representation', () => {
      expect(1 == '1').toBeTruthy();
      expect(1 != '1').toBeFalsy();
    });

    it('knows that numbers and strings are not exactly the same', () => {
      expect(1 === '1').toBeFalsy();
      expect(1 !== '1').toBeTruthy();
    });

    it('joins parts as string when using the plus operator', () => {
      expect(1 + 'a').toEqual('1a');
    });

    it('operates integers before joining the string', () => {
      expect(1 + 1 + '2').toEqual('22');
    });

    it('knows the type of the variable', () => {
      const x = 1;

      expect(typeof x).toEqual('number');
    });

    it('surprises me, NaN is not comparable with NaN', () => {
      expect(5 / 'a').toEqual(5 / 'a');
      expect(typeof (NaN)).toEqual(typeof (NaN));
      expect(isNaN(5 / 'a')).toBeTruthy();
    });

    it('considers an empty string to be falsy', () => {
      expect("" == false).toBeTruthy();// Truthy or Falsy
      expect("" === false).toBeFalsy();// Truthy or Falsy
    });

    it('considers zero to be falsy', () => {
      expect(0 == false).toBeTruthy();// Truthy or Falsy
      expect(0 === false).toBeFalsy();// Truthy or Falsy
    });

    it('considers nulls to be falsy', () => {
      const x = null;
      let result;

      if (x) {
        result = true;
      } else {
        result = false;
      }

      expect(result == false).toBeTruthy();// Truthy or Falsy
      expect(null === false).toBeFalsy();// Truthy or Falsy
      expect(null == false).toBeFalsy();// Truthy or Falsy   // No lo entendemos
    });

    it('knows the type of a function', () => {
      function x() {
        // ...
      }

      expect(typeof x).toBe('function');
      expect(typeof (xxx)).toBe('undefined');
    });

    it('has arrays and they can contain anything inside', () => {
      const arr = [1, 2, 3, 4];
      arr.push(5);
      arr[9] = 6;

      const matrix = [['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 7, 8]];



      expect(arr[1]).toEqual(2);
      expect(arr[4]).toEqual(5);
      expect(arr[6]).toEqual(undefined);
      expect(arr[9]).toEqual(6);
      expect(matrix[0][2]).toEqual('c');

    });

    it('may contain functions inside arrays', () => {
      const arr = [
        1,
        2,
        function (arg) {
          return 3 + arg;
        }
      ];

      expect(arr[2](1)).toEqual(4);
    });

    it('concatenate arrays - well, kind of', () => {
      const a = [1, 2, 3];
      const b = [4, 5, 6];

      expect(a + b).toEqual("1,2,34,5,6");  // Jasmine no detecta el test
    });

    it('joins arrays and strings', () => {
      const a = [1, 2, 3];

      expect("1" + a).toEqual('11,2,3');
      expect(a + "1").toEqual('1,2,31');
    });

    it('joins arrays and other things', () => {
      const a = [1, 2, 3];
      const b = ['x', 'y', 'z'];

      expect(1 + a).toEqual('11,2,3');
      expect(a + 1).toEqual('1,2,31');
      expect(1 + b).toEqual('1x,y,z');
      expect(true + a).toEqual("true1,2,3");
      // expect(true + "").toEqual("true");
    });

    it("can't compare arrays", () => {
      const a = [1, 2, 3];
      const b = [1, 2, 3];

      expect(a == b).toBeFalsy();  // Truthy or Falsy
      expect(a === b).toBeFalsy(); // Truthy or Falsy
    });

    it('is not the same to compare by value than by reference ', () => {
      const a = [1, 2, 3];
      const b = [1, 2, 3];

      expect(a).toEqual(b); // Jasmine toEqual compares by value
      expect(a).not.toBe(b); // Jasmine toBe compares by reference
    });
  });

  describe('considers functions as first class citizens', () => {
    it('can declare named functions', () => {
      function example() {
        return 'some example';
      }

      expect(example()).toEqual('some example');
    });

    it('can declare anonymous functions', () => {
      const someVar = function (a, b) {
        return a + b;
      };

      expect(typeof (someVar)).toBe("function");
      expect(someVar(1, 1)).toBe(2);
    });

    it('may return anything', () => {
      function example(arg) {
        return [arg, arg * 2, arg * 3];
      }

      const result = example(2);

      expect(result[1]).toEqual(4);
    });

    it('may return arrays that contains functions and so on', () => {
      function example() {
        return [
          function (popino) {
            return [popino, popino * 10]
          },
          "hola"
        ]
      }

      expect(example()[0](1)[1]).toEqual(10);
    });

    it("doesn't care about the declaration order when they are named", () => {
      function exampleA() {
        return exampleB(1);
      }

      expect(exampleA()).toEqual(1);

      function exampleB(arg1) {
        return arg1;
      }
    });

    it('matters, the declaration order when they are anonymous', () => {
      const exampleA = function () {
        return exampleB(1);
      };

      const exampleB = function (arg1) {
        return arg1;
      };

      expect(exampleA()).toEqual(1);


    });

    it('can use optional parameters', () => {
      function example(a, b, c) {
        if (c) {
          return a + b + c;
        }
        return a + b;
      }

      // expect(example(1,1,1)).toBe();
      // expect(example(1,1)).toBe();
    });

    it('anonymous functions are anonymous', () => {
      const x = function z() {
        return 1;
      };
      //expect(typeof(z)).toEqual();
      //expect(x()).toEqual();
    });

    it('can create closures with free variables', () => {
      function external() {
        const a = 1;

        function internal() {
          return a + 1;
        }

        return internal();
      }

      //expect(external()).toBe();
    });

    it('can create closures with several free variables', () => {
      function external() {
        const a = 1,
          b = 2;

        function internal() {
          const c = 3;
          return a + b + c;
        }
      }

      //expect(external()).toBe(6);
    });

    it('defines a pure function when there are no free variables', () => {
      function external() {
        const a = 1,
          b = 2;

        function internal(a, b) {
          const c = 1;
          return a + b + c;
        }

        return internal(4, 4);
      }

      //expect(external()).toBe();
    });

    it('may return arrays that contains closures and so on', () => {
      function example() {
        // write the missing code here
      }

      //expect(example()[0](1)[1]).toEqual(10);
      //expect(example()[0](2)[1]).toEqual(11);
      //expect(example()[0](3)[1]).toEqual(12);
    });

    it('passes primitive types as values (a copy) to functions', () => {
      function example(arg) {
        arg = 'test!';
      }

      const x = 1;
      const y = 'example';
      const z = true;

      example(x);
      //expect(x).toEqual();

      example(y);
      //expect(y).toEqual();

      example(z);
      //expect(z).toEqual();
    });

    it('passes arrays by reference', () => {
      function example(arg) {
        arg[0] = 100;
      }

      const x = [1, 2, 3];

      example(x);
      //expect(x).toEqual();
    });

    it('passes objects by reference', () => {
      function example(arg) {
        arg.property = 'test';
      }

      const x = { property: 'cool!' };

      example(x);
      //expect(x).toEqual();
    });

    it('may return a function as the result of invoking a function', () => {
      function add(a, b) {
        return a + b;
      }

      function example() {
        return add;
      }

      //expect(example()(1,2)).toEqual();
      const f = example();
      //expect(f(2,2)).toEqual();
    });

    it('can return closures as a function result', () => {
      function plus(amount) {
        return function (number) {
          return number + amount;
        };
      }

      const f = plus(5);

      //expect(f(3)).toBe();
    });

    it('can have functions that receive other functions as arguments', () => {
      function add(a, b) {
        return a + b;
      }

      function example(arg) {
        return arg(2, 2) + 1;
      }

      //expect(example(add)).toEqual();
    });

    it('may have functions as the input and the output', () => {
      function plus(originalFunction) {
        return function (arg1) {
          return originalFunction() + arg1;
        };
      }

      const f = plus(function () {
        return 1;
      });

      //expect(f(2)).toBe();
    });

    it("can invoke functions indirectly using the special 'call'", () => {
      function f(a, b) {
        return a + b;
      }

      //expect(f.call(f,1,1)).toEqual();
    });

    it("can invoke functions indirectly using the special 'apply'", () => {
      function f(a, b) {
        return a + b;
      }

      //expect(f.apply(f, [1,1])).toEqual();
    });

    it("doesn't have a private scope inside blocks", () => {
      let j = 0;
      for (let i = 0; i < 5; i++) {
        j += i;
      }

      //expect(i).toEqual();
      //expect(j).toEqual();
    });
  });

  describe('has multiple ways to define and create objects', () => {
    it('can define object literals', () => {
      const obj = {
        name: 'bob',
        theName: function () {
          return this.name;
        }
      };

      //expect(obj.theName()).toBe();
    });

    it('can create properties dynamically', () => {
      const obj = {
        name: 'bob',
        surname: 'sponge'
      };
      obj.address = 'palm tree';

      //expect(obj.address).toEqual();
      //expect(obj['address']).toEqual();
      //expect(obj['name']).toEqual();
    });

    it('may define complex objects', () => {
      let user;
      // write the contents of the obj to make the satisfy the expectations:

      //expect(user.address.street).toEqual('sesame');
      //expect(user.friends[0].name).toEqual('triki');
    });

    it('has a pattern called, the Module Pattern', () => {
      function createObject() {
        let points = 0;

        return {
          addPoint: function () {
            ++points;
          },
          score: function () {
            return points;
          }
        };
      }

      const obj = createObject();
      obj.addPoint();

      //expect(obj.score()).toEqual();
      //expect(typeof(obj.points)).toEqual();
    });

    it('may create objects also with the module pattern', () => {
      function createObject(initialScore) {
        // write the code here
      }

      /*
      const obj = createObject(5, 'red');
      obj.incrementScoreIn(5);
      expect(obj.color).toEqual('red');
      expect(obj.points()).toEqual(10);
      */
    });

    it('can define constructors', () => {
      function Obj() {
        const name = 'bob';

        this.theName = function () {
          return name;
        };
      }

      const obj = new Obj();
      //expect(obj.theName()).toBe();
    });

    it("may contain 'static' methods", () => {
      function Obj() {
        const name = 'bob';

        this.theName = function () {
          return name;
        };
      }

      Obj.someStaticMethod = function () {
        return 22;
      };

      //expect(Obj.someStaticMethod()).toBe();
    });

    it('can have have methods in the prototype', () => {
      function Obj() {
        const name = 'bob';
      }

      Obj.prototype.theName = function () {
        return this.name;
      };

      const obj = new Obj();
      //expect(obj.theName()).toEqual();
      //expect(obj.theName).toBe(new Obj().theName);
    });

    it('can define a factory', () => {
      function obj() {
        const self = {};
        const name = 'bob';

        self.theName = function () {
          return name;
        };

        return self;
      }

      const instance = obj();
      //expect(instance.theName()).toBe();
      //expect(instance.theName).not.toBe(obj().theName);
    });

    it('can create methods dynamically on an object instance', () => {
      const obj = {};
      const methodNames = ['meow', 'jump'];

      for (let i = 0; i < methodNames.length; i++) {
        obj[[methodNames[i]]] = function () {
          return 'it works';
        };
      }

      //expect(obj.meow()).toEqual();
    });

    describe('the polymorphism', () => {
      it('may use constructor plus prototype', () => {
        function Parent() {
          this.name = 'parent';
        }
        Parent.prototype.someMethod = function () {
          return 10;
        };

        function Child() {
          Parent.call(this); // constructor stealing
          this.name = 'child';
        }
        Child.prototype = Object.create(Parent.prototype); // prototype chaining

        const child = new Child();
        //expect(child.someMethod()).toEqual();
        //expect(child.name).toEqual();
      });

      it('may use the functional inheritance', () => {
        function parent() {
          const name = 'parent';
          const self = {};
          self.someMethod = function () {
            return 10;
          };
          return self;
        }

        function child() {
          const name = 'child';
          const self = parent();
          return self;
        }

        const instance = child();
        //expect(instance.someMethod()).toBe();
      });
    });
  });

  describe('commons patterns with functions and behaviors', () => {
    it('can invoke functions immediately to take advantage of scopes', () => {
      const myNamespace = {};

      (function (theNamespace) {
        let counter = 0;

        theNamespace.addOne = function () {
          counter++;
        };

        theNamespace.giveMeTheCount = function () {
          return counter;
        };
      })(myNamespace);

      myNamespace.addOne();
      myNamespace.addOne();

      //expect(myNamespace.giveMeTheCount()).toBe();
    });

    it("hoists variables the way you probably don't expect", () => {
      function generate() {
        const functions = [];
        for (let i = 0; i < 5; i++) {
          functions.push(function () {
            return i;
          });
        }
        return functions;
      }

      //expect(generate()[0]()).toEqual();
      //expect(generate()[1]()).toEqual();
    });
  });

  context('has ways to simulate classes', () => {
    // "Class"
    function Cat() {
      this.kilos = 1;
      this.feed = function () {
        this.kilos++;
      };
      this.isPurring = function () {
        return true;
      };
    }

    //////////////////////////////////////
    // "Class"
    //////////////////////////////////////
    function Lion(energy) {
      Cat.call(this);
      this.energy = energy || 100;
      const self = this;

      const run = function () {
        // private method
        self.energy -= 10;
      };
      const attack = function () {
        // private method
        self.energy -= 5;
      };
      this.playWithFriend = function (friend) {
        if (friend.isPurring()) self.energy += 10;
      };
      this.hunt = function () {
        // public method
        run();
        attack();
        this.onHunting(); // fire event
      };
      this.onHunting = function () {
        /* event */
      };
    }

    context('and the THIS keyword', () => {
      let cat;

      beforeEach(function () {
        cat = new Cat();
        window.kilos = 0;
      });

      it('sometimes works as expected in other languages', () => {
        cat.feed();
        cat.feed();

        //expect(cat.kilos).toEqual();
      });

      it('works different on detached functions', () => {
        window.kilos = 10;
        let feed = cat.feed;

        feed();

        //expect(window.kilos).toEqual();
        //expect(cat.kilos).toEqual();
      });

      it('can be bound explicitly with CALL and APPLY', () => {
        const feed = cat.feed;
        feed.apply(cat);

        //expect(cat.kilos).toEqual();
      });

      it('can be bound in modern browsers with BIND', () => {
        const feed = cat.feed;
        const bound = feed.bind(cat);

        bound();

        //expect(cat.kilos).toEqual();
      });

      it('works different when function is attached to other object', () => {
        const otherCat = new Cat();
        otherCat.kilos = 10;
        otherCat.feed = cat.feed;

        otherCat.feed();
        //expect(otherCat.kilos).toEqual();
        //expect(cat.kilos).toEqual();
      });

      it('can be handled using the SELF trick', () => {
        const energy = 200;
        const lion = new Lion(energy);

        lion.hunt();

        //expect(lion.energy).toEqual();
      });

      it('interprets the THIS when the function is executed', () => {
        const energy = 200;
        const lion = new Lion();

        lion.hunt = function () {
          this.energy = 4000;
        };
        lion.hunt();

        //expect(lion.energy).toEqual();
      });
    });
  });
});
