text:
/* eslint-disable max-len */

import _ from 'lodash';

import PrintView from '../../../src/modules/print/PrintView';

import * as patients from '../../../data/patient/profiles';

import {
  getTimezoneFromTimePrefs,
  formatBirthdate,
  formatCurrentDate,
} from '../../../src/utils/datetime';

import {
  DEFAULT_FONT_SIZE,
  FOOTER_FONT_SIZE,
  HEADER_FONT_SIZE,
  LARGE_FONT_SIZE,
  SMALL_FONT_SIZE,
  EXTRA_SMALL_FONT_SIZE,
} from '../../../src/modules/print/utils/constants';

import Doc from '../../helpers/pdfDoc';
import { getPatientFullName } from '../../../src/utils/misc';

describe('PrintView', () => {
  let Renderer;

  const data = {
    bgPrefs: {
      bgBounds: {
        veryHighThreshold: 300,
        targetUpperBound: 180,
        targetLowerBound: 70,
        veryLowThreshold: 54,
      },
      bgUnits: 'mg/dL',
    },
    timePrefs: {
      timezoneAware: true,
      timezoneName: 'US/Pacific',
    },
    metaData: {
      bgSources: { current: 'cbg' },
      latestPumpUpload: { manufacturer: 'PumpCo' },
    },
    data: {
      current: {
        endpoints: {
          range: [100, 200],
        },
        stats: {
          averageGlucose: {
            averageGlucose: 12.8,
            total: 70,
          },
        },
        aggregationsByDate: { myAggregation: 'foo' },
      },
    },
  };

  const DPI = 72;
  const MARGIN = DPI / 2;

  let doc;

  const opts = {

    debug: false,
    dpi: DPI,
    defaultFontSize: DEFAULT_FONT_SIZE,
    footerFontSize: FOOTER_FONT_SIZE,
    headerFontSize: HEADER_FONT_SIZE,
    largeFontSize: LARGE_FONT_SIZE,
    smallFontSize: SMALL_FONT_SIZE,
    extraSmallFontSize: EXTRA_SMALL_FONT_SIZE,
    height: 11 * DPI - (2 * MARGIN),
    margins: {
      left: MARGIN,
      top: MARGIN,
      right: MARGIN,
      bottom: MARGIN,
    },
    patient: patients.standard,

    width: 8.5 * DPI - (2 * MARGIN),
    title: 'Print View',
  };

  beforeEach(() => {
    doc = new Doc({ margin: MARGIN });
    Renderer = new PrintView(doc, data, opts);
  });

  describe('class constructor', () => {
    it('should instantiate without errors', () => {
      expect(Renderer).to.be.an('object');
    });

    it('should set default properties as provided by constructor args', () => {
      expect(Renderer.doc).to.eql(doc);
      expect(Renderer.data).to.eql(data);

      const overrideOpts = [
        'title',
        'debug',
        'dpi',
        'margins',
        'defaultFontSize',
        'footerFontSize',
        'headerFontSize',
        'largeFontSize',
        'smallFontSize',
        'extraSmallFontSize',
        'width',
        'height',
        'patient',
      ];

      _.each(overrideOpts, opt => {
        expect(Renderer[opt]).to.equal(opts[opt]);
      });

      expect(Renderer.bgUnits).to.equal(data.bgPrefs.bgUnits);
      expect(Renderer.bgBounds).to.eql(data.bgPrefs.bgBounds);
      expect(Renderer.timezone).to.equal(getTimezoneFromTimePrefs(data.timePrefs));
      expect(Renderer.endpoints).to.eql(data.data.current.endpoints);
      expect(Renderer.bgSource).to.equal(data.metaData.bgSources.current);
      expect(Renderer.latestPumpUpload).to.eql(data.metaData.latestPumpUpload);
      expect(Renderer.manufacturer).to.equal(data.metaData.latestPumpUpload.manufacturer);
      expect(Renderer.stats.averageGlucose.data.raw.averageGlucose).to.eql(data.data.current.stats.averageGlucose.averageGlucose);
      expect(Renderer.aggregationsByDate).to.eql(data.data.current.aggregationsByDate);
    });

    it('should set fallback default properties when not provided by constructor args', () => {
      const fallbackOpts = [
        'debug',
        'dpi',
        'margins',
        'defaultFontSize',
        'footerFontSize',
        'headerFontSize',
        'largeFontSize',
        'smallFontSize',
        'extraSmallFontSize',
        'width',
        'height',
      ];

      const strippedOpts = _.cloneDeep(opts);

      _.forEach(fallbackOpts, opt => {
        delete(strippedOpts[opt]);
      });

      const strippedRenderer = new PrintView(doc, data, opts);

      _.each(fallbackOpts, opt => {
        expect(strippedRenderer[opt]).to.not.be.undefined;
      });
    });

    it('should set data to an empty object when not provided to constructor', () => {
      const noDataRenderer = new PrintView(doc, undefined, opts);
      expect(noDataRenderer.data).to.eql({});
    });

    it('should set it\'s own required initial instance properties', () => {
      const requiredProps = [
        { prop: 'font', type: 'string' },
        { prop: 'boldFont', type: 'string' },
        { prop: 'colors', type: 'object' },
        { prop: 'tableSettings', type: 'object' },
        { prop: 'leftEdge', type: 'number', value: Renderer.margins.left },
        { prop: 'rightEdge', type: 'number', value: Renderer.margins.left + Renderer.width },
        { prop: 'bottomEdge', type: 'number', value: Renderer.margins.top + Renderer.height },
        { prop: 'patientInfoBox', type: 'object', value: {
          width: 0,
          height: 0,
        } },
        { prop: 'chartArea', type: 'object' },
        { prop: 'initialChartArea', type: 'object' },
        { prop: 'totalPages', type: 'number', value: 0 },
        { prop: 'initialTotalPages', type: 'number', value: 0 },
        { prop: 'currentPageIndex', type: 'number', value: -1 },
      ];

      _.each(requiredProps, item => {
        expect(Renderer[item.prop]).to.be.a(item.type);
        item.hasOwnProperty('value') && expect(Renderer[item.prop]).to.eql(item.value);
      });
    });

    it('should remove any existing listeners assigned to the doc\'s pageAdded event', () => {
      sinon.assert.calledWith(Renderer.doc.removeAllListeners, 'pageAdded');
    });

    it('should assign the newPage function as a callback to the doc\'s pageAdded event', () => {
      sinon.assert.calledWith(Renderer.doc.on, 'pageAdded', Renderer.newPage);
    });

    it('should subtract the header and footer size from the chart area', () => {
      expect(Renderer.chartArea.topEdge).to.be.above(Renderer.initialChartArea.topEdge);
      expect(Renderer.chartArea.bottomEdge).to.be.below(Renderer.initialChartArea.bottomEdge);
    });
  });

  describe('newPage', () => {
    it('should render a header and footer', () => {
      sinon.stub(Renderer, 'renderHeader').returns(Renderer);
      sinon.stub(Renderer, 'renderFooter');

      Renderer.newPage();
      sinon.assert.called(Renderer.renderHeader);
      sinon.assert.called(Renderer.renderFooter);
    });

    it('should increment `currentPageIndex` each time it\'s called', () => {
      expect(Renderer.currentPageIndex).to.equal(-1);
      Renderer.newPage();
      expect(Renderer.currentPageIndex).to.equal(0);
      Renderer.newPage();
      expect(Renderer.currentPageIndex).to.equal(1);
    });

    it('should increment `totalPages` each time it\'s called', () => {
      expect(Renderer.totalPages).to.equal(0);
      Renderer.newPage();
      expect(Renderer.totalPages).to.equal(1);
      Renderer.newPage();
      expect(Renderer.totalPages).to.equal(2);
    });

    it('should reset the font styles after rendering the footer', () => {
      sinon.stub(Renderer, 'renderHeader').returns(Renderer);
      sinon.stub(Renderer, 'renderFooter');
      Renderer.doc.font.resetHistory();
      Renderer.doc.fontSize.resetHistory();
      Renderer.newPage();

      sinon.assert.calledOnce(Renderer.doc.font);
      sinon.assert.calledOnce(Renderer.doc.fontSize);
      sinon.assert.callOrder(
        Renderer.renderHeader,
        Renderer.renderFooter,
        Renderer.doc.font,
        Renderer.doc.fontSize
      );
    });

    it('should maintain the previous page\'s column layout and position', () => {
      expect(Renderer.layoutColumns).to.be.undefined;

      Renderer.setLayoutColumns({ width: Renderer.width, count: 3 });
      Renderer.goToLayoutColumnPosition(1);

      expect(Renderer.layoutColumns.activeIndex).to.equal(1);
      expect(Renderer.layoutColumns.count).to.equal(3);

      Renderer.newPage();

      expect(Renderer.layoutColumns.activeIndex).to.equal(1);
      expect(Renderer.layoutColumns.count).to.equal(3);
    });

    it('should call `setNewPageTablePosition` when rendering a table', () => {
      const setNewPageTablePositionSpy = sinon.spy(Renderer, 'setNewPageTablePosition');

      Renderer.table = undefined;
      Renderer.newPage();

      sinon.assert.callCount(setNewPageTablePositionSpy, 0);

      Renderer.table = {
        pos: {
          x: 100,
          y: 100,
        },
        pdf: doc,
      };

      Renderer.newPage();
      sinon.assert.callCount(setNewPageTablePositionSpy, 1);
    });
  });

  describe('setNewPageTablePosition', () => {
    it('should maintain the previous page\'s table x position when in a layout column', () => {
      Renderer.setLayoutColumns({ width: 100, count: 3, gutter: 10 });
      Renderer.goToLayoutColumnPosition(1);

      const xPos = Renderer.layoutColumns.columns[1].x;
      const yPos = 300;

      Renderer.doc.x = xPos;
      Renderer.doc.y = yPos;

      Renderer.table = {
        pos: {
          x: xPos,
          y: yPos,
        },
        pdf: doc,
      };

      Renderer.setNewPageTablePosition();

      expect(Renderer.doc.y).to.equal(Renderer.chartArea.topEdge);
      expect(Renderer.table.pos.y).to.equal(Renderer.chartArea.topEdge);

      expect(Renderer.doc.x).to.equal(xPos);
      expect(Renderer.table.pos.x).to.equal(xPos);
    });

    it('should use the left edge as the x position when not in a layout column', () => {
      Renderer.layoutColumns = undefined;

      Renderer.doc.x = 200;
      Renderer.doc.y = 100;

      Renderer.table = {
        pos: {
          x: 200,
          y: 100,
        },
        pdf: doc,
      };

      Renderer.setNewPageTablePosition();

      expect(Renderer.doc.y).to.equal(Renderer.chartArea.topEdge);
      expect(Renderer.table.pos.y).to.equal(Renderer.chartArea.topEdge);

      expect(Renderer.doc.x).to.equal(Renderer.chartArea.leftEdge);
      expect(Renderer.table.pos.x).to.equal(Renderer.chartArea.leftEdge);
    });
  });

  describe('setLayoutColumns', () => {
    it('should define an equal spaced column layout when type is unspecified', () => {
      Renderer.setLayoutColumns({ count: 3 });
      expect(Renderer.layoutColumns.type).to.equal('equal');
    });

    it('should define an equal spaced column layout with correct count and gutters', () => {
      const colCount = 3;

      Renderer.setLayoutColumns({
        type: 'equal',
        count: colCount,
        gutter: 10,
        width: 120,
      });

      expect(Renderer.layoutColumns.type).to.equal('equal');
      expect(Renderer.layoutColumns.columns.length).to.equal(colCount);
      expect(Renderer.layoutColumns.columns[0].width).to.equal(100 / colCount);
      expect(Renderer.layoutColumns.columns[1].width).to.equal(100 / colCount);
      expect(Renderer.layoutColumns.columns[2].width).to.equal(100 / colCount);
    });

    it('should define a percentage-based column layout with correct count and gutters', () => {
      const colCount = 3;

      Renderer.setLayoutColumns({
        type: 'percentage',
        gutter: 10,
        width: 1020,
        widths: [30, 40, 30],
      });

      expect(Renderer.layoutColumns.type).to.equal('percentage');
      expect(Renderer.layoutColumns.columns.length).to.equal(colCount);
      expect(Renderer.layoutColumns.columns[0].width).to.equal(300);
      expect(Renderer.layoutColumns.columns[1].width).to.equal(400);
      expect(Renderer.layoutColumns.columns[2].width).to.equal(300);
    });
  });

  describe('updateLayoutColumnPosition', () => {
    it('should update the position of a layout column to the current doc cursor position', () => {
      Renderer.doc.y = Renderer.chartArea.topEdge;

      const activeColumn = 0;
      Renderer.setLayoutColumns({ count: 3 });
      Renderer.goToLayoutColumnPosition(activeColumn);

      expect(Renderer.layoutColumns.columns[activeColumn].x).to.equal(Renderer.chartArea.leftEdge);
      expect(Renderer.layoutColumns.columns[activeColumn].y).to.equal(Renderer.chartArea.topEdge);

      Renderer.doc.x = 400;
      Renderer.doc.y = 500;

      Renderer.updateLayoutColumnPosition(activeColumn);
      expect(Renderer.layoutColumns.columns[activeColumn].x).to.equal(400);
      expect(Renderer.layoutColumns.columns[activeColumn].y).to.equal(500);
    });
  });

  describe('goToLayoutColumnPosition', () => {
    it('should move the doc cursor to a layout column position', () => {
      Renderer.doc.x = Renderer.chartArea.leftEdge;
      Renderer.doc.y = Renderer.chartArea.topEdge;

      const activeColumn = 1;
      Renderer.setLayoutColumns({ count: 3, width: 900 });

      expect(Renderer.doc.x).to.equal(Renderer.chartArea.leftEdge);
      expect(Renderer.doc.y).to.equal(Renderer.chartArea.topEdge);

      Renderer.goToLayoutColumnPosition(activeColumn);

      expect(Renderer.doc.x).to.equal(Renderer.chartArea.leftEdge + 300);
      expect(Renderer.doc.y).to.equal(Renderer.chartArea.topEdge);
    });
  });

  describe('goToPage', () => {
    it('should set `currentPageIndex` to the provided index', () => {
      Renderer.initialTotalPages = 0;
      Renderer.currentPageIndex = 2;
      Renderer.goToPage(1);
      expect(Renderer.currentPageIndex).to.equal(1);
    });

    it('should call `doc.switchToPage` with the provided index + initialTotalPages ', () => {
      Renderer.initialTotalPages = 2;
      Renderer.goToPage(1);
      sinon.assert.calledWith(Renderer.doc.switchToPage, 3);
    });
  });

  describe('getShortestLayoutColumn', () => {
    it('should return the index of the column with the top-most y position', () => {
      Renderer.doc.y = 100;
      Renderer.setLayoutColumns({ count: 3, width: 900 });

      expect(Renderer.getShortestLayoutColumn()).to.equal(0);

      Renderer.doc.y = 200;
      Renderer.updateLayoutColumnPosition(0);
      expect(Renderer.getShortestLayoutColumn()).to.equal(1);

      Renderer.doc.y = 300;
      Renderer.updateLayoutColumnPosition(0);
      Renderer.updateLayoutColumnPosition(1);

      expect(Renderer.getShortestLayoutColumn()).to.equal(2);
    });
  });

  describe('getLongestLayoutColumn', () => {
    it('should return the index of the column with the bottom-most y position', () => {
      Renderer.doc.y = 100;
      Renderer.setLayoutColumns({ count: 3, width: 900 });

      expect(Renderer.getLongestLayoutColumn()).to.equal(0);

      Renderer.doc.y = 200;
      Renderer.updateLayoutColumnPosition(0);
      expect(Renderer.getLongestLayoutColumn()).to.equal(0);

      Renderer.doc.y = 300;
      Renderer.updateLayoutColumnPosition(2);

      expect(Renderer.getLongestLayoutColumn()).to.equal(2);

      Renderer.doc.y = 400;
      Renderer.updateLayoutColumnPosition(1);

      expect(Renderer.getLongestLayoutColumn()).to.equal(1);
    });
  });

  describe('getActiveColumnWidth', () => {
    it('should return the width of the active column', () => {
      Renderer.setLayoutColumns({
        type: 'percentage',
        gutter: 10,
        width: 1020,
        widths: [30, 40, 30],
      });

      Renderer.goToLayoutColumnPosition(1);
      expect(Renderer.getActiveColumnWidth()).to.equal(400);
    });
  });

  describe('getDateRange', () => {
    it('should return the formatted date range when provided date strings and format', () => {
      const result = Renderer.getDateRange('2017-12-01', '2017-12-10', 'YYYY-MM-DD');
      expect(result).to.equal('Date range: Dec 1 - Dec 10, 2017');
    });

    it('should return the formatted date range when provided date timestamps', () => {
      Renderer.timezone = 'UTC';
      const result = Renderer.getDateRange(Date.parse('2017-12-01T00:00:00.000Z'), Date.parse('2017-12-10T00:00:00.000Z'));
      expect(result).to.equal('Date range: Dec 1 - Dec 10, 2017');
    });
  });

  describe('setFill', () => {
    it('should call doc fill methods with default args', () => {
      Renderer.setFill();
      sinon.assert.calledWith(Renderer.doc.fillColor, 'black');
      sinon.assert.calledWith(Renderer.doc.fillOpacity, 1);
    });

    it('should call doc fill methods with provided args', () => {
      Renderer.setFill('blue', 0.5);
      sinon.assert.calledWith(Renderer.doc.fillColor, 'blue');
      sinon.assert.calledWith(Renderer.doc.fillOpacity, 0.5);
    });
  });

  describe('setStroke', () => {
    it('should call doc stroke methods with default args', () => {
      Renderer.setStroke();
      sinon.assert.calledWith(Renderer.doc.strokeColor, 'black');
      sinon.assert.calledWith(Renderer.doc.strokeOpacity, 1);
    });

    it('should call doc stroke methods with provided args', () => {
      Renderer.setStroke('blue', 0.5);
      sinon.assert.calledWith(Renderer.doc.strokeColor, 'blue');
      sinon.assert.calledWith(Renderer.doc.strokeOpacity, 0.5);
    });
  });

  describe('resetText', () => {
    it('should reset the doc text style', () => {
      sinon.spy(Renderer, 'setFill');

      Renderer.resetText();
      sinon.assert.calledOnce(Renderer.setFill);
      sinon.assert.calledWith(Renderer.doc.lineGap, 0);
      sinon.assert.calledWith(Renderer.doc.fontSize, opts.defaultFontSize);
      sinon.assert.calledWith(Renderer.doc.font, Renderer.font);
    });
  });

  describe('renderSectionHeading', () => {
    it('should render a section heading provided as a string', () => {
      Renderer.renderSectionHeading('hello');
      sinon.assert.calledWith(Renderer.doc.text, 'hello');
    });

    it('should render a section heading provided as an object with text and subText keys', () => {
      Renderer.renderSectionHeading({
        text: 'hello',
        subText: 'there',
      });

      sinon.assert.calledWith(Renderer.doc.text, 'hello');
      sinon.assert.calledWith(Renderer.doc.text, ' there');
    });

    it('should set default font and font sizes for the text and subText', () => {
      Renderer.renderSectionHeading({
        text: 'hello',
        subText: 'there',
      });

      sinon.assert.calledWith(Renderer.doc.fontSize, Renderer.headerFontSize);
      sinon.assert.calledWith(Renderer.doc.font, Renderer.font);

      sinon.assert.calledWith(Renderer.doc.fontSize, Renderer.defaultFontSize);
    });

    it('should override default font sizes for the text and subText', () => {
      Renderer.renderSectionHeading({
        text: 'hello',
        subText: 'there',
      }, {
        font: 'mainFont',
        fontSize: 16,
        subTextFont: 'otherFont',
        subTextFontSize: 18,
      });

      sinon.assert.calledWith(Renderer.doc.fontSize, 16);
      sinon.assert.calledWith(Renderer.doc.font, 'mainFont');

      sinon.assert.calledWith(Renderer.doc.fontSize, 18);
      sinon.assert.calledWith(Renderer.doc.font, 'otherFont');
    });

    it('should default to the current doc position', () => {
      Renderer.doc.x = Renderer.chartArea.leftEdge;
      Renderer.doc.y = Renderer.chartArea.topEdge;

      const xPos = Renderer.doc.x;
      const yPos = Renderer.doc.y;
      Renderer.renderSectionHeading('hello');

      sinon.assert.calledWithMatch(Renderer.doc.text, 'hello', xPos, yPos);
    });

    it('should override the current doc position with provided args', () => {
      Renderer.doc.x = Renderer.chartArea.leftEdge;
      Renderer.doc.y = Renderer.chartArea.topEdge;

      Renderer.renderSectionHeading('hello', {
        xPos: 100,
        yPos: 300,
      });

      sinon.assert.calledWithMatch(Renderer.doc.text, 'hello', 100, 300);
    });

    it('should reset the text style when done', () => {
      sinon.spy(Renderer, 'resetText');
      Renderer.renderSectionHeading('hello');

      sinon.assert.calledOnce(Renderer.resetText);
    });

    it('should move the cursor down a default or specified amount when done', () => {
      Renderer.renderSectionHeading('hello');
      sinon.assert.calledOnce(Renderer.doc.moveDown);
      sinon.assert.calledWith(Renderer.doc.moveDown, 1);

      Renderer.renderSectionHeading('hello', { moveDown: 3.5 });
      sinon.assert.calledWith(Renderer.doc.moveDown, 3.5);
    });
  });

  describe('renderCellStripe', () => {
    const pos = {
      x: 100,
      y: 200,
    };

    const column = {
      fillStripe: {
        color: 'blue',
        opacity: 0.6,
        width: 8,
      },
      headerFillStripe: {
        color: 'green',
        opacity: 0.7,
        width: 10,
      },
      height: 30,
      headerHeight: 40,
    };

    beforeEach(() => {
      sinon.spy(Renderer, 'setFill');
    });

    afterEach(() => {
      Renderer.setFill.resetHistory();
    });

    it('should not render a fill stripe when missing column fill stripe definition', () => {
      const stripe = Renderer.renderCellStripe({}, {}, pos);

      expect(stripe).to.eql({
        width: 0,
        height: 0,
        color: Renderer.colors.grey,
        opacity: 1,
        background: false,
        padding: 0,
      });

      sinon.assert.notCalled(Renderer.setFill);
      sinon.assert.notCalled(Renderer.doc.rect);
      sinon.assert.notCalled(Renderer.doc.fill);
    });

    it('should render a fill stripe with default styles in a table cell', () => {
      const stripe = Renderer.renderCellStripe({}, { fillStripe: true, height: 20 }, pos);

      sinon.assert.calledWith(Renderer.setFill, Renderer.colors.grey, 1);
      sinon.assert.calledWith(Renderer.doc.rect, pos.x + 0.25, pos.y + 0.25, 6);
      sinon.assert.calledOnce(Renderer.doc.fill);

      expect(stripe).to.eql({
        width: 6,
        height: 20,
        color: Renderer.colors.grey,
        opacity: 1,
        background: false,
        padding: 0,
      });
    });

    it('should render a fill stripe with custom styles from column def in a standard cell', () => {
      const paddedColumn = _.assign({}, column, {
        fillStripe: _.assign({}, column.fillStripe, {
          padding: 5,
        }),
      });

      const stripe = Renderer.renderCellStripe({}, paddedColumn, pos);
      const height = stripe.height - 0.5 - (2 * paddedColumn.fillStripe.padding);
      const xPos = pos.x + 0.25 + paddedColumn.fillStripe.padding;
      const yPos = pos.y + 0.25 + paddedColumn.fillStripe.padding;

      sinon.assert.calledWith(Renderer.setFill, 'blue', 0.6);
      sinon.assert.calledWith(Renderer.doc.rect, xPos, yPos, 8, height);
      sinon.assert.calledOnce(Renderer.doc.fill);

      expect(stripe).to.eql({
        width: 8,
        height: 30,
        color: 'blue',
        opacity: 0.6,
        background: false,
        padding: 5,
      });
    });

    it('should render a fill stripe with custom styles from data def in a standard cell', () => {
      const colData = {
        _fillStripe: {
          color: 'orange',
          opacity: 0.9,
          width: 100,
        },
        _renderedContent: {
          height: 80,
        },
      };

      const stripe = Renderer.renderCellStripe(colData, {}, pos);
      const height = stripe.height - 0.5;
      const xPos = pos.x + 0.25;
      const yPos = pos.y + 0.25;

      sinon.assert.calledWith(Renderer.setFill, 'orange', 0.9);
      sinon.assert.calledWith(Renderer.doc.rect, xPos, yPos, 100, height);
      sinon.assert.calledOnce(Renderer.doc.fill);

      expect(stripe).to.eql({
        width: 100,
        height: 80,
        color: 'orange',
        opacity: 0.9,
        background: false,
        padding: 0,
      });
    });

    it('should render a fill stripe with custom styles from column def in a header cell', () => {
      const isHeader = true;
      const stripe = Renderer.renderCellStripe({}, column, pos, isHeader);
      const height = stripe.height - 0.5;

      sinon.assert.calledWith(Renderer.setFill, 'green', 0.7);
      sinon.assert.calledWith(Renderer.doc.rect, pos.x + 0.25, pos.y + 0.25, 10, height);
      sinon.assert.calledOnce(Renderer.doc.fill);

      expect(stripe).to.eql({
        width: 10,
        height: 40,
        color: 'green',
        opacity: 0.7,
        background: false,
        padding: 0,
      });
    });
  });

  describe('renderCustomTextCell', () => {
    const table = {};

    const cellData = {
      cell: {
        text: 'foo',
        subText: 'bar',
        note: 'baz',
      },
      header: {
        text: 'dog',
        subText: 'cat',
        note: 'mouse',
      },
      stringCell: 'pow',
    };

    const draw = true;

    const column = {
      id: 'cell',
      width: 100,
    };

    const headerColumn = {
      id: 'header',
      width: 200,
    };

    const pos = {
      x: 100,
      y: 200,
    };

    const padding = {
      left: 0,
      right: 0,
      top: 0,
      bottom: 0,
    };

    beforeEach(() => {
      sinon.spy(Renderer, 'renderCellStripe');
      Renderer.doc.font.resetHistory();
      Renderer.doc.fontSize.resetHistory();
    });

    afterEach(() => {
      Renderer.renderCellStripe.resetHistory();
    });

    it('should make the call to render a cell stripe', () => {
      Renderer.renderCustomTextCell(table, cellData, draw, column, pos, padding);

      sinon.assert.calledOnce(Renderer.renderCellStripe);
    });

    it('should render a standard cell with a text string provided', () => {
      const stringColumn = _.assign({}, column, {
        id: 'stringCell',
      });

      Renderer.renderCustomTextCell(table, cellData, draw, stringColumn, pos, padding);

      sinon.assert.calledWith(Renderer.doc.text, 'pow');
    });

    it('should render a header cell with a text string provided', () => {
      const stringColumn = _.assign({}, headerColumn, {
        header: 'chicken',
      });

      Renderer.renderCustomTextCell(table, cellData, draw, stringColumn, pos, padding, true);

      sinon.assert.calledWith(Renderer.doc.text, 'chicken');
    });

    it('should render a standard cell with a text, subtext, and note object provided', () => {
      Renderer.renderCustomTextCell(table, cellData, draw, column, pos, padding);

      sinon.assert.calledWith(Renderer.doc.text, 'foo');
      sinon.assert.calledWith(Renderer.doc.text, 'bar');
      sinon.assert.calledWith(Renderer.doc.text, 'baz');
    });

    it('should render a header cell with a text, subtext, and note object provided', () => {
      Renderer.renderCustomTextCell(table, cellData, draw, headerColumn, pos, padding, true);

      sinon.assert.calledWith(Renderer.doc.text, 'dog');
      sinon.assert.calledWith(Renderer.doc.text, 'cat');
      sinon.assert.calledWith(Renderer.doc.text, 'mouse');
    });

    it('should use proper default font styles in a standard cell', () => {
      Renderer.renderCustomTextCell(table, cellData, draw, column, pos, padding);

      sinon.assert.alwaysCalledWith(Renderer.doc.font, Renderer.font);
      sinon.assert.neverCalledWith(Renderer.doc.font, Renderer.boldFont);
      sinon.assert.alwaysCalledWith(Renderer.doc.fontSize, Renderer.defaultFontSize);
    });

    it('should use proper default font styles in a header cell', () => {
      Renderer.renderCustomTextCell(table, cellData, draw, headerColumn, pos, padding, true);

      sinon.assert.calledWith(Renderer.doc.font, Renderer.boldFont);
      sinon.assert.calledWith(Renderer.doc.font, Renderer.font);
      sinon.assert.alwaysCalledWith(Renderer.doc.fontSize, Renderer.defaultFontSize);
    });

    it('should use left alignment by default in a standard cell', () => {
      Renderer.renderCustomTextCell(table, cellData, draw, column, pos, padding);

      sinon.assert.calledWith(
        Renderer.doc.text,
        'foo',
        pos.x,
        pos.y,
        {
          continued: true,
          align: 'left',
          width: 100,
        }
      );
    });

    it('should use left alignment by default in a header cell', () => {
      Renderer.renderCustomTextCell(table, cellData, draw, headerColumn, pos, padding, true);

      sinon.assert.calledWith(
        Renderer.doc.text,
        'dog',
        pos.x,
        pos.y,
        {
          continued: true,
          align: 'left',
          width: 200,
        }
      );
    });

    it('should allow custom font styles and alignment in a standard cell', () => {
      const customColumn = _.assign({}, column, {
        align: 'left',
        font: 'comic sans',
        fontSize: 13,
        noteFontSize: 9,
      });

      Renderer.renderCustomTextCell(table, cellData, draw, customColumn, pos, padding);

      sinon.assert.calledWith(Renderer.doc.font, 'comic sans');
      sinon.assert.calledWith(Renderer.doc.fontSize, 13);
      sinon.assert.calledWith(Renderer.doc.fontSize, 9);

      sinon.assert.calledWith(
        Renderer.doc.text,
        'foo',
        pos.x,
        pos.y,
        {
          continued: true,
          align: 'left',
          width: 100,
        }
      );

      sinon.assert.calledWith(
        Renderer.doc.text,
        'bar',
        pos.x,
        pos.y,
        {
          align: 'left',
          width: 100,
        }
      );

      sinon.assert.calledWith(
        Renderer.doc.text,
        'baz',
        {
          align: 'left',
          width: 100,
        }
      );
    });

    it('should offset the text position when right aligned', () => {
      const customColumn = _.assign({}, column, {
        align: 'right',
        font: 'comic sans',
        fontSize: 13,
        noteFontSize: 9,
      });

      Renderer.doc.widthOfString = sinon.stub().returns(40);

      Renderer.renderCustomTextCell(table, cellData, draw, customColumn, pos, padding);

      sinon.assert.calledWith(Renderer.doc.font, 'comic sans');
      sinon.assert.calledWith(Renderer.doc.fontSize, 13);
      sinon.assert.calledWith(Renderer.doc.fontSize, 9);

      sinon.assert.calledWith(
        Renderer.doc.text,
        'foo',
        pos.x - 40, // should subtract the width of subText string from the x position
        pos.y,
        {
          continued: true,
          align: 'right',
          width: 100,
        }
      );
    });

    it('should allow custom font styles and alignment in a header cell', () => {
      const customColumn = _.assign({}, headerColumn, {
        headerAlign: 'center',
        headerFont: 'courrier new',
        fontSize: 15,
        noteFontSize: 11,
      });

      Renderer.renderCustomTextCell(table, cellData, draw, customColumn, pos, padding, true);

      sinon.assert.calledWith(Renderer.doc.font, 'courrier new');
      sinon.assert.calledWith(Renderer.doc.fontSize, 15);
      sinon.assert.calledWith(Renderer.doc.fontSize, 11);

      sinon.assert.calledWith(
        Renderer.doc.text,
        'dog',
        pos.x,
        pos.y,
        {
          continued: true,
          align: 'center',
          width: 200,
        }
      );

      sinon.assert.calledWith(
        Renderer.doc.text,
        'cat',
        pos.x,
        pos.y,
        {
          align: 'center',
          width: 200,
        }
      );

      sinon.assert.calledWith(
        Renderer.doc.text,
        'mouse',
        {
          align: 'center',
          width: 200,
        }
      );
    });

    it('should allow rendering a specified row in bold', () => {
      const customData = _.assign({}, cellData, {
        _bold: true,
      });

      Renderer.renderCustomTextCell(table, customData, draw, column, pos, padding);
      sinon.assert.calledWith(Renderer.doc.font, Renderer.boldFont);
    });
  });

  describe('renderTableHeading', () => {
    let renderTable;
    let resetText;

    const defaultColumns = [
      {
        id: 'heading',
        align: 'left',
        height: 24,
        cache: false,
      },
    ];

    const defaultRows = [
      {
        heading: {},
        note: undefined,
      },
    ];

    const defaultOpts = {
      columnDefaults: {
        headerBorder: '',
      },
      bottomMargin: 0,
      showHeaders: false,
    };

    beforeEach(() => {
      renderTable = sinon.stub(Renderer, 'renderTable');
      resetText = sinon.stub(Renderer, 'resetText');
      defaultColumns[0].renderer = Renderer.renderCustomTextCell;
      defaultColumns[0].font = Renderer.boldFont;
      defaultColumns[0].fontSize = Renderer.largeFontSize;
    });

    afterEach(() => {
      renderTable.restore();
      resetText.restore();
    });

    it('should render a table heading with default settings when no args provided', () => {
      Renderer.renderTableHeading();

      sinon.assert.calledWith(
        Renderer.renderTable,
        defaultColumns,
        defaultRows,
        defaultOpts,
      );
    });

    it('should render a table heading with custom settings when args provided', () => {
      const heading = {
        text: 'foo',
        note: 'bar',
      };

      const customOpts = {
        height: 60,
        align: 'right',
        font: 'comic sans',
        fontSize: 22,
      };

      Renderer.renderTableHeading(heading, customOpts);

      sinon.assert.calledWith(
        Renderer.renderTable,
        [_.assign({}, defaultColumns[0], {
          height: customOpts.height,
          align: customOpts.align,
          font: customOpts.font,
          fontSize: customOpts.fontSize,
        })],
        [{ heading, note: heading.note }],
        _.assign({}, defaultOpts, customOpts),
      );
    });
  });

  describe('renderTable', () => {
    const columns = [
      {
        id: 'name',
        fill: 'blue',
      },
      {
        id: 'value',
      },
    ];

    const rows = [
      { name: 'one', value: 1 },
      { name: 'two', value: 2 },
      { name: 'three', value: 3 },
    ];

    const defaultOpts = {
      columnDefaults: {
        headerBorder: 'TBLR',
        border: 'TBLR',
        align: 'left',
        padding: [7, 5, 3, 5],
        headerPadding: [7, 5, 3, 5],
        fill: false,
      },
      bottomMargin: 20,
    };

    class Table {
      constructor() {
        this.addHeader = sinon.stub().returns(this);
        this.addPlugin = sinon.stub().returns(this);
        this.onCellBackgroundAdd = sinon.stub().resolves(true);
        this.onCellBackgroundAdded = sinon.stub().resolves(true);
        this.onCellBorderAdd = sinon.stub().resolves(true);
        this.onCellBorderAdded = sinon.stub().resolves(true);
        this.onRowAdd = sinon.stub().resolves(true);
        this.onRowAdded = sinon.stub().resolves(true);
        this.onPageAdd = sinon.stub().resolves(true);
        this.onPageAdded = sinon.stub().resolves(true);
        this.onBodyAdded = sinon.stub().resolves(true);
        this.setColumnsDefaults = sinon.stub().returns(this);
        this.addColumns = sinon.stub().returns(this);
        this.addBody = sinon.stub().returns(this);
        this.bottomMargin = 20;
        this.pdf = {
          addPage: sinon.stub(),
          switchToPage: sinon.stub(),
        };
      }
    }

    let TableStub;
    let FitColumnStub;
    let setFill;
    let setStroke;
    let resetText;

    beforeEach(() => {
      /* eslint-disable new-cap */
      TableStub = new sinon.stub().returns(new Table());
      FitColumnStub = new sinon.stub().resolves(true);
      /* eslint-enable new-cap */

      defaultOpts.columnDefaults.borderColor = Renderer.tableSettings.colors.border;
      defaultOpts.pos = {
        maxY: Renderer.chartArea.bottomEdge,
      };

      TableStub.resetHistory();
      FitColumnStub.resetHistory();

      setFill = sinon.spy(Renderer, 'setFill');
      setStroke = sinon.spy(Renderer, 'setStroke');
      resetText = sinon.spy(Renderer, 'resetText');
    });

    afterEach(() => {
      setFill.resetHistory();
      setStroke.resetHistory();
      resetText.resetHistory();
    });

    it('should render a table with default settings when no args provided', () => {
      Renderer.renderTable([], [], {}, TableStub, FitColumnStub);

      sinon.assert.calledWithNew(TableStub);
      sinon.assert.calledWith(TableStub, Renderer.doc, defaultOpts);
      sinon.assert.calledWith(Renderer.table.setColumnsDefaults, defaultOpts.columnDefaults);
      sinon.assert.calledWith(Renderer.table.addColumns, []);
      sinon.assert.calledWith(Renderer.table.addBody, []);
    });

    it('should render a table with custom rows, columns, and opts extending defaults', () => {
      const customOpts = {
        bottomMargin: 80,
      };

      Renderer.renderTable(columns, rows, customOpts, TableStub, FitColumnStub);

      sinon.assert.calledWith(TableStub, Renderer.doc, _.defaultsDeep({}, customOpts, defaultOpts));
      sinon.assert.calledWith(Renderer.table.setColumnsDefaults, defaultOpts.columnDefaults);
      sinon.assert.calledWith(Renderer.table.addColumns, columns);
      sinon.assert.calledWith(Renderer.table.addBody, rows);
    });

    it('should initialize the FitColumn table plugin when required', () => {
      Renderer.renderTable([], [], { flexColumn: 'test' }, TableStub, FitColumnStub);
      sinon.assert.calledOnce(Renderer.table.addPlugin);
      sinon.assert.calledWith(Renderer.table.addPlugin, new FitColumnStub());
      sinon.assert.calledWith(FitColumnStub, { column: 'test' });
    });

    it('should add a listener for the `onPageAdd` table event', () => {
      Renderer.renderTable([], [], {}, TableStub, FitColumnStub);
      sinon.assert.calledOnce(Renderer.table.onPageAdd);
    });

    it('should add a listener for the `onPageAdded` table event', () => {
      Renderer.renderTable([], [], {}, TableStub, FitColumnStub);
      sinon.assert.calledOnce(Renderer.table.onPageAdded);
    });

    it('should add a listener for the `onCellBackgroundAdd` table event', () => {
      Renderer.renderTable([], [], {}, TableStub, FitColumnStub);
      sinon.assert.calledOnce(Renderer.table.onCellBackgroundAdd);
    });

    it('should add a listener for the `onCellBackgroundAdded` table event', () => {
      Renderer.renderTable([], [], {}, TableStub, FitColumnStub);
      sinon.assert.calledOnce(Renderer.table.onCellBackgroundAdded);
    });

    it('should add a listener for the `onCellBorderAdd` table event', () => {
      Renderer.renderTable([], [], {}, TableStub, FitColumnStub);
      sinon.assert.calledOnce(Renderer.table.onCellBorderAdd);
    });

    it('should add a listener for the `onCellBorderAdded` table event', () => {
      Renderer.renderTable([], [], {}, TableStub, FitColumnStub);
      sinon.assert.calledOnce(Renderer.table.onCellBorderAdded);
    });

    it('should add a listener for the `onRowAdd` table event', () => {
      Renderer.renderTable([], [], {}, TableStub, FitColumnStub);
      sinon.assert.calledOnce(Renderer.table.onRowAdd);
    });

    it('should add a listener for the `onRowAdded` table event', () => {
      Renderer.renderTable([], [], {}, TableStub, FitColumnStub);
      sinon.assert.calledOnce(Renderer.table.onRowAdded);
    });

    it('should add a listener for the `onBodyAdded` table event', () => {
      Renderer.renderTable([], [], {}, TableStub, FitColumnStub);
      sinon.assert.calledOnce(Renderer.table.onBodyAdded);
    });

    describe('onPageAdd', () => {
      it('should call `addPage` and not `switchToPage` or `setNewPageTablePosition` when we are on the last page in the document', () => {
        Renderer.renderTable([], [], {}, TableStub, FitColumnStub);
        sinon.spy(Renderer, 'setNewPageTablePosition');

        Renderer.initialTotalPages = 2;
        Renderer.totalPages = 4; // 2 pages have been added to the initial count
        Renderer.currentPageIndex = 1; // zero-based index, so this is the last page

        sinon.assert.notCalled(Renderer.table.pdf.addPage);

        Renderer.onPageAdd(Renderer.table, {}, { cancel: false });
        sinon.assert.calledOnce(Renderer.table.pdf.addPage);
        sinon.assert.notCalled(Renderer.table.pdf.switchToPage);
        sinon.assert.notCalled(Renderer.setNewPageTablePosition);
      });

      it('should call `switchToPage` and `setNewPageTablePosition` and not `addPage` when we are not on the last page in the document', () => {
        Renderer.renderTable([], [], {}, TableStub, FitColumnStub);
        sinon.spy(Renderer, 'setNewPageTablePosition');

        Renderer.table.pos = {
          x: 100,
          y: 100,
        };

        Renderer.table.pdf = Renderer.doc;

        Renderer.initialTotalPages = 0;
        Renderer.totalPages = 2; // 2 pages have been added to the initial count
        Renderer.currentPageIndex = 0; // zero-based index, so this is the first of 2 pages

        sinon.assert.notCalled(Renderer.table.pdf.switchToPage);
        sinon.assert.notCalled(Renderer.setNewPageTablePosition);

        Renderer.onPageAdd(Renderer.table, {}, { cancel: false });
        sinon.assert.calledOnce(Renderer.table.pdf.switchToPage);
        sinon.assert.calledOnce(Renderer.setNewPageTablePosition);
        sinon.assert.notCalled(Renderer.table.pdf.addPage);
      });
    });

    describe('onPageAdded', () => {
      it('should add a table header', () => {
        Renderer.renderTable([], [], {}, TableStub, FitColumnStub);
        sinon.assert.notCalled(Renderer.table.addHeader);

        Renderer.onPageAdded(Renderer.table);
        sinon.assert.calledOnce(Renderer.table.addHeader);
      });
    });

    describe('onCellBackgroundAdd', () => {
      it('should set the standard cell fill color when defined as a string', () => {
        Renderer.onCellBackgroundAdd(
          Renderer.table,
          columns[0],
          rows[0],
          0,
          false,
        );

        sinon.assert.calledOnce(Renderer.setFill);
        sinon.assert.calledWith(Renderer.setFill, 'blue', 1);
      });

      it('should set the standard cell fill color and opacity when defined as an object', () => {
        Renderer.onCellBackgroundAdd(
          Renderer.table,
          {
            fill: {
              color: 'yellow',
              opacity: 0.4,
            },
          },
          rows[0],
          0,
          false,
        );

        sinon.assert.calledOnce(Renderer.setFill);
        sinon.assert.calledWith(Renderer.setFill, 'yellow', 0.4);
      });

      it('should set the header cell fill color when defined as a string', () => {
        Renderer.onCellBackgroundAdd(
          Renderer.table,
          {
            headerFill: 'magenta',
          },
          rows[0],
          0,
          true,
        );

        sinon.assert.calledOnce(Renderer.setFill);
        sinon.assert.calledWith(Renderer.setFill, 'magenta', 1);
      });

      it('should set the header cell fill color and opacity when defined as an object', () => {
        Renderer.onCellBackgroundAdd(
          Renderer.table,
          {
            headerFill: {
              color: 'red',
              opacity: 0.5,
            },
          },
          rows[0],
          0,
          true,
        );

        sinon.assert.calledOnce(Renderer.setFill);
        sinon.assert.calledWith(Renderer.setFill, 'red', 0.5);
      });

      it('should not set the fill styles when not defined', () => {
        Renderer.onCellBackgroundAdd(
          Renderer.table,
          columns[1],
          rows[0],
          0,
          false,
        );

        sinon.assert.notCalled(Renderer.setFill);
      });

      it('should set even zebra fill style when defined but fill color not set', () => {
        Renderer.onCellBackgroundAdd(
          Renderer.table,
          { zebra: true, fill: true },
          rows[0],
          0,
          false,
        );

        sinon.assert.calledOnce(Renderer.setFill);
        sinon.assert.calledWithExactly(
          Renderer.setFill,
          Renderer.tableSettings.colors.zebraEven,
          1
        );
      });

      it('should set odd zebra fill style when defined but fill color not set', () => {
        Renderer.onCellBackgroundAdd(
          Renderer.table,
          { zebra: true, fill: true },
          rows[1],
          1,
          false,
        );

        sinon.assert.calledOnce(Renderer.setFill);
        sinon.assert.calledWithExactly(
          Renderer.setFill,
          Renderer.tableSettings.colors.zebraOdd,
          1
        );
      });

      it('should set even zebra fill style at full opacity when fill color set as object', () => {
        Renderer.onCellBackgroundAdd(
          Renderer.table,
          {
            zebra: true,
            fill: {
              color: 'yellow',
              opacity: 0.4,
            },
          },
          rows[0],
          0,
          false,
        );

        sinon.assert.calledOnce(Renderer.setFill);
        sinon.assert.calledWithExactly(
          Renderer.setFill,
          'yellow',
          0.4
        );
      });

      it('should set odd zebra fill style at half opacity when fill color set as object', () => {
        Renderer.onCellBackgroundAdd(
          Renderer.table,
          {
            zebra: true,
            fill: {
              color: 'yellow',
              opacity: 0.4,
            },
          },
          rows[1],
          1,
          false,
        );

        sinon.assert.calledOnce(Renderer.setFill);
        sinon.assert.calledWithExactly(
          Renderer.setFill,
          'yellow',
          0.2
        );
      });

      it('should set zebra header fill style when defined but fill color not set', () => {
        Renderer.onCellBackgroundAdd(
          Renderer.table,
          { zebra: true, headerFill: true },
          rows[1],
          1,
          true,
        );

        sinon.assert.calledOnce(Renderer.setFill);
        sinon.assert.calledWithExactly(
          Renderer.setFill,
          Renderer.tableSettings.colors.zebraHeader,
          1
        );
      });
    });

    describe('onCellBackgroundAdded', () => {
      it('should reset to the default fill styles', () => {
        Renderer.onCellBackgroundAdded();

        sinon.assert.calledOnce(Renderer.setFill);
        sinon.assert.calledWithExactly(Renderer.setFill);
      });
    });

    describe('onCellBorderAdd', () => {
      it('should set the border width', () => {
        Renderer.onCellBorderAdd(Renderer.table, {});

        sinon.assert.calledOnce(Renderer.doc.lineWidth);
        sinon.assert.calledWithExactly(Renderer.doc.lineWidth, Renderer.tableSettings.borderWidth);
      });

      it('should set the border color to black by default', () => {
        Renderer.onCellBorderAdd(Renderer.table, {});

        sinon.assert.calledOnce(Renderer.setStroke);
        sinon.assert.calledWithExactly(Renderer.setStroke, 'black', 1);
      });
    });

    describe('onCellBorderAdded', () => {
      it('should reset to the default stroke styles', () => {
        Renderer.onCellBorderAdded();

        sinon.assert.calledOnce(Renderer.setStroke);
        sinon.assert.calledWithExactly(Renderer.setStroke);
      });
    });

    describe('onRowAdd', () => {
      it('should set the font to bold if required', () => {
        Renderer.onRowAdd(Renderer.table, { _bold: true });

        sinon.assert.calledOnce(Renderer.doc.font);
        sinon.assert.calledWithExactly(Renderer.doc.font, Renderer.boldFont);
      });

      it('should not set the font to bold if not required', () => {
        Renderer.onRowAdd(Renderer.table, {});

        sinon.assert.notCalled(Renderer.doc.font);
      });
    });

    describe('onRowAdded', () => {
      it('should reset to the default text styles', () => {
        Renderer.onRowAdded();

        sinon.assert.calledOnce(Renderer.resetText);
        sinon.assert.calledWithExactly(Renderer.resetText);
      });
    });

    describe('onBodyAdded', () => {
      it('should properly update the pdf cursor position after rendering', () => {
        Renderer.doc.x = 150;
        Renderer.doc.y = 200;

        Renderer.onBodyAdded({
          pos: { x: 100 },
          bottomMargin: 50,
        });

        expect(Renderer.doc.x).to.equal(100);
        expect(Renderer.doc.y).to.equal(250);
      });

      it('should default to the page\'s left margin when the table position isn\'t set', () => {
        Renderer.doc.x = 150;

        Renderer.onBodyAdded(new Table());

        expect(Renderer.doc.x).to.equal(36);
      });
    });
  });

  describe('renderPatientInfo', () => {
    it('should render patient information', () => {
      Renderer.doc.y = 32;
      Renderer.renderPatientInfo();
      sinon.assert.calledWith(Renderer.doc.text, getPatientFullName(opts.patient));
      sinon.assert.calledWith(Renderer.doc.text, `DOB: ${formatBirthdate(opts.patient)}`);

      expect(Renderer.patientInfoBox.width).to.be.a('number');
      expect(Renderer.patientInfoBox.width > 0).to.be.true;

      expect(Renderer.patientInfoBox.height).to.be.a('number');
      expect(Renderer.patientInfoBox.height > 0).to.be.true;
    });
  });

  describe('renderTitle', () => {
    it('should be a function', () => {
      expect(Renderer.renderTitle).to.be.a('function');
    });

    it('should render the page title as is for the first rendered page', () => {
      Renderer.doc.text.reset();
      Renderer.currentPageIndex = 0;
      Renderer.renderTitle();
      sinon.assert.calledWith(Renderer.doc.text, 'Print View');
    });

    it('should render the page title with (cont.)` for subsequent pages', () => {
      Renderer.doc.text.reset();
      Renderer.currentPageIndex = 1;
      Renderer.renderTitle();
      sinon.assert.calledWith(Renderer.doc.text, 'Print View (cont.)');
    });

    it('should calculate the width of the title', () => {
      Renderer.renderTitle();
      expect(Renderer.titleWidth).to.be.a('number');
      expect(Renderer.titleWidth > 0).to.be.true;
    });
  });

  describe('renderDateText', () => {
    it('should render the provided date text', () => {
      const text = 'Date range';

      Renderer.renderDateText(text);
      sinon.assert.calledWith(Renderer.doc.text, text);
    });
  });

  describe('renderLogo', () => {
    it('should be a function', () => {
      expect(Renderer.renderLogo).to.be.a('function');
    });

    it('should render the Tidepool logo', () => {
      Renderer.renderLogo();
      sinon.assert.calledOnce(Renderer.doc.image);
    });
  });

  describe('renderDebugGrid', () => {
    it('should be a function', () => {
      expect(Renderer.renderDebugGrid).to.be.a('function');
    });

    // Not really used for anything except local debugging,
    // so no need to test deeply
  });

  describe('renderHeader', () => {
    it('should be a function', () => {
      expect(Renderer.renderHeader).to.be.a('function');
    });

    it('should render the header', () => {
      sinon.spy(Renderer, 'renderPatientInfo');
      sinon.spy(Renderer, 'renderTitle');
      sinon.spy(Renderer, 'renderLogo');
      sinon.spy(Renderer, 'renderDateText');

      Renderer.renderHeader();

      sinon.assert.calledOnce(Renderer.renderPatientInfo);
      sinon.assert.calledOnce(Renderer.renderTitle);
      sinon.assert.calledOnce(Renderer.renderLogo);
      sinon.assert.calledOnce(Renderer.renderDateText);
    });
  });

  describe('renderFooter', () => {
    it('should be a function', () => {
      expect(Renderer.renderFooter).to.be.a('function');
    });

    it('should render the footer help text', () => {
      Renderer.renderFooter();

      sinon.assert.calledWith(
        Renderer.doc.text,
        'Questions or feedback? Please email support@tidepool.org or visit support.tidepool.org.'
      );
    });

    it('should render the date printed', () => {
      Renderer.renderFooter();
      sinon.assert.calledWith(Renderer.doc.text, `Printed on: ${formatCurrentDate()}`);
    });
  });

  describe('setFooterSize', () => {
    it('should be a function', () => {
      expect(Renderer.setFooterSize).to.be.a('function');
    });

    it('should set the footer size', () => {
      const bottomEdge = Renderer.chartArea.bottomEdge - Renderer.doc.currentLineHeight() * 3;

      Renderer.setFooterSize();
      expect(Renderer.chartArea.bottomEdge).to.equal(bottomEdge);
    });
  });

  describe('setHeaderSize', () => {
    it('should be a function', () => {
      expect(Renderer.setHeaderSize).to.be.a('function');
    });

    it('should set the footer size', () => {
      const topEdge = Renderer.chartArea.topEdge + Renderer.doc.currentLineHeight() * 4;

      Renderer.setHeaderSize();
      expect(Renderer.chartArea.topEdge).to.equal(topEdge);
    });
  });
});


text:
namespace Poco {
namespace Data {

using namespace Keywords;

Range::Range(Limit::SizeT lowValue, Limit::SizeT upValue, bool hardLimit) :
	_lower(lowerLimit(lowValue)),
	_upper(upperLimit(upValue, hardLimit))
{
	if (lowValue > upValue)
		throw LimitException("lowerLimit > upperLimit!");
}

Range::~Range()
{
}

} } // namespace Poco::Data

redpajama_set_name: RedPajamaGithub


text:
package com.xelita.study.spring.boot.controller;

import com.xelita.study.spring.boot.domain.*;
import com.xelita.study.spring.boot.repository.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.transaction.annotation.*;
import org.springframework.web.bind.annotation.*;

import java.util.*;

@Transactional(readOnly = true)
@RequestMapping("/api")
@RestController
public class UserController {

	@Autowired
	private UserRepository userRepository;

	// *************************************************************************
	//
	// Rest methods
	//
	// *************************************************************************

	@RequestMapping(value = "/users", method = RequestMethod.GET)
	public List<User> getUsers() {
		return this.userRepository.findAll();
	}
}


text:
package com.ysd.mymap.db;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

import java.sql.SQLException;
import java.util.Calendar;

/**
 * Created by Administrator on 2016/1/28.
 */
public class TrackDbAdapter extends DbAdapter{
    private static final String TAG="TrackDbAdapter";
    public static final String TABLE_NAME = "tracks";//表名
    public static final String ID = "_id";//主键
    public static final String KEY_ROWID = "_id";
    public static final String NAME = "name";//名
    public static final String DESC = "desc";//说明
    public static final String DIST = "distance";//距离
    public static final String TRACKEDTIME = "tracked_time";//时间
    public static final String LOCATE_COUNT = "locates_count";//点数
    public static final String CREATED = "created_at";//创建时间
    public static final String UPDATED = "update_at";//更新时间
    public static final String AVGSPEED = "avg_speed";//平均速度
    public static final String MAXSPEED = "max_speed";//最大速度
    private DatabaseHelper mDbHelper;
    private SQLiteDatabase mDb;
    private final Context mCtx;

    public TrackDbAdapter(Context mCtx) {
        this.mCtx = mCtx;
    }
    public TrackDbAdapter open() throws SQLException {
        mDbHelper = new DatabaseHelper(mCtx);
        mDb = mDbHelper.getWritableDatabase();
        return this;
    }
    public void close(){
        mDbHelper.close();
    }
    public Cursor getTrack(long rowId) throws SQLException {
        Cursor mCursor = mDb.query(true, TABLE_NAME, new String[]{KEY_ROWID,NAME,DESC,CREATED}, KEY_ROWID + "=" + rowId, null, null, null, null, null);
        if (mCursor != null) {
            mCursor.moveToFirst();
        }
        return mCursor;
    }
    //增加
    public long createTrack(String name, String desc) {
        Log.d(TAG, "createTrack");
        ContentValues initialValues = new ContentValues();
        initialValues.put(NAME,name);
        initialValues.put(DESC,desc);
        Calendar calendar = Calendar.getInstance();
        String created = calendar.get(Calendar.YEAR) + "-" + calendar.get(Calendar.MONTH) + "-" + calendar.get(Calendar.DAY_OF_MONTH) + " " + calendar.get(Calendar.HOUR_OF_DAY) + ":" + calendar.get(Calendar.MINUTE) + ":" + calendar.get(Calendar.SECOND);
        initialValues.put(CREATED,created);
        initialValues.put(UPDATED,created);
        return mDb.insert(TABLE_NAME, null, initialValues);
    }
    //删除
    public boolean deleteTrack(long rowId) {
        return mDb.delete(TABLE_NAME, KEY_ROWID + "=" + rowId, null) > 0;
    }

    //修改
    public boolean updateTrack(long rowId, String name, String desc) {
        ContentValues args = new ContentValues();
        args.put(NAME,name);
        args.put(DESC, desc);
        Calendar calendar = Calendar.getInstance();
        String updated = calendar.get(Calendar.YEAR) + "-" + calendar.get(Calendar.MONTH) + "-" + calendar.get(Calendar.DAY_OF_MONTH) + " " + calendar.get(Calendar.HOUR_OF_DAY) + ":" + calendar.get(Calendar.MINUTE) + ":" + calendar.get(Calendar.SECOND);
        args.put(UPDATED,updated);
        return mDb.update(TABLE_NAME, args, KEY_ROWID + "=" + rowId, null) > 0;
    }
}


text:
var MetaLayerCharts ={
	/*startDate: MetaLayer.getLastMonthDate(), // Default start date
	endDate: MetaLayer.getCurrentDate(), // Default end date
	startMonth: MetaLayer.getMonth(), // Default month date*/
};

MetaLayerCharts =  {

	regionsMeasure: "[Region].[All Regions]",
	selectedRegionMeasure : "[Region].[All Regions]",
	departmentMeasure: "[Department].[All Departments]",

	pieChartClicked:function(value){
		if(value == "All Regions"){
			MetaLayerCharts.regionsMeasure = "[Region].[All Regions].Children";
			Dashboards.fireChange("MetaLayerCharts.regionsMeasure",MetaLayerCharts.regionsMeasure);
		}
		else
		{
			MetaLayerCharts.selectedRegionMeasure = "[Region].[All Regions].["+ value + "]";
			Dashboards.fireChange("MetaLayerCharts.selectedRegionMeasure",MetaLayerCharts.selectedRegionMeasure);
		}
	},

	barChartClicked:function(value){

		MetaLayerCharts.departmentMeasure = "[Department].[All Departments].[" + value + "]";
		Dashboards.fireChange("MetaLayerCharts.departmentMeasure",MetaLayerCharts.departmentMeasure);
	},

	pieChartDefinition : {
		width: 300,
		height: 200,
		chartType: "PieChart",
		datasetType: "CategoryDataset",
		is3d: "true",
		isStacked: "true",
		includeLegend: "false",
		foregroundAlpha: 0.7,
		queryType: 'mdx',
		jndi: "SampleData",
		catalog: "solution:steel-wheels/analysis/SampleData.mondrian.xml",
		urlTemplate: "javascript:MetaLayerCharts.pieChartClicked( encode_prepare('{region}') )",
		parameterName: "region",
		title: "First choose region:",
		query: function(){

		var query = "with member [Measures].[Variance Percent] as '([Measures].[Variance] / [Measures].[Budget])',"+
			" format_string = IIf(((([Measures].[Variance] / [Measures].[Budget]) * 100.0) > 2.0), \"|#.00%|style='green'\","+
            " IIf(((([Measures].[Variance] / [Measures].[Budget]) * 100.0) < 0.0), \"|#.00%|style='red'\", \"#.00%\"))" +
			" select NON EMPTY {[Measures].[Actual], [Measures].[Budget], [Measures].[Variance], [Measures].[Variance Percent]} ON COLUMNS," +
			" NON EMPTY ( " + MetaLayerCharts.regionsMeasure + " ) ON ROWS " +
			" from [Quadrant Analysis]";

			return query;
		}
	},

		barChartDefinition : {
		width: 300,
		height: 250,
		chartType: "BarChart",
		datasetType: "CategoryDataset",
		is3d: "true",
		isStacked: "true",
		includeLegend: "false",
		foregroundAlpha: 0.7,
		queryType: 'mdx',
		jndi: "SampleData",
		catalog: "solution:steel-wheels/analysis/SampleData.mondrian.xml",
		title: "Then analyse departments:",
		urlTemplate: "javascript:MetaLayerCharts.barChartClicked(encode_prepare('{department}'))",
		parameterName: "department",
		query: function(){

		var query = "with member [Measures].[Variance Percent] as '([Measures].[Variance] / [Measures].[Budget])',"+
			" format_string = IIf(((([Measures].[Variance] / [Measures].[Budget]) * 100.0) > 2.0), \"|#.00%|style='green'\","+
            " IIf(((([Measures].[Variance] / [Measures].[Budget]) * 100.0) < 0.0), \"|#.00%|style='red'\", \"#.00%\"))" +
			" select NON EMPTY {[Measures].[Actual], [Measures].[Budget], [Measures].[Variance], [Measures].[Variance Percent]} ON COLUMNS," +
			" NON EMPTY ([Department].[All Departments].Children ) ON ROWS " +
			" from [Quadrant Analysis]" +
			" where (" + MetaLayerCharts.selectedRegionMeasure + ")";

			return query;
		}
	},

	dialChartDefinition : {
		width: 300,
		height: 200,
		chartType: "DialChart",
		queryType: 'mdx',
		is3d: 'true',
		jndi: "SampleData",
		title: "Check current budget",
		catalog: "solution:steel-wheels/analysis/SampleData.mondrian.xml",
		//colors: ["#F16C3A","#FFFF00","#B0D837"],
		intervals: [7000000,70000000,150000000],
		includeLegend: true,

		query: function(){

			var query =  " select NON EMPTY [Measures].[Budget] ON COLUMNS," +
			" NON EMPTY (" + MetaLayerCharts.departmentMeasure + " ) ON ROWS " +
			" from [Quadrant Analysis]";

			return query;
		}
	}
};


text:
using namespace clang;
using namespace ento;

static bool shouldRunOnFunctionOrMethod(const NamedDecl *ND);
static bool isInitializationMethod(const ObjCMethodDecl *MD);
static bool isInitMessage(const ObjCMethodCall &Msg);
static bool isSelfVar(SVal location, CheckerContext &C);

namespace {
class ObjCSelfInitChecker : public Checker<  check::PostObjCMessage,
                                             check::PostStmt<ObjCIvarRefExpr>,
                                             check::PreStmt<ReturnStmt>,
                                             check::PreCall,
                                             check::PostCall,
                                             check::Location,
                                             check::Bind > {
  mutable std::unique_ptr<BugType> BT;

  void checkForInvalidSelf(const Expr *E, CheckerContext &C,
                           const char *errorStr) const;

public:
  ObjCSelfInitChecker() {}
  void checkPostObjCMessage(const ObjCMethodCall &Msg, CheckerContext &C) const;
  void checkPostStmt(const ObjCIvarRefExpr *E, CheckerContext &C) const;
  void checkPreStmt(const ReturnStmt *S, CheckerContext &C) const;
  void checkLocation(SVal location, bool isLoad, const Stmt *S,
                     CheckerContext &C) const;
  void checkBind(SVal loc, SVal val, const Stmt *S, CheckerContext &C) const;

  void checkPreCall(const CallEvent &CE, CheckerContext &C) const;
  void checkPostCall(const CallEvent &CE, CheckerContext &C) const;

  void printState(raw_ostream &Out, ProgramStateRef State,
                  const char *NL, const char *Sep) const override;
};
} // end anonymous namespace

namespace {
enum SelfFlagEnum {
  /// No flag set.
  SelfFlag_None = 0x0,
  /// Value came from 'self'.
  SelfFlag_Self    = 0x1,
  /// Value came from the result of an initializer (e.g. [super init]).
  SelfFlag_InitRes = 0x2
};
}

REGISTER_MAP_WITH_PROGRAMSTATE(SelfFlag, SymbolRef, unsigned)
REGISTER_TRAIT_WITH_PROGRAMSTATE(CalledInit, bool)

/// A call receiving a reference to 'self' invalidates the object that
/// 'self' contains. This keeps the "self flags" assigned to the 'self'
/// object before the call so we can assign them to the new object that 'self'
/// points to after the call.
REGISTER_TRAIT_WITH_PROGRAMSTATE(PreCallSelfFlags, unsigned)

static SelfFlagEnum getSelfFlags(SVal val, ProgramStateRef state) {
  if (SymbolRef sym = val.getAsSymbol())
    if (const unsigned *attachedFlags = state->get<SelfFlag>(sym))
      return (SelfFlagEnum)*attachedFlags;
  return SelfFlag_None;
}

static SelfFlagEnum getSelfFlags(SVal val, CheckerContext &C) {
  return getSelfFlags(val, C.getState());
}

static void addSelfFlag(ProgramStateRef state, SVal val,
                        SelfFlagEnum flag, CheckerContext &C) {
  // We tag the symbol that the SVal wraps.
  if (SymbolRef sym = val.getAsSymbol()) {
    state = state->set<SelfFlag>(sym, getSelfFlags(val, state) | flag);
    C.addTransition(state);
  }
}

static bool hasSelfFlag(SVal val, SelfFlagEnum flag, CheckerContext &C) {
  return getSelfFlags(val, C) & flag;
}

/// Returns true of the value of the expression is the object that 'self'
/// points to and is an object that did not come from the result of calling
/// an initializer.
static bool isInvalidSelf(const Expr *E, CheckerContext &C) {
  SVal exprVal = C.getSVal(E);
  if (!hasSelfFlag(exprVal, SelfFlag_Self, C))
    return false; // value did not come from 'self'.
  if (hasSelfFlag(exprVal, SelfFlag_InitRes, C))
    return false; // 'self' is properly initialized.

  return true;
}

void ObjCSelfInitChecker::checkForInvalidSelf(const Expr *E, CheckerContext &C,
                                              const char *errorStr) const {
  if (!E)
    return;

  if (!C.getState()->get<CalledInit>())
    return;

  if (!isInvalidSelf(E, C))
    return;

  // Generate an error node.
  ExplodedNode *N = C.generateErrorNode();
  if (!N)
    return;

  if (!BT)
    BT.reset(new BugType(this, "Missing \"self = [(super or self) init...]\"",
                         categories::CoreFoundationObjectiveC));
  C.emitReport(llvm::make_unique<BugReport>(*BT, errorStr, N));
}

void ObjCSelfInitChecker::checkPostObjCMessage(const ObjCMethodCall &Msg,
                                               CheckerContext &C) const {
  // When encountering a message that does initialization (init rule),
  // tag the return value so that we know later on that if self has this value
  // then it is properly initialized.

  // FIXME: A callback should disable checkers at the start of functions.
  if (!shouldRunOnFunctionOrMethod(dyn_cast<NamedDecl>(
                                C.getCurrentAnalysisDeclContext()->getDecl())))
    return;

  if (isInitMessage(Msg)) {
    // Tag the return value as the result of an initializer.
    ProgramStateRef state = C.getState();

    // FIXME this really should be context sensitive, where we record
    // the current stack frame (for IPA).  Also, we need to clean this
    // value out when we return from this method.
    state = state->set<CalledInit>(true);

    SVal V = C.getSVal(Msg.getOriginExpr());
    addSelfFlag(state, V, SelfFlag_InitRes, C);
    return;
  }

  // We don't check for an invalid 'self' in an obj-c message expression to cut
  // down false positives where logging functions get information from self
  // (like its class) or doing "invalidation" on self when the initialization
  // fails.
}

void ObjCSelfInitChecker::checkPostStmt(const ObjCIvarRefExpr *E,
                                        CheckerContext &C) const {
  // FIXME: A callback should disable checkers at the start of functions.
  if (!shouldRunOnFunctionOrMethod(dyn_cast<NamedDecl>(
                                 C.getCurrentAnalysisDeclContext()->getDecl())))
    return;

  checkForInvalidSelf(
      E->getBase(), C,
      "Instance variable used while 'self' is not set to the result of "
      "'[(super or self) init...]'");
}

void ObjCSelfInitChecker::checkPreStmt(const ReturnStmt *S,
                                       CheckerContext &C) const {
  // FIXME: A callback should disable checkers at the start of functions.
  if (!shouldRunOnFunctionOrMethod(dyn_cast<NamedDecl>(
                                 C.getCurrentAnalysisDeclContext()->getDecl())))
    return;

  checkForInvalidSelf(S->getRetValue(), C,
                      "Returning 'self' while it is not set to the result of "
                      "'[(super or self) init...]'");
}

// When a call receives a reference to 'self', [Pre/Post]Call pass
// the SelfFlags from the object 'self' points to before the call to the new
// object after the call. This is to avoid invalidation of 'self' by logging
// functions.
// Another common pattern in classes with multiple initializers is to put the
// subclass's common initialization bits into a static function that receives
// the value of 'self', e.g:
// @code
//   if (!(self = [super init]))
//     return nil;
//   if (!(self = _commonInit(self)))
//     return nil;
// @endcode
// Until we can use inter-procedural analysis, in such a call, transfer the
// SelfFlags to the result of the call.

void ObjCSelfInitChecker::checkPreCall(const CallEvent &CE,
                                       CheckerContext &C) const {
  // FIXME: A callback should disable checkers at the start of functions.
  if (!shouldRunOnFunctionOrMethod(dyn_cast<NamedDecl>(
                                 C.getCurrentAnalysisDeclContext()->getDecl())))
    return;

  ProgramStateRef state = C.getState();
  unsigned NumArgs = CE.getNumArgs();
  // If we passed 'self' as and argument to the call, record it in the state
  // to be propagated after the call.
  // Note, we could have just given up, but try to be more optimistic here and
  // assume that the functions are going to continue initialization or will not
  // modify self.
  for (unsigned i = 0; i < NumArgs; ++i) {
    SVal argV = CE.getArgSVal(i);
    if (isSelfVar(argV, C)) {
      unsigned selfFlags = getSelfFlags(state->getSVal(argV.castAs<Loc>()), C);
      C.addTransition(state->set<PreCallSelfFlags>(selfFlags));
      return;
    } else if (hasSelfFlag(argV, SelfFlag_Self, C)) {
      unsigned selfFlags = getSelfFlags(argV, C);
      C.addTransition(state->set<PreCallSelfFlags>(selfFlags));
      return;
    }
  }
}

void ObjCSelfInitChecker::checkPostCall(const CallEvent &CE,
                                        CheckerContext &C) const {
  // FIXME: A callback should disable checkers at the start of functions.
  if (!shouldRunOnFunctionOrMethod(dyn_cast<NamedDecl>(
                                 C.getCurrentAnalysisDeclContext()->getDecl())))
    return;

  ProgramStateRef state = C.getState();
  SelfFlagEnum prevFlags = (SelfFlagEnum)state->get<PreCallSelfFlags>();
  if (!prevFlags)
    return;
  state = state->remove<PreCallSelfFlags>();

  unsigned NumArgs = CE.getNumArgs();
  for (unsigned i = 0; i < NumArgs; ++i) {
    SVal argV = CE.getArgSVal(i);
    if (isSelfVar(argV, C)) {
      // If the address of 'self' is being passed to the call, assume that the
      // 'self' after the call will have the same flags.
      // EX: log(&self)
      addSelfFlag(state, state->getSVal(argV.castAs<Loc>()), prevFlags, C);
      return;
    } else if (hasSelfFlag(argV, SelfFlag_Self, C)) {
      // If 'self' is passed to the call by value, assume that the function
      // returns 'self'. So assign the flags, which were set on 'self' to the
      // return value.
      // EX: self = performMoreInitialization(self)
      addSelfFlag(state, CE.getReturnValue(), prevFlags, C);
      return;
    }
  }

  C.addTransition(state);
}

void ObjCSelfInitChecker::checkLocation(SVal location, bool isLoad,
                                        const Stmt *S,
                                        CheckerContext &C) const {
  if (!shouldRunOnFunctionOrMethod(dyn_cast<NamedDecl>(
        C.getCurrentAnalysisDeclContext()->getDecl())))
    return;

  // Tag the result of a load from 'self' so that we can easily know that the
  // value is the object that 'self' points to.
  ProgramStateRef state = C.getState();
  if (isSelfVar(location, C))
    addSelfFlag(state, state->getSVal(location.castAs<Loc>()), SelfFlag_Self,
                C);
}

void ObjCSelfInitChecker::checkBind(SVal loc, SVal val, const Stmt *S,
                                    CheckerContext &C) const {
  // Allow assignment of anything to self. Self is a local variable in the
  // initializer, so it is legal to assign anything to it, like results of
  // static functions/method calls. After self is assigned something we cannot
  // reason about, stop enforcing the rules.
  // (Only continue checking if the assigned value should be treated as self.)
  if ((isSelfVar(loc, C)) &&
      !hasSelfFlag(val, SelfFlag_InitRes, C) &&
      !hasSelfFlag(val, SelfFlag_Self, C) &&
      !isSelfVar(val, C)) {

    // Stop tracking the checker-specific state in the state.
    ProgramStateRef State = C.getState();
    State = State->remove<CalledInit>();
    if (SymbolRef sym = loc.getAsSymbol())
      State = State->remove<SelfFlag>(sym);
    C.addTransition(State);
  }
}

void ObjCSelfInitChecker::printState(raw_ostream &Out, ProgramStateRef State,
                                     const char *NL, const char *Sep) const {
  SelfFlagTy FlagMap = State->get<SelfFlag>();
  bool DidCallInit = State->get<CalledInit>();
  SelfFlagEnum PreCallFlags = (SelfFlagEnum)State->get<PreCallSelfFlags>();

  if (FlagMap.isEmpty() && !DidCallInit && !PreCallFlags)
    return;

  Out << Sep << NL << *this << " :" << NL;

  if (DidCallInit)
    Out << "  An init method has been called." << NL;

  if (PreCallFlags != SelfFlag_None) {
    if (PreCallFlags & SelfFlag_Self) {
      Out << "  An argument of the current call came from the 'self' variable."
          << NL;
    }
    if (PreCallFlags & SelfFlag_InitRes) {
      Out << "  An argument of the current call came from an init method."
          << NL;
    }
  }

  Out << NL;
  for (SelfFlagTy::iterator I = FlagMap.begin(), E = FlagMap.end();
       I != E; ++I) {
    Out << I->first << " : ";

    if (I->second == SelfFlag_None)
      Out << "none";

    if (I->second & SelfFlag_Self)
      Out << "self variable";

    if (I->second & SelfFlag_InitRes) {
      if (I->second != SelfFlag_InitRes)
        Out << " | ";
      Out << "result of init method";
    }

    Out << NL;
  }
}

// FIXME: A callback should disable checkers at the start of functions.
static bool shouldRunOnFunctionOrMethod(const NamedDecl *ND) {
  if (!ND)
    return false;

  const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(ND);
  if (!MD)
    return false;
  if (!isInitializationMethod(MD))
    return false;

  // self = [super init] applies only to NSObject subclasses.
  // For instance, NSProxy doesn't implement -init.
  ASTContext &Ctx = MD->getASTContext();
  IdentifierInfo* NSObjectII = &Ctx.Idents.get("NSObject");
  ObjCInterfaceDecl *ID = MD->getClassInterface()->getSuperClass();
  for ( ; ID ; ID = ID->getSuperClass()) {
    IdentifierInfo *II = ID->getIdentifier();

    if (II == NSObjectII)
      break;
  }
  return ID != nullptr;
}

/// Returns true if the location is 'self'.
static bool isSelfVar(SVal location, CheckerContext &C) {
  AnalysisDeclContext *analCtx = C.getCurrentAnalysisDeclContext();
  if (!analCtx->getSelfDecl())
    return false;
  if (!location.getAs<loc::MemRegionVal>())
    return false;

  loc::MemRegionVal MRV = location.castAs<loc::MemRegionVal>();
  if (const DeclRegion *DR = dyn_cast<DeclRegion>(MRV.stripCasts()))
    return (DR->getDecl() == analCtx->getSelfDecl());

  return false;
}

static bool isInitializationMethod(const ObjCMethodDecl *MD) {
  return MD->getMethodFamily() == OMF_init;
}

static bool isInitMessage(const ObjCMethodCall &Call) {
  return Call.getMethodFamily() == OMF_init;
}

//===----------------------------------------------------------------------===//
// Registration.
//===----------------------------------------------------------------------===//

void ento::registerObjCSelfInitChecker(CheckerManager &mgr) {
  mgr.registerChecker<ObjCSelfInitChecker>();
}


text:
"""
    The PyBuilder error module.
    Defines all possible errors that can arise during the execution of PyBuilder.
"""

class PyBuilderException(Exception):

    def __init__(self, message, *arguments):
        self._message = message
        self._arguments = arguments

    @property
    def message(self):
        return self._message % self._arguments

    def __str__(self):
        return self.message

class InvalidNameException(PyBuilderException):

    def __init__(self, name):
        super(InvalidNameException, self).__init__("Invalid name: %s", name)

class NoSuchTaskException(PyBuilderException):

    def __init__(self, name):
        super(NoSuchTaskException, self).__init__("No such task %s", name)

class CircularTaskDependencyException(PyBuilderException):

    def __init__(self, first, second=None):
        if second:
            super(
                CircularTaskDependencyException, self).__init__("Circular task dependency detected between %s and %s",
                                                                first,
                                                                second)
        self.first = first
        self.second = second

class MissingPrerequisiteException(PyBuilderException):

    def __init__(self, prerequisite, caller="n/a"):
        super(
            MissingPrerequisiteException, self).__init__("Missing prerequisite %s required by %s",
                                                         prerequisite, caller)

class MissingTaskDependencyException(PyBuilderException):

    def __init__(self, source, dependency):
        super(
            MissingTaskDependencyException, self).__init__("Missing task '%s' required for task '%s'",
                                                           dependency, source)

class MissingActionDependencyException(PyBuilderException):

    def __init__(self, source, dependency):
        super(
            MissingActionDependencyException, self).__init__("Missing task '%s' required for action '%s'",
                                                             dependency, source)

class MissingPluginException(PyBuilderException):

    def __init__(self, plugin, message=""):
        super(MissingPluginException, self).__init__(
            "Missing plugin '%s': %s", plugin, message)

class BuildFailedException(PyBuilderException):
    pass

class MissingPropertyException(PyBuilderException):

    def __init__(self, property):
        super(MissingPropertyException, self).__init__(
            "No such property: %s", property)

class ProjectValidationFailedException(BuildFailedException):

    def __init__(self, validation_messages):
        BuildFailedException.__init__(
            self, "Project validation failed: " + "\n-".join(validation_messages))
        self.validation_messages = validation_messages

class InternalException(PyBuilderException):
    pass

class DependenciesNotResolvedException(InternalException):
    def __init__(self):
        super(DependenciesNotResolvedException, self).__init__("Dependencies have not been resolved.")


text:
QueryManager::ElapsedTimeMap QueryManager::m_gpuElapsedTime;
QueryManager::ElapsedTimeMap QueryManager::m_cpuElapsedTime;
uint32_t QueryManager::m_maxHistoryCount = 1024;
Timer QueryManager::m_timer;
uint32_t QueryManager::m_writeQueryBufferIdx = 0;
uint32_t QueryManager::m_readQueryBufferIdx = 0;
InternalElapsedTimeInfo* QueryManager::m_currentTimeInfo[2]{nullptr, nullptr};

struct InternalElapsedTimeInfo
{
    InternalElapsedTimeInfo(const InternalElapsedTimeInfo&) = delete;
    InternalElapsedTimeInfo& operator=(const InternalElapsedTimeInfo&) = delete;

    InternalElapsedTimeInfo() {}
    InternalElapsedTimeInfo(const std::string& name)
    {
        info[ElapsedTimeInfoType::PER_FRAME] = ElapsedTimeInfo(name);
        info[ElapsedTimeInfoType::AVERAGE_PER_INTERVAL] = ElapsedTimeInfo(name);
        info[ElapsedTimeInfoType::MAX_PER_INTERVAL] = ElapsedTimeInfo(name);
    }

    virtual ~InternalElapsedTimeInfo() {}

    virtual void begin(uint32_t bufferIdx)
    {
        if (expectingEnd)
            LOG_ERROR("Expecting end call but begin was called: " << info[ElapsedTimeInfoType::PER_FRAME].m_name);

        expectingEnd = true;
        ++beginEndCounter;
        secureTimingObject();
    }

    virtual void end(uint32_t)
    {
        if (!expectingEnd)
            LOG_ERROR("Expecting begin call but end was called: " << info[ElapsedTimeInfoType::PER_FRAME].m_name);

        expectingEnd = false;
    }

    void setParent(InternalElapsedTimeInfo* p)
    {
        parent = p;
        parent->children.push_back(this);
    }

    void reset()
    {
        beginEndCounter = 0;
        parent = nullptr;
        children.clear();
    }

    virtual uint64_t getElapsedTimeInMicroseconds(uint32_t bufferIdx) = 0;
    virtual void secureTimingObject() = 0;

    uint64_t getElapsedTimeOfChildrenInMicroseconds(uint32_t bufferIdx)
    {
        uint64_t sum = 0;
        for (auto c : children)
            sum += c->getElapsedTimeInMicroseconds(bufferIdx);

        return sum;
    }

    bool expectingEnd{ false };
    ElapsedTimeInfoBag info;
    ElapsedTimeInfo::Entry maxEntry;
    ElapsedTimeInfo::Entry averageEntry;
    uint32_t historyUpdateRateInMilliseconds{250};
    uint32_t historyUpdateTimerInMilliseconds{ 0 };
    uint32_t historyUpdateFrameCounter{ 0 };

    // OpenGL doesn't allow nested begin/end calls but having this functionality is very convenient
    // and can be realized with a child/parent hierarchy.
    // Note: Calls like: begin0 [code...] begin1 [code...] end0 ->ERROR are currently not supported.
    // Only begin0 [code...] begin1 [code...] end1 [code...] end0 with an arbitrary amount of begin/end calls inbetween.
    std::vector<InternalElapsedTimeInfo*> children;
    InternalElapsedTimeInfo* parent{ nullptr };

    // Number of begin/end calls during the same frame
    uint32_t beginEndCounter{ 0 };
};

struct InternalElapsedTimeInfoGPU : InternalElapsedTimeInfo
{
    InternalElapsedTimeInfoGPU(const std::string& name)
        : InternalElapsedTimeInfo(name) {}

    void secureTimingObject() override
    {
        for (int i = 0; i < MAX_QUERY_OBJECT_BUFFERS; ++i)
            if (beginEndCounter > queryObjects[i].size())
            {
                queryObjects[i].push_back(std::move(std::make_unique<GLQueryObject>()));
                // To make sure that glGetQueryObject() doesn't cause GL_INVALID_OPERATION
                queryObjects[i][beginEndCounter - 1]->begin(GL_TIME_ELAPSED);
                queryObjects[i][beginEndCounter - 1]->end();
            }
    }

    void begin(uint32_t bufferIdx) override
    {
        InternalElapsedTimeInfo::begin(bufferIdx);
        queryObjects[bufferIdx][beginEndCounter - 1]->begin(GL_TIME_ELAPSED);
    }

    void end(uint32_t bufferIdx) override
    {
        InternalElapsedTimeInfo::end(bufferIdx);
        queryObjects[bufferIdx][beginEndCounter - 1]->end();
    }

    uint64_t getElapsedTimeInMicroseconds(uint32_t bufferIdx) override
    {
        GLuint64 sum = 0;

        // Get accumulated time (OpenGL timing is in nanoseconds)
        for (uint32_t i = 0; i < beginEndCounter; ++i)
            sum += queryObjects[bufferIdx][i]->getUInt64(GL_QUERY_RESULT);

        return uint64_t(sum / GLuint64(1000)) + getElapsedTimeOfChildrenInMicroseconds(bufferIdx);
    }

    std::vector<std::unique_ptr<GLQueryObject>> queryObjects[MAX_QUERY_OBJECT_BUFFERS];
};

struct InternalElapsedTimeInfoCPU : InternalElapsedTimeInfo
{
    InternalElapsedTimeInfoCPU(const std::string& name)
        : InternalElapsedTimeInfo(name) {}

    void secureTimingObject() override
    {
        for (int i = 0; i < MAX_QUERY_OBJECT_BUFFERS; ++i)
            if (beginEndCounter > timers[i].size())
            timers[i].push_back(Timer());
    }

    void begin(uint32_t bufferIdx) override
    {
        InternalElapsedTimeInfo::begin(bufferIdx);
        timers[bufferIdx][beginEndCounter - 1].start();
    }

    void end(uint32_t bufferIdx) override
    {
        InternalElapsedTimeInfo::end(bufferIdx);
        timers[bufferIdx][beginEndCounter - 1].tick();
    }

    uint64_t getElapsedTimeInMicroseconds(uint32_t bufferIdx) override
    {
        uint64_t sum = 0;

        // Get accumulated time
        for (uint32_t i = 0; i < beginEndCounter; ++i)
            sum += timers[bufferIdx][i].deltaTimeInMicroseconds();

        return sum + getElapsedTimeOfChildrenInMicroseconds(bufferIdx);
    }

    std::vector<Timer> timers[MAX_QUERY_OBJECT_BUFFERS];
};

uint64_t ElapsedTimeInfo::getAverageInMicroseconds(uint64_t intervalInMilliseconds) const
{
    if (m_curEntry < 0 || m_elapsedTimeHistory.size() <= static_cast<size_t>(m_curEntry))
        return 0;

    uint64_t firstTimeStamp = m_elapsedTimeHistory[m_curEntry].timestampInMilliseconds;
    uint64_t average = 0;
    for (uint32_t i = 0; i < m_historySize; ++i)
    {
        int idx = (m_curEntry - i + m_historySize) % m_historySize;
        uint64_t interval = firstTimeStamp - m_elapsedTimeHistory[idx].timestampInMilliseconds;
        average += m_elapsedTimeHistory[idx].elapsedTimeInMicroseconds;
        if (interval >= intervalInMilliseconds)
            return average / (i + 1);
    }

    return average / m_historySize;
}

uint64_t ElapsedTimeInfo::getMaxInMicroseconds(uint64_t intervalInMilliseconds) const
{
    if (m_curEntry < 0 || m_elapsedTimeHistory.size() <= static_cast<size_t>(m_curEntry))
        return 0;

    uint64_t firstTimeStamp = m_elapsedTimeHistory[m_curEntry].timestampInMilliseconds;
    uint64_t highest = 0;
    for (uint32_t i = 0; i < m_historySize; ++i)
    {
        int idx = (m_curEntry - i + m_historySize) % m_historySize;
        uint64_t interval = firstTimeStamp - m_elapsedTimeHistory[idx].timestampInMilliseconds;

        if (highest < m_elapsedTimeHistory[idx].elapsedTimeInMicroseconds)
            highest = m_elapsedTimeHistory[idx].elapsedTimeInMicroseconds;

        if (interval >= intervalInMilliseconds)
            return highest;
    }

    return highest;
}

std::vector<ElapsedTimeInfo::Entry> ElapsedTimeInfo::getHistory(int start, int end) const
{
    assert(start <= end);

    if (m_curEntry < 0 || m_elapsedTimeHistory.size() <= static_cast<size_t>(m_curEntry))
        return std::vector<Entry>();

    start = std::max(start, -int(m_historySize));
    end = std::max(end, -int(m_historySize));
    uint32_t count = (end - start) + 1; // + 1 to include end

    std::vector<Entry> history;
    for (uint32_t i = 0; i < m_historySize && i < count; ++i)
    {
        int idx = (m_curEntry + 1 + i + start + m_historySize) % m_historySize;
        history.push_back(m_elapsedTimeHistory[idx]);
    }

    return history;
}

void ElapsedTimeInfo::add(Entry e, uint32_t maxHistoryCount)
{
    if (m_historySize < maxHistoryCount)
    {
        ++m_historySize;
        m_elapsedTimeHistory.resize(maxHistoryCount);
    }

    m_curEntry = (m_curEntry + 1) % m_historySize;
    m_elapsedTimeHistory[m_curEntry] = e;
    m_addedEntryCount++;
}

void QueryManager::update()
{
    m_writeQueryBufferIdx = (m_writeQueryBufferIdx + 1) % MAX_QUERY_OBJECT_BUFFERS;

    if (m_writeQueryBufferIdx == m_readQueryBufferIdx)
    {
        uint64_t curTimeInMilliseconds = Time::getTimestampInMilliseconds();

        updateEntries(m_gpuElapsedTime, curTimeInMilliseconds);
        updateEntries(m_cpuElapsedTime, curTimeInMilliseconds);

        m_readQueryBufferIdx = (m_readQueryBufferIdx + 1) % MAX_QUERY_OBJECT_BUFFERS;
    }

    for (auto& p : m_cpuElapsedTime)
        p.second->reset();

    for (auto& p : m_gpuElapsedTime)
        p.second->reset();

    for (int i = 0; i < 2; ++i)
        m_currentTimeInfo[i] = nullptr;

    m_timer.tick();
}

void QueryManager::reserveElapsedTime(QueryTarget target, const std::string& name)
{
    auto entryMap = getElapsedTimeMap(target);

    auto it = entryMap->find(name);
    if (it == entryMap->end())
    {
        (*entryMap)[name] = getNewEntry(target, name);
    }
    else
        LOG_ERROR("Attempting to reserve a reserved name: " << name);
}

void QueryManager::beginElapsedTime(QueryTarget target, const std::string& name)
{
    auto entryMap = getElapsedTimeMap(target);
    auto it = entryMap->find(name);

    if (it == entryMap->end())
    {
        reserveElapsedTime(target, name);
        it = entryMap->find(name);
    }

    auto curInfo = m_currentTimeInfo[int(target)];
    if (curInfo && curInfo != it->second.get())
        it->second->setParent(curInfo);

    if (it->second->parent)
        it->second->parent->end(m_writeQueryBufferIdx);

    m_currentTimeInfo[int(target)] = it->second.get();

    it->second->begin(m_writeQueryBufferIdx);
}

void QueryManager::endElapsedTime(QueryTarget target, const std::string& name)
{
    auto entryMap = getElapsedTimeMap(target);
    auto it = entryMap->find(name);

    if (it != entryMap->end())
    {
        it->second->end(m_writeQueryBufferIdx);
        if (it->second->parent)
            it->second->parent->begin(m_writeQueryBufferIdx);

        m_currentTimeInfo[int(target)] = it->second->parent;
    }
    else
        LOG_ERROR("Could not find query: " << name);
}

std::vector<ElapsedTimeInfoBag> QueryManager::getElapsedTimeInfo(QueryTarget target)
{
    ElapsedTimeMap* entryMap = getElapsedTimeMap(target);

    std::vector<ElapsedTimeInfoBag> info;

    for (auto& p : *entryMap)
        info.push_back(p.second->info);

    return info;
}

QueryManager::ElapsedTimeMap* QueryManager::getElapsedTimeMap(QueryTarget target)
{
    switch (target)
    {
    case QueryTarget::CPU:
        return &m_cpuElapsedTime;
    case QueryTarget::GPU:
        return &m_gpuElapsedTime;
    default:
        assert(false);
        break;
    }

    return nullptr;
}

std::unique_ptr<InternalElapsedTimeInfo> QueryManager::getNewEntry(QueryTarget target, const std::string& name)
{
    switch (target)
    {
    case QueryTarget::CPU:
        return std::move(std::make_unique<InternalElapsedTimeInfoCPU>(name));
    case QueryTarget::GPU:
        return std::move(std::make_unique<InternalElapsedTimeInfoGPU>(name));
    default: break;
    }

    return nullptr;
}

void QueryManager::updateEntries(ElapsedTimeMap& entryMap, uint64_t curTimeInMilliseconds)
{
    for (auto& p : entryMap)
    {
        auto& info = p.second;
        assert(!info->expectingEnd);

        ElapsedTimeInfo::Entry entry(info->getElapsedTimeInMicroseconds(m_readQueryBufferIdx), curTimeInMilliseconds);
        info->info[ElapsedTimeInfoType::PER_FRAME].add(entry, m_maxHistoryCount);

        if (info->maxEntry.elapsedTimeInMicroseconds < entry.elapsedTimeInMicroseconds)
            info->maxEntry = entry;

        // Compute a cumulative moving average
        info->averageEntry.elapsedTimeInMicroseconds = (entry.elapsedTimeInMicroseconds + info->historyUpdateFrameCounter * info->averageEntry.elapsedTimeInMicroseconds) / (info->historyUpdateFrameCounter + 1);

        info->historyUpdateTimerInMilliseconds += uint32_t(m_timer.deltaTimeInMilliseconds());
        info->historyUpdateFrameCounter++;

        if (info->historyUpdateTimerInMilliseconds >= info->historyUpdateRateInMilliseconds)
        {
            info->averageEntry.timestampInMilliseconds = curTimeInMilliseconds;
            info->maxEntry.timestampInMilliseconds = curTimeInMilliseconds;
            info->info[ElapsedTimeInfoType::AVERAGE_PER_INTERVAL].add(info->averageEntry, m_maxHistoryCount);
            info->info[ElapsedTimeInfoType::MAX_PER_INTERVAL].add(info->maxEntry, m_maxHistoryCount);

            info->historyUpdateTimerInMilliseconds = 0;
            info->maxEntry = ElapsedTimeInfo::Entry(0, curTimeInMilliseconds);
            info->averageEntry = ElapsedTimeInfo::Entry(0, curTimeInMilliseconds);
            info->historyUpdateFrameCounter = 0;
        }
    }
}


text:
%!TEX root = Programmierparadigmen.tex
\chapter{Programmiertechniken}
\section{Rekursion}
\index{Rekursion|(}

\begin{definition}[rekursive Funktion]\xindex{Funktion!rekursive}%
    Eine Funktion $f: X \rightarrow X$ heißt rekursiv definiert,
    wenn in der Definition der Funktion die Funktion selbst wieder
    steht.
\end{definition}

\begin{beispiel}[rekursive Funktionen]
    \begin{bspenum}
        \item Fibonacci-Funktion:\xindex{Fibonacci-Funktion}
                \begin{align*}
                    fib: \mdn_0 &\rightarrow \mdn_0\\
                    fib(n) &= \begin{cases}
                            n                   &\text{falls } n \leq 1\\
                            fib(n-1) + fib(n-2) &\text{sonst}
                        \end{cases}
                \end{align*}
              Erzeugt die Zahlen $0, 1, 1, 2, 3, 5, 8, 13, \dots$
        \item Fakultät:\xindex{Fakultät}
                \begin{align*}
                    !  &: \mdn_0 \rightarrow \mdn_0\\
                    n! &= \begin{cases}
                            1              &\text{falls } n \leq 1\\
                            n\cdot (n-1)!  &\text{sonst}
                        \end{cases}
                \end{align*}
        \item \label{bsp:binomialkoeffizient} Binomialkoeffizient:\xindex{Binomialkoeffizient}
                \begin{align*}
                    \binom{\cdot}{\cdot}    &: \mdn_0 \times \mdn_0 \rightarrow \mdn_0\\
                            \binom{n}{k}    &= \begin{cases}
                        1                               &\text{falls } k=0 \lor k = n\\
                        \binom{n-1}{k-1}+\binom{n-1}{k} &\text{sonst}
                        \end{cases}
                \end{align*}
    \end{bspenum}
\end{beispiel}

Ein Problem von rekursiven Funktionen in Computerprogrammen ist der
Speicherbedarf. Für jeden rekursiven Aufruf müssen alle lokalen Variablen
der aufrufenden Funktion (\enquote{stack frame}) gespeichert bleiben,
bis der rekursive Aufruf beendet ist. Im Fall der Fibonacci-Funktion
sieht ist der Call-Stack in \cref{fig:fib-callstack} abgebildet.

\begin{figure}[htp]
    \centering
    \includegraphics*[width=0.5\linewidth, keepaspectratio]{figures/fib-callstack.png}
    \caption{Call-Stack der Fibonacci-Funktion}
    \label{fig:fib-callstack}
\end{figure}

\begin{bemerkung}
    Die Anzahl der rekursiven Aufrufe der Fibonacci-Funktion $f_C$ ist:
        \[f_C(n) = \begin{cases}
                        1 &\text{falls } n=0\\
                    2 \cdot fib(n) - 1 &\text{falls } n \geq 1
                    \end{cases}\]
\end{bemerkung}
\begin{beweis}\leavevmode
    \begin{itemize}
        \item Offensichtlich gilt $f_C(0) = 1$
        \item Offensichtlich gilt $f_C(1) = 1 = 2 \cdot fib(1) - 1$
        \item Offensichtlich gilt $f_C(2) = 3 = 2 \cdot fib(2) - 1$
        \item Für $n \geq 3$:
           \begin{align*}
                f_C(n) &= 1 + f_C(n-1) + f_C(n-2)\\
                       &= 1 + (2\cdot fib(n-1)-1) + (2 \cdot fib(n-2)-1)\\
                       &=2\cdot (fib(n-1) + fib(n-2)) - 1\\
                       &=2 \cdot fib(n) - 1
            \end{align*}
    \end{itemize}
\end{beweis}

Mit Hilfe der Formel von Moivre-Binet folgt:

\[f_C \in \mathcal{O} \left (\frac{\varphi^n- \psi^n}{\varphi - \psi} \right) \text{ mit } \varphi := \frac{1+ \sqrt{5}}{2} \text{ und }\psi := 1 - \varphi\]

Dabei ist der Speicherbedarf $\mathcal{O}(n)$. Dieser kann durch
das Benutzen eines Akkumulators signifikant reduziert werden.\todo{TODO}

\begin{definition}[linear rekursive Funktion]\xindex{Funktion!linear rekursive}%
    Eine Funktion heißt linear rekursiv, wenn in jedem Definitionszweig
    der Funktion höchstens ein rekursiver Aufruf vorkommt.
\end{definition}

\begin{definition}[endrekursive Funktion]\xindex{Funktion!endrekursive}\xindex{tail recursive}%
    Eine Funktion heißt endrekursiv, wenn in jedem Definitionszweig
    der Rekursive aufruf am Ende des Ausdrucks steht. Der rekursive
    Aufruf darf also insbesondere nicht in einen anderen Ausdruck
    eingebettet sein.
\end{definition}

Auf Englisch heißen endrekursive Funktionen \textit{tail recursive}.

\begin{beispiel}[Linear- und endrekursive Funktionen]
    \begin{bspenum}
        \item \texttt{fak n = if (n==0) then 1 else (n * fak (n-1))}\\
              ist eine linear rekursive Funkion, aber nicht endrekursiv,
              da nach der Rückgabe von \texttt{fak (n-1)} noch die Multiplikation
              ausgewertet werden muss.
        \item \texttt{fakAcc n acc = if (n==0) then acc else fakAcc (n-1) (n*acc)}\\
              ist eine endrekursive Funktion.
        \item \texttt{fib n = n <= 1 ? n : fib(n-1) + fib (n-2)}\\
              ist weder linear- noch endrekursiv.
    \end{bspenum}
\end{beispiel}

Wenn eine rekursive Funktion nicht terminiert oder wenn

\index{Rekursion|)}
\section{Backtracking}
\index{Backtracking|(}
Unter \textit{Backtracking} versteht man eine Programmiertechnik, die
(eventuell implizit) auf einem Suchbaum arbeitet und mittels Tiefensuche versucht
eine Lösung zu finden.

\begin{beispiel}[Backtracking]
    Probleme, bei deren (vollständigen) Lösung Backtracking verwendet wird, sind:
    \begin{bspenum}
        \item Damenproblem
        \item Springerproblem
        \item Rucksackproblem
    \end{bspenum}
\end{beispiel}
\index{Backtracking|)}

\section{Funktionen höherer Ordnung}
Funktionen höherer Ordnung sind Funktionen, die auf Funktionen arbeiten.
Bekannte Beispiele sind:
\begin{itemize}
    \item \texttt{map(function, list)}\xindex{map}\\
          \texttt{map} wendet \texttt{function} auf jedes einzelne
          Element aus \texttt{list} an.
    \item \texttt{filter(function, list)}\xindex{filter}\\
          \texttt{filter} gibt eine Liste aus Elementen zurück, für
          die \texttt{function} mit \texttt{true} evaluiert.
    \item \texttt{reduce(function, list)}\xindex{reduce}\\
          \texttt{function} ist für zwei Elemente aus \texttt{list}
          definiert und gibt ein Element des gleichen Typs zurück.
          Nun steckt \texttt{reduce} zuerst zwei Elemente aus \texttt{list}
          in \texttt{function}, merkt sich dann das Ergebnis und nimmt
          so lange weitere Elemente aus \texttt{list}, bis jedes
          Element genommen wurde.\\
          Bei \texttt{reduce} ist die Assoziativität wichtig (vgl. \cpageref{bsp:foldl-und-foldr})
\end{itemize}


text:
namespace tfecore {
    class CollapseStrategy {
    private:
        bool collapseHorizontal(std::queue<std::tuple<int, int>> tileQueue, int collapseDirection, std::vector<std::vector<int>> &board);

        bool collapseVertical(std::queue<std::tuple<int, int>> tileQueue, int collapseDirection, std::vector<std::vector<int>> &board);

    public:
        bool collapseDown(std::vector<std::vector<int>> &board);

        bool collapseLeft(std::vector<std::vector<int>> &board);

        bool collapseRight(std::vector<std::vector<int>> &board);

        bool collapseUp(std::vector<std::vector<int>> &board);
    };
}


text:
<!doctype html>

	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">

	<title>Todo App</title>

	<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
	<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css">
	<link rel="stylesheet" href="styles/main.css">

what To-Do {{ numberOfActiveTodos(todos) }}

 0">

No Todos yet!

<button ng-click="showYou=!showYou">Show</button>

					<button ng-click="showYou=!showYou">Hide</button>

Expired To-do's

Expired To-do's

								<input type="checkbox" ng-model="todo.completed" ng-checked="todo.completed" ng-change="archiveTodo(todo)">
									{{ todo.text}}

						<input type="checkbox" ng-model="todo.completed" ng-checked="todo.completed" ng-change="archiveTodo(todo)">
						{{ todo.text }}

Priority: {{ todo.priority }}

Due: {{ todo.due_date }}

						<button ng-click="deleteTodo(todo._id)">Delete</button>

<button ng-click="showMe=!showMe">Show</button>

					<button ng-click="showMe=!showMe">Hide</button>

Completed To-do's

								<input type="checkbox" ng-model="todo.completed" ng-checked="todo.completed" ng-change="archiveTodo(todo)">
									{{ todo.text}}<button ng-click="deleteTodo(todo._id)">Delete</button>

				<form>

						<input type="text" class="form-control input-lg text-center" placeholder="Hire two private investigators. Get them to follow each other." ng-model="formData.text">
						<input style="width:30%;margin-left:265px;" type="text" class="form-control input text-center" placeholder="Priority -> low medium or high" ng-model="formData.priority">
						<input style="width:30%;margin-left:265px;" type="text" class="form-control input-sm text-center" placeholder="Due Date" ng-model="formData.due_date">

					<button type="submit" class="btn btn-default btn-lg" ng-click="createTodo()">Add</button>
				</form>

Created by Chris Palmer

Check out the code