describe("TrivialConsoleReporter", function() {
  //keep these literal.  otherwise the test loses value as a test.
  function green(str) {
    return '\033[32m' + str + '\033[0m';
  }

  function red(str) {
    return '\033[31m' + str + '\033[0m';
  }

  function yellow(str) {
    return '\033[33m' + str + '\033[0m';
  }

  function prefixGreen(str) {
    return '\033[32m' + str;
  }

  function prefixRed(str) {
    return '\033[31m' + str;
  }

  var newline = "\n";

  var passingSpec = {
    results: function() {
      return {
        passed: function() {
          return true;
        }
      };
    }
  },
    failingSpec = {
      results: function() {
        return {
          passed: function() {
            return false;
          }
        };
      }
    },
    skippedSpec = {
      results: function() {
        return {skipped: true};
      }
    },
    passingRun = {
      specs: function() {
        return [null, null, null];
      },
      results: function() {
        return {failedCount: 0, items_: [null, null, null]};
      }
    },
    failingRun = {
      specs: function() {
        return [null, null, null];
      },
      results: function() {
        return {
          failedCount: 7, items_: [null, null, null]};
      }
    };

  function repeatedlyInvoke(f, times) {
    for (var i = 0; i < times; i++) f(times + 1);
  }

  function repeat(thing, times) {
    var arr = [];
    for (var i = 0; i < times; i++) arr.push(thing);
    return arr;
  }

  function simulateRun(reporter, specResults, suiteResults, finalRunner, startTime, endTime) {
    reporter.reportRunnerStarting();
    for (var i = 0; i < specResults.length; i++) {
      reporter.reportSpecResults(specResults[i]);
    }
    for (i = 0; i < suiteResults.length; i++) {
      reporter.reportSuiteResults(suiteResults[i]);
    }
    reporter.runnerStartTime = startTime;
    reporter.now = function() {
      return endTime;
    };
    reporter.reportRunnerResults(finalRunner);
  }

  var reporter, out, done;

  beforeEach(function() {
    out = (function() {
      var output = "";
      return {
        print:function(str) {
          output += str;
        },
        getOutput:function() {
          return output;
        },
        clear: function() {
          output = "";
        }
      };
    })();

    done = false;
    reporter = new jasmine.TrivialConsoleReporter(out.print, function(runner) {
      done = true
    });
  });


  describe('Integration', function() {
    it("prints the proper output under a pass scenario.  small numbers.", function() {
      simulateRun(reporter,
        repeat(passingSpec, 3),
        [],
      {
        specs: function() {
          return [null, null, null];
        },
        results:function() {
          return {
            items_: [null, null, null],
            totalCount: 7,
            failedCount: 0
          };
        }
      },
        1000,
        1777
        );

      expect(out.getOutput()).toEqual(
        [
          "Started",
          green(".") + green(".") + green("."),
          "",
          "Finished in 0.777 seconds",
          green("3 specs, 0 failures"),
          ""
        ].join("\n") + "\n"
        );
    });

    it("prints the proper output under a pass scenario.  large numbers.", function() {
      simulateRun(reporter,
        repeat(passingSpec, 57),
        [],
      {
        specs: function() {
          return [null, null, null];
        },
        results:function() {
          return {
            items_: [null, null, null],
            totalCount: 7,
            failedCount: 0
          };
        }
      },
        1000,
        1777);

      expect(out.getOutput()).toEqual(
        [
          "Started",

          green(".") + green(".") + green(".") + green(".") + green(".") + //50 green dots
            green(".") + green(".") + green(".") + green(".") + green(".") +
            green(".") + green(".") + green(".") + green(".") + green(".") +
            green(".") + green(".") + green(".") + green(".") + green(".") +
            green(".") + green(".") + green(".") + green(".") + green(".") +
            green(".") + green(".") + green(".") + green(".") + green(".") +
            green(".") + green(".") + green(".") + green(".") + green(".") +
            green(".") + green(".") + green(".") + green(".") + green(".") +
            green(".") + green(".") + green(".") + green(".") + green(".") +
            green(".") + green(".") + green(".") + green(".") + green(".") +

            green(".") + green(".") + green(".") + green(".") + green(".") + //7 green dots
            green(".") + green("."),

          "",
          "Finished in 0.777 seconds",
          green("3 specs, 0 failures"),
          ""
        ].join("\n") + "\n"
        );
    });


    it("prints the proper output under a failure scenario.", function() {
      simulateRun(reporter,
        [failingSpec, passingSpec, failingSpec],
        [
          {description:"The oven",
            results:function() {
              return {
                items_:[
                  {failedCount:2,
                    description:"heats up",
                    items_:[
                      {trace:{stack:"stack trace one\n  second line"}},
                      {trace:{stack:"stack trace two"}}
                    ]}
                ]
              };
            }},
          {description:"The washing machine",
            results:function() {
              return {
                items_:[
                  {failedCount:2,
                    description:"washes clothes",
                    items_:[
                      {trace:{stack:"stack trace one"}}
                    ]}
                ]
              };
            }}
        ],
      {
        specs: function() {
          return [null, null, null];
        },
        results:function() {
          return {
            items_: [null, null, null],
            totalCount: 7,
            failedCount: 2
          };
        }
      },
        1000,
        1777);

      expect(out.getOutput()).toEqual(
        [
          "Started",
          red("F") + green(".") + red("F"),
          "",
          "The oven heats up",
          "  stack trace one",
          "    second line",
          "  stack trace two",
          "",
          "The washing machine washes clothes",
          "  stack trace one",
          "",
          "Finished in 0.777 seconds",
          red("3 specs, 2 failures"),
          ""
        ].join("\n") + "\n"
        );
    });
  });

  describe('When a Jasmine environment executes', function() {
    beforeEach(function() {
      reporter.reportRunnerStarting();
    });

    it("should print 'Started' to the console", function() {
      expect(out.getOutput()).toEqual("Started" + newline);
    });

    describe('when a spec reports', function() {
      beforeEach(function() {
        out.clear();
      });

      it("prints a green dot if the spec passes", function() {
        reporter.reportSpecResults(passingSpec);

        expect(out.getOutput()).toEqual(green("."));
      });

      it("prints a red dot if the spec fails", function() {
        reporter.reportSpecResults(failingSpec);

        expect(out.getOutput()).toEqual(red("F"));
      });

      it("prints a yellow star if the spec was skipped", function() {
        reporter.reportSpecResults(skippedSpec);

        expect(out.getOutput()).toEqual(yellow("*"));
      });
    });

    describe('when a suite reports', function() {
      var emptyResults;
      beforeEach(function() {
        emptyResults = function() {
          return {
            items_:[]
          };
        };
      });

      it("remembers suite results", function() {
        reporter.reportSuiteResults({description: "Oven", results: emptyResults});
        reporter.reportSuiteResults({description: "Mixer", results: emptyResults});

        expect(reporter.suiteResults[0].description).toEqual('Oven');
        expect(reporter.suiteResults[1].description).toEqual('Mixer');
      });

      it("creates a description out of the current suite and any parent suites", function() {
        var grandparentSuite = {
          description: "My house",
          results: emptyResults
        };
        var parentSuite = {
          description: "kitchen",
          parentSuite: grandparentSuite,
          results: emptyResults
        };
        reporter.reportSuiteResults({ description: "oven", parentSuite: parentSuite, results: emptyResults });

        expect(reporter.suiteResults[0].description).toEqual("My house kitchen oven");
      });

      it("gathers failing spec results from the suite - the spec must have a description.", function() {
        reporter.reportSuiteResults({description:"Oven",
          results: function() {
            return {
              items_:[
                { failedCount: 0, description: "specOne" },
                { failedCount: 99, description: "specTwo" },
                { failedCount: 0, description: "specThree" },
                { failedCount: 88, description: "specFour" },
                { failedCount: 3 }
              ]
            };
          }});

        expect(reporter.suiteResults[0].failedSpecResults).
          toEqual([
          { failedCount: 99, description: "specTwo" },
          { failedCount: 88, description: "specFour" }
        ]);
      });

    });

    describe('and finishes', function() {

      describe('when reporting spec failure information', function() {

        it("prints suite and spec descriptions together as a sentence", function() {
          reporter.suiteResults = [
            {description:"The oven", failedSpecResults:[
              {description:"heats up", items_:[]},
              {description:"cleans itself", items_:[]}
            ]},
            {description:"The mixer", failedSpecResults:[
              {description:"blends things together", items_:[]}
            ]}
          ];

          reporter.reportRunnerResults(failingRun);

          expect(out.getOutput()).toContain("The oven heats up");
          expect(out.getOutput()).toContain("The oven cleans itself");
          expect(out.getOutput()).toContain("The mixer blends things together");
        });

        it("prints stack trace of spec failure", function() {
          reporter.suiteResults = [
            {description:"The oven", failedSpecResults:[
              {description:"heats up",
                items_:[
                  {trace:{stack:"stack trace one"}},
                  {trace:{stack:"stack trace two"}}
                ]}
            ]}
          ];

          reporter.reportRunnerResults(failingRun);

          expect(out.getOutput()).toContain("The oven heats up");
          expect(out.getOutput()).toContain("stack trace one");
          expect(out.getOutput()).toContain("stack trace two");
        });

      });

      describe('when reporting the execution time', function() {

        it("prints the full finished message", function() {
          reporter.now = function() {
            return 1000;
          };
          reporter.reportRunnerStarting();
          reporter.now = function() {
            return 1777;
          };
          reporter.reportRunnerResults(failingRun);
          expect(out.getOutput()).toContain("Finished in 0.777 seconds");
        });

        it("prints round time numbers correctly", function() {
          function run(startTime, endTime) {
            out.clear();
            reporter.runnerStartTime = startTime;
            reporter.now = function() {
              return endTime;
            };
            reporter.reportRunnerResults(passingRun);
          }

          run(1000, 11000);
          expect(out.getOutput()).toContain("10 seconds");

          run(1000, 2000);
          expect(out.getOutput()).toContain("1 seconds");

          run(1000, 1100);
          expect(out.getOutput()).toContain("0.1 seconds");

          run(1000, 1010);
          expect(out.getOutput()).toContain("0.01 seconds");

          run(1000, 1001);
          expect(out.getOutput()).toContain("0.001 seconds");
        });
      });

      describe("when reporting the results summary", function() {
        it("prints statistics in green if there were no failures", function() {
          reporter.reportRunnerResults({
            specs: function() {
              return [null, null, null];
            },
            results:function() {
              return {items_: [null, null, null], totalCount: 7, failedCount: 0};
            }
          });
          expect(out.getOutput()).
            toContain("3 specs, 0 failures");
        });

        it("prints statistics in red if there was a failure", function() {
          reporter.reportRunnerResults({
            specs: function() {
              return [null, null, null];
            },
            results:function() {
              return {items_: [null, null, null], totalCount: 7, failedCount: 3};
            }
          });
          expect(out.getOutput()).
            toContain("3 specs, 3 failures");
        });

        it("handles pluralization with 1's ones appropriately", function() {
          reporter.reportRunnerResults({
            specs: function() {
              return [null];
            },
            results:function() {
              return {items_: [null], totalCount: 1, failedCount: 1};
            }
          });
          expect(out.getOutput()).
            toContain("1 spec, 1 failure");
        });
      });

      describe("done callback", function() {
        it("calls back when done", function() {
          expect(done).toBeFalsy();
          reporter.reportRunnerResults({
            specs: function() {
              return [null, null, null];
            },
            results:function() {
              return {items_: [null, null, null], totalCount: 7, failedCount: 0};
            }
          });
          expect(done).toBeTruthy();
        });
      });
    });
  });
});