// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/style/style_core.h"

#include "ui/effects/animation_value.h"
#include "ui/painter.h"
#include "styles/style_basic.h"
#include "styles/palette.h"

#include <QtGui/QPainter>

#include <rpl/event_stream.h>
#include <rpl/variable.h>

namespace style {
namespace internal {
namespace {

constexpr auto kMinContrastAlpha = 64;
constexpr auto kMinContrastDistance = 64 * 64 * 4;
constexpr auto kContrastDeltaL = 64;

auto PaletteChanges = rpl::event_stream<>();
auto PaletteVersion = 0;
auto ShortAnimationRunning = rpl::variable<bool>(false);
auto RunningShortAnimations = 0;

[[nodiscard]] std::vector<internal::ModuleBase*> &StyleModules() {
	static auto result = std::vector<internal::ModuleBase*>();
	return result;
}

void StartModules(int scale) {
	for (const auto module : StyleModules()) {
		module->start(scale);
	}
}

} // namespace

void registerModule(ModuleBase *module) {
	StyleModules().push_back(module);
}

void StartShortAnimation() {
	if (++RunningShortAnimations == 1) {
		ShortAnimationRunning = true;
	}
}

void StopShortAnimation() {
	if (--RunningShortAnimations == 0) {
		ShortAnimationRunning = false;
	}
}

} // namespace internal

void StartManager(int scale) {
	internal::RegisterFontFamily("Open Sans");
	internal::StartModules(scale);
}

void StopManager() {
	internal::DestroyFonts();
	internal::DestroyIcons();
}

rpl::producer<> PaletteChanged() {
	return internal::PaletteChanges.events();
}

int PaletteVersion() {
	return internal::PaletteVersion;
}

void NotifyPaletteChanged() {
	++internal::PaletteVersion;
	internal::PaletteChanges.fire({});
}

rpl::producer<bool> ShortAnimationPlaying() {
	return internal::ShortAnimationRunning.value();
}

void colorizeImage(
		const QImage &src,
		const QColor &color,
		not_null<QImage*> outResult,
		QRect srcRect,
		QPoint dstPoint,
		bool useAlpha) {
	// In background_box ColorizePattern we use the fact that
	// colorizeImage takes only first byte of the mask, so it
	// could be used for wallpaper patterns, which have values
	// in ranges (0, 0, 0, 0) to (0, 0, 0, 255) (only 'alpha').
	if (srcRect.isNull()) {
		srcRect = src.rect();
	} else {
		Assert(src.rect().contains(srcRect));
	}
	auto width = srcRect.width();
	auto height = srcRect.height();
	Assert(outResult->rect().contains(QRect(dstPoint, srcRect.size())));
	outResult->detach();

	auto pattern = anim::shifted(color);

	constexpr auto resultIntsPerPixel = 1;
	auto resultIntsPerLine = (outResult->bytesPerLine() >> 2);
	auto resultIntsAdded = resultIntsPerLine - width * resultIntsPerPixel;
	auto resultInts = reinterpret_cast<uint32*>(outResult->bits())
		+ (dstPoint.y() * resultIntsPerLine)
		+ (dstPoint.x() * resultIntsPerPixel);
	Assert(resultIntsAdded >= 0);
	Assert(outResult->depth()
		== static_cast<int>((resultIntsPerPixel * sizeof(uint32)) << 3));
	Assert(outResult->bytesPerLine() == (resultIntsPerLine << 2));

	auto maskBytesPerPixel = (src.depth() >> 3);
	auto maskBytesPerLine = src.bytesPerLine();
	auto maskBytesAdded = maskBytesPerLine - width * maskBytesPerPixel;
	auto maskBytes = src.constBits()
		+ (srcRect.y() * maskBytesPerLine)
		+ (srcRect.x() * maskBytesPerPixel)
		+ (useAlpha ? 3 : 0);
	Assert(maskBytesAdded >= 0);
	Assert(src.depth() == (maskBytesPerPixel << 3));
	for (int y = 0; y != height; ++y) {
		for (int x = 0; x != width; ++x) {
			auto maskOpacity = static_cast<anim::ShiftedMultiplier>(*maskBytes) + 1;
			*resultInts = anim::unshifted(pattern * maskOpacity);
			maskBytes += maskBytesPerPixel;
			resultInts += resultIntsPerPixel;
		}
		maskBytes += maskBytesAdded;
		resultInts += resultIntsAdded;
	}

	outResult->setDevicePixelRatio(src.devicePixelRatio());
}

QImage TransparentPlaceholder() {
	const auto size = st::transparentPlaceholderSize * DevicePixelRatio();
	auto result = QImage(
		2 * size, 2 * size,
		QImage::Format_ARGB32_Premultiplied);
	result.fill(st::mediaviewTransparentBg->c);
	{
		QPainter p(&result);
		p.fillRect(0, size, size, size, st::mediaviewTransparentFg);
		p.fillRect(size, 0, size, size, st::mediaviewTransparentFg);
	}
	result.setDevicePixelRatio(DevicePixelRatio());
	return result;
}

namespace internal {

QImage createCircleMask(int size, QColor bg, QColor fg) {
	int realSize = size * DevicePixelRatio();
	auto result = QImage(realSize, realSize, QImage::Format::Format_Grayscale8);
	{
		QPainter p(&result);
		PainterHighQualityEnabler hq(p);

		p.fillRect(0, 0, realSize, realSize, bg);
		p.setPen(Qt::NoPen);
		p.setBrush(fg);
		p.drawEllipse(0, 0, realSize, realSize);
	}
	result.setDevicePixelRatio(DevicePixelRatio());
	return result;
}

[[nodiscard]] bool GoodForContrast(const QColor &c1, const QColor &c2) {
	auto r1 = 0;
	auto g1 = 0;
	auto b1 = 0;
	auto r2 = 0;
	auto g2 = 0;
	auto b2 = 0;
	c1.getRgb(&r1, &g1, &b1);
	c2.getRgb(&r2, &g2, &b2);
	const auto rMean = (r1 + r2) / 2;
	const auto r = r1 - r2;
	const auto g = g1 - g2;
	const auto b = b1 - b2;
	const auto distance = (((512 + rMean) * r * r) >> 8)
		+ (4 * g * g)
		+ (((767 - rMean) * b * b) >> 8);
	return (distance > kMinContrastDistance);
}

QColor EnsureContrast(const QColor &over, const QColor &under) {
	auto overH = 0;
	auto overS = 0;
	auto overL = 0;
	auto overA = 0;
	auto underH = 0;
	auto underS = 0;
	auto underL = 0;
	over.getHsl(&overH, &overS, &overL, &overA);
	under.getHsl(&underH, &underS, &underL);
	const auto good = GoodForContrast(over, under);
	if (overA >= kMinContrastAlpha && good) {
		return over;
	}
	const auto newA = std::max(overA, kMinContrastAlpha);
	const auto newL = (overL > underL && overL + kContrastDeltaL <= 255)
		? (overL + kContrastDeltaL)
		: (overL < underL && overL - kContrastDeltaL >= 0)
		? (overL - kContrastDeltaL)
		: (underL > 128)
		? (underL - kContrastDeltaL)
		: (underL + kContrastDeltaL);
	return QColor::fromHsl(overH, overS, newL, newA).toRgb();
}

void EnsureContrast(ColorData &over, const ColorData &under) {
	const auto good = EnsureContrast(over.c, under.c);
	if (over.c != good) {
		over.c = good;
		over.p = QPen(good);
		over.b = QBrush(good);
	}
}

} // namespace internal
} // namespace style
