synchronized public void drawProblems(Graphics g, int windowWidth, int windowHeight, int rowHeight, int minRowHeight) {
		System.out.println(">>> DRAW PROBLEMS START <<<");
		long now = System.currentTimeMillis() / 1000;
		try
		{
			String loadImage = null;
			long startLoadTs, endLoadTs;
			double took;
			Color bgColor = config.getBackgroundColor();
			int newLogoHeight = -1, newLogoWidth = -1;

			if (firstDraw) {
				// Let user know what app xe is using
				// load logo
				InputStream is = getClass().getClassLoader().getResourceAsStream("com/vanheusden/CoffeeSaint/footer01.png");
				Image logo = ImageIO.read(is);
				// draw
				g.setColor(Color.WHITE);
				g.fillRect(0, 0, getWidth(), getHeight());
				Toolkit.getDefaultToolkit().sync();
				g.setColor(Color.BLACK);
				Font f = new Font("Arial", Font.PLAIN, 20);
				g.setFont(f);
				int halfX = getWidth() / 2;
				int halfY = getHeight() / 2;
				String msg1 = "CoffeeSaint " + CoffeeSaint.getVersionNr();
				int w1 = getTextWidth(f, msg1);
				g.drawString(msg1, Math.max(0, halfX - w1 / 2), halfY - 25);
				String msg2 = "(C) 2009-2011 by folkert@vanheusden.com";
				int w2 = getTextWidth(f, msg2);
				g.drawString(msg2, Math.max(0, halfX - w2 / 2), halfY + 25);
				f = new Font("Arial", Font.PLAIN, 10);
				g.setFont(f);
				String msg3 = "Please wait...";
				int w3 = getTextWidth(f, msg3);
				g.drawString(msg3, Math.max(0, halfX - w3 / 2), halfY + 50);
				//
				new ImageIcon(logo); //loads the image
				Toolkit.getDefaultToolkit().sync();
				// System.out.println("logo: " + logo.getHeight(null) + ", res: " + getHeight());
				g.drawImage(logo, halfX - logo.getWidth(null) / 2, halfY + 75, this);
				// annoy user :-D
				Thread.sleep(1000);

				firstDraw = false;
			}

			/* block in upper right to inform about load */
			g.setColor(Color.BLUE);
			g.fillRect(windowWidth - rowHeight, 0, rowHeight, rowHeight);

			/* font for all texts */
			String fontName = config.getFontName();
			Font f = new Font(fontName, Font.PLAIN, rowHeight);
			g.setFont(f);

			/* start loading webcam images */
			startLoadTs = System.currentTimeMillis();
			ImageLoadingParameters ilp = coffeeSaint.startLoadingImages(this, windowWidth, g);
			endLoadTs = System.currentTimeMillis();
			double imgLoadTook = (double)(endLoadTs - startLoadTs) / 1000.0;

			/* load & process nagios data */
			if (config.getVerbose())
				prepareRow(g, windowWidth, 0, "Loading Nagios data", 0, "0", true, bgColor, 1.0f, null, false, false, false, - 1);
			Object [] result = CoffeeSaint.loadNagiosData(this, windowWidth, g);
			JavNag javNag = (JavNag)result[0];
			windowMovingParts = new ArrayList<ScrollableContent>();
			if (result[1] != null)
				showCoffeeSaintProblem(new Exception((String)result[1]), g, windowWidth, rowHeight);
			coffeeSaint.collectPerformanceData(javNag);
			coffeeSaint.collectLatencyData(javNag);
			java.util.List<Problem> problems = CoffeeSaint.findProblems(javNag);
			coffeeSaint.learnProblemCount(problems.size());

			/* calculate row height if we need to shrink them due to the number of problems */
			int nRowsWithMaxHeight = windowHeight / rowHeight;
			int curRowHeight = rowHeight, configNRows = config.getNRows();
			if (problems.size() > nRowsWithMaxHeight && minRowHeight > 0) {
				curRowHeight = Math.max(minRowHeight, windowHeight / problems.size());
				configNRows = windowHeight / curRowHeight;
			}
			f = new Font(fontName, Font.PLAIN, curRowHeight);
			g.setFont(f);

			/* determine new size of logo; keep correct aspect ratio */
			if (logo != null)
			{
				int imgLogoWidth  = logo.getWidth(null);
				int imgLogoHeight = logo.getHeight(null);

				newLogoHeight = curRowHeight;
				newLogoWidth  = (int)(((double)newLogoHeight / (double)imgLogoHeight) * imgLogoWidth);
			}

			/* finish loading images */
			startLoadTs = System.currentTimeMillis();
			if (config.getVerbose())
				prepareRow(g, windowWidth, 0, "Loading image(s)", 0, "0", true, bgColor, 1.0f, null, false, false, false, curRowHeight);
			ImageParameters [] imageParameters = coffeeSaint.loadImage(ilp, this, windowWidth, g);
			endLoadTs = System.currentTimeMillis();
			imgLoadTook += (double)(endLoadTs - startLoadTs) / 1000.0;
			statistics.addToTotalImageLoadTime(imgLoadTook);

			movingPartsSemaphore.acquireUninterruptibly();
			bordersParameters = null;
			movingPartsSemaphore.release();

			Calendar rightNow = Calendar.getInstance();

			/* determine background color */
			if (problems.size() == 0 && config.getBrainFileName() != null)
				bgColor = coffeeSaint.predictWithColor(rightNow);
			else if (config.getSetBgColorToState())
			{
				int state = 0;
				for(Problem currentProblem : problems)
					state = Math.max(state, Integer.valueOf(currentProblem.getCurrent_state()));

				if (state == 0)
					bgColor = config.getBackgroundColorOkStatus();
				else if (state == 1)
					bgColor = config.getWarningBgColor();
				else if (state == 2)
					bgColor = config.getCriticalBgColor();
				else
					bgColor = config.getNagiosUnknownBgColor();
			}

			/* clear frame & draw background gradient, if selected */
			g.setColor(bgColor);
			g.fillRect(0, 0, windowWidth, windowHeight);
			Color fadeToBgColor = config.getBackgroundColorFadeTo();
			if (fadeToBgColor != null)
			{
				configureRendered((Graphics2D)g, false);
				int scR = bgColor.getRed(), scG = bgColor.getGreen(), scB = bgColor.getBlue();
				int ftcR = fadeToBgColor.getRed(), ftcG = fadeToBgColor.getGreen(), ftcB = fadeToBgColor.getBlue();
				double stepR = (double)(ftcR - scR) / (double)windowHeight;
				double stepG = (double)(ftcG - scG) / (double)windowHeight;
				double stepB = (double)(ftcB - scB) / (double)windowHeight;

				for(int y = 0; y<windowHeight; y++)
				{
					double pos = y;
					int curR = Math.min(Math.max(0, scR + (int)(pos * stepR)), 255);
					int curG = Math.min(Math.max(0, scG + (int)(pos * stepG)), 255);
					int curB = Math.min(Math.max(0, scB + (int)(pos * stepB)), 255);

					g.setColor(new Color(curR, curG, curB));
					g.drawLine(0, y, windowWidth - 1, y);
				}
				configureRendered((Graphics2D)g, true);
			}

			/* webcam */
			if (imageParameters != null)
				displayImage(imageParameters, problems.size(), g, curRowHeight, config.getAdaptImageSize(), windowWidth, windowHeight);

			/**** start of headers/rows/footers displaying ****/
			int curNRows = 0;
			int dummyNRows = configNRows - ((config.getShowHeader() ? 1 : 0) + (config.getFooter() != null ? 1 : 0));
			int curNColumns = -1;
			if (config.getFlexibleNColumns())
				curNColumns = Math.min(config.getNProblemCols(), (problems.size() + dummyNRows - 1) / dummyNRows);
			else
				curNColumns = config.getNProblemCols();
			curNColumns = Math.max(curNColumns, 1);

			rowData = new RowData[configNRows][curNColumns];

			/* header */
			if (config.getShowHeader())
			{
				String header = coffeeSaint.getScreenHeader(javNag, rightNow, problems.size() > 0);
				String stateForColor = problems.size() == 0 ? "0" : "255";
				if (config.getHeaderAlwaysBGColor())
					stateForColor = "255";
				// if (config.getHeaderTransparency() != 1.0f)
				//	stateForColor = "254";
				int xStart = 0, ww = windowWidth;;
				Position lp = config.getLogoPosition();
				if (logo != null && (lp == Position.UPPER_LEFT || lp == Position.UPPER_RIGHT || lp == Position.LEFT || lp == Position.RIGHT))
				{
					if (lp == Position.UPPER_LEFT || lp == Position.LEFT)
						xStart = newLogoWidth;

					ww -= newLogoWidth;
				}

				if (config.getScrollingHeader())
					windowMovingParts.add(new ScrollableContent(createRowImage(fontName, header + " ", stateForColor, true, bgColor, curRowHeight, null, false), xStart, 0, ww, false, true));
				else
					prepareRow(g, ww, xStart, header, curNRows, stateForColor, true, bgColor, config.getHeaderTransparency(), null, false, false, false, curRowHeight);

				curNRows++;

				RowData headerRow = new RowData(header);
				for(int col=0; col<curNColumns; col++)
					rowData[0][col] = headerRow;
			}

			/* problems */
			int colNr = 0;
			final int rowColWidth = windowWidth / curNColumns;
System.out.println("+ COLUMNS: " + rowColWidth + " " + curNColumns);
			int curMaxNRows = 0;
			for(Problem currentProblem : problems)
			{
				boolean flash = false;
				boolean isFlapping = false;
				String escapeString;
				if (currentProblem.getService() == null) {
					escapeString = config.getHostIssue();
					isFlapping = currentProblem.getHost().getParameter("is_flapping").equals("0") == false;
				}
				else {
					escapeString = config.getServiceIssue();
					isFlapping = currentProblem.getService().getParameter("is_flapping").equals("0") == false;
				}
				Object [] processing = coffeeSaint.processStringWithEscapes(escapeString, javNag, rightNow, currentProblem, problems.size() > 0, true);
				String output = (String)processing[0];
				flash |= (Boolean)processing[1];

				CoffeeSaint.log.add(output);

				// find out if this problem is new and/or not older than something
				if (config.getFlash()) {
					long lsc = Long.valueOf(currentProblem.getHost().getParameter("last_state_change"));
					if (currentProblem.getService() != null) {
						lsc = Math.max(lsc, Long.valueOf(currentProblem.getService().getParameter("last_state_change")));
					}
					long since = now - lsc;
					if (since <= config.getSleepTime() * 1.5) {
						// System.out.println("flash since " + since + " " + currentProblem.getHost().getHostName() + " " + now + "/" + lsc);
						flash = true;
					}
				}

				BufferedImage sparkLine = null;
				int sparkLineWidth = config.getSparkLineWidth();
				if (sparkLineWidth > 0)
				{
					Service currentService = currentProblem.getService();
					sparkLine = coffeeSaint.getSparkLine(currentProblem.getHost().getHostName(), currentService != null ? currentService.getServiceName() : null, sparkLineWidth, curRowHeight - (config.getRowBorder()?1:0), false);
				}

				int xStart = rowColWidth * colNr;

				movingPartsSemaphore.acquireUninterruptibly();
				prepareRow(g, rowColWidth, xStart, output, curNRows, currentProblem.getCurrent_state(), currentProblem.getHard(), bgColor, config.getTransparency(), sparkLine, config.getScrollIfNotFit(), isFlapping, flash, curRowHeight);
				movingPartsSemaphore.release();

				rowData[curNRows][colNr] = new RowData(currentProblem.getHost(), currentProblem.getService());

				curNRows++;

				if (curNRows == (configNRows - (config.getFooter() != null ? 1 : 0)))
				{
					if (curNRows > curMaxNRows)
						curMaxNRows = curNRows;
					curNRows = config.getShowHeader() ? 1 : 0;
					colNr++;
					if (colNr == curNColumns)
						break;
				}
			}
			if (curMaxNRows == 0)
				curMaxNRows = curNRows;

			/* footer */
			if (config.getFooter() != null)
			{
				Object [] processedResult = coffeeSaint.processStringWithEscapes(config.getFooter(), javNag, rightNow, null, problems.size() > 0, true);
				String footer = (String)processedResult[0];
				boolean flash = (Boolean)processedResult[1];
				String stateForColor = problems.size() == 0 ? "0" : "255";
				int row = configNRows - 1;
				if (config.getHeaderAlwaysBGColor())
					stateForColor = "255";

				int xStart = 0, ww = windowWidth;;
				if (logo != null && config.getLogoPosition() == Position.LOWER_LEFT || config.getLogoPosition() == Position.LOWER_RIGHT)
				{
					if (config.getLogoPosition() == Position.LOWER_LEFT)
						xStart = newLogoWidth;

					ww -= newLogoWidth;
				}

				if (config.getScrollingFooter())
				{
					int y = config.getShowHeader() ? config.getUpperRowBorderHeight(): 0;
					y += row * curRowHeight;
					windowMovingParts.add(new ScrollableContent(createRowImage(fontName, footer + " ", stateForColor, true, bgColor, curRowHeight, null, false), xStart, y, ww, flash, true));
				}
				else
					prepareRow(g, ww, xStart, footer, row, stateForColor, true, bgColor, config.getHeaderTransparency(), null, false, false, flash, curRowHeight);
			}

			/* no problems message */
			if (problems.size() == 0)
			{
				String okMsg = config.getNoProblemsText();
				if (okMsg != null && config.getNoProblemsTextPosition() != Position.NONE)
				{
					int x = 0;
					int y = 0;
					if (config.getShowHeader())
						y = 1;

					Object [] resultOk = coffeeSaint.processStringWithEscapes(okMsg, javNag, rightNow, null, false, true);
					okMsg = (String)resultOk[0];

					BufferedImage allFineMsg = createRowImage(config.getFontName(), okMsg + " ", "0", true, config.getBackgroundColor(), curRowHeight, null, false);
					int width = allFineMsg.getWidth();

					switch(config.getNoProblemsTextPosition())
					{
						case UPPER_LEFT:
							x = 0;
							break;

						case UPPER_RIGHT:
							x = Math.max(0, windowWidth - width);
							break;

						case LOWER_LEFT:
							x = 0;
							y = (windowHeight / curRowHeight) - 1;
							break;

						case LOWER_RIGHT:
							y = (windowHeight / curRowHeight) - 1;
							x = Math.max(0, windowWidth - width);
							break;

						case CENTER:
							y = (windowHeight / curRowHeight) / 2;
							x = Math.max(0, (windowWidth / 2) - (width / 2));
							break;
					}

					if (config.getNoProblemsTextBg())
						prepareRow(g, width, x, okMsg, y, "0", true, bgColor, 1.0f, null, false, false, false, curRowHeight);
					else
						prepareRow(g, width, x, okMsg, y, "254", true, bgColor, 1.0f, null, false, false, false, curRowHeight);
				}
			}

			/* draw logo */
			if (logo != null)
			{
				int plotX = -1, plotY = 0;
				Position logoPosition = config.getLogoPosition();

				if (logoPosition == Position.UPPER_LEFT)
					plotX = 0;
				else if (logoPosition == Position.UPPER_RIGHT)
					plotX = Math.max(0, windowWidth - newLogoWidth);
				else if (logoPosition == Position.LOWER_LEFT)
				{
					plotX = 0;
					plotY = (configNRows - 1) * curRowHeight;
				}
				else if (logoPosition == Position.LOWER_RIGHT)
				{
					plotX = Math.max(0, windowWidth - newLogoWidth);
					plotY = (configNRows - 1) * curRowHeight;
				}
				else
					throw new Exception("Unknown logo position: " + logoPosition);

				g.drawImage(logo, plotX, plotY, newLogoWidth, newLogoHeight, this);
			}

			/* draw borders */
			if (config.getRowBorder())
			{
				bordersParameters = new BordersParameters(problems.size(), curNColumns, windowWidth, curRowHeight, configNRows);
				movingPartsSemaphore.acquireUninterruptibly();
				if (windowMovingParts.size() == 0)
					drawBorders((Graphics2D)g, bordersParameters);
				movingPartsSemaphore.release();
			}

			boolean tooOld = false;
			long maxAge = config.getMaxCheckAge();
			if (maxAge != -1) {
				String nagiosHostDown = javNag.findHostDown(maxAge);
				if (nagiosHostDown != null) {
					tooOld = true;
					prepareRow(g, windowWidth, 0, "NAGIOS " + nagiosHostDown + " STOPPED RUNNING!", configNRows / 2, "2", true, Color.RED, 1.0f, null, false, false, config.getFlash(), curRowHeight);
				}
			}

			/* play sound */
			if (problems.size() > 0 || tooOld)
			{
				if (lastState == false || tooOld)
				{
					if (config.getProblemSound() != null)
					{
						CoffeeSaint.log.add("Playing sound " + config.getProblemSound());
						new PlayWav(config.getProblemSound());
					}

					if (config.getExec() != null)
					{
						CoffeeSaint.log.add("Invoking " + config.getExec());
						Runtime.getRuntime().exec(config.getExec());
					}
				}

				lastState = true;
			}
			else
			{
				lastState = false;
			}

			CoffeeSaint.log.add("Memory usage: " + ((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024)) + "MB");
		}
		catch(UnsupportedAudioFileException uafe)
		{
			g.setColor(Color.RED);
			g.fillRect(windowWidth - rowHeight, 0, rowHeight, rowHeight);
			windowMovingParts = new ArrayList<ScrollableContent>();
			prepareRow(g, windowWidth, 0, "Audio sample unsupported format (" + uafe + ")", config.getNRows() - 1, "2", true, Color.GRAY, 1.0f, null, true, false, config.getFlash(), -1);
		}
		catch(Exception e)
		{
			statistics.incExceptions();
			CoffeeSaint.showException(e);
			windowMovingParts = new ArrayList<ScrollableContent>();

			if (g != null)
				showCoffeeSaintProblem(e, g, windowWidth, rowHeight);
		}
		System.out.println(">>> DRAW PROBLEMS END <<<");
	}