// Copyright (c) 2005-2010, Rodrigo Braz Monteiro
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//   * Neither the name of the Aegisub Group nor the names of its contributors
//     may be used to endorse or promote products derived from this software
//     without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Aegisub Project http://www.aegisub.org/

/// @file video_display.cpp
/// @brief Control displaying a video frame obtained from the video context
/// @ingroup video main_ui
///

#include "video_display.h"

#include "libaegisub/log.h"

#include "ass_file.h"
#include "async_video_provider.h"
#include "command/command.h"
#include "compat.h"
#include "format.h"
#include "include/aegisub/context.h"
#include "include/aegisub/hotkey.h"
#include "include/aegisub/menu.h"
#include "options.h"
#include "project.h"
#include "spline_curve.h"
#include "utils.h"
#include "video_out_gl.h"
#include "video_controller.h"
#include "visual_tool.h"


#include <algorithm>
#include <wx/combobox.h>
#include <wx/menu.h>
#include <wx/textctrl.h>
#include <wx/toolbar.h>

#ifdef HAVE_OPENGL_GL_H
#include <OpenGL/gl.h>
#else
#include <GL/gl.h>
#endif

namespace {

/// Attribute list for gl canvases; set the canvases to doublebuffered rgba with an 8 bit stencil buffer
wxGLAttributes buildGLAttributes() {
	wxGLAttributes attrs;
	attrs.PlatformDefaults().RGBA().MinRGBA(8, 8, 8, 0).DoubleBuffer().Stencil(8).EndList();
	return attrs;
}

}

/// An OpenGL error occurred while uploading or displaying a frame
class OpenGlException final : public agi::Exception {
public:
	OpenGlException(const char *func, int err)
	: agi::Exception(agi::format("%s failed with error code %d", func, err))
	{ }
};

#define E(cmd) cmd; if (GLenum err = glGetError()) throw OpenGlException(#cmd, err)

enum {
	SCALE_VIDEO,
	SCALE_VIDEO_REV,
	ZOOM_VIDEO,
	ZOOM_VIDEO_REV,
	PAN_VIDEO,
	PAN_VIDEO_SWAP,
	NOTHING,
};

VideoDisplay::VideoDisplay(wxToolBar *toolbar, bool freeSize, wxComboBox *zoomBox, wxWindow *parent, agi::Context *c)
: wxGLCanvas(parent, buildGLAttributes())
, autohideTools(OPT_GET("Tool/Visual/Autohide"))
, con(c)
, windowZoomValue(OPT_GET("Video/Default Zoom")->GetInt() * .125 + .125)
, toolBar(toolbar)
, zoomBox(zoomBox)
, freeSize(freeSize)
, scale_factor(GetContentScaleFactor())
{
	zoomBox->SetValue(fmt_wx("%g%%", windowZoomValue * 100.));
	zoomBox->Bind(wxEVT_COMBOBOX, &VideoDisplay::SetWindowZoomFromBox, this);
	zoomBox->Bind(wxEVT_TEXT_ENTER, &VideoDisplay::SetWindowZoomFromBoxText, this);

	con->videoController->Bind(EVT_FRAME_READY, &VideoDisplay::UploadFrameData, this);
	connections = agi::signal::make_vector({
		con->project->AddVideoProviderListener([this] (AsyncVideoProvider *provider) {
			if (!provider) ResetContentZoom();
			FitClientSizeToVideo();
		}),
		con->videoController->AddARChangeListener(&VideoDisplay::FitClientSizeToVideo, this),
	});

	if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE)) {
		LOG_E("video/display") << "Failed to enable touch events.";
	}

	Bind(wxEVT_PAINT, std::bind(&VideoDisplay::Render, this));
	Bind(wxEVT_SIZE, &VideoDisplay::OnSizeEvent, this);
	Bind(wxEVT_CONTEXT_MENU, &VideoDisplay::OnContextMenu, this);
	Bind(wxEVT_ENTER_WINDOW, &VideoDisplay::OnMouseEvent, this);
	Bind(wxEVT_CHAR_HOOK, &VideoDisplay::OnKeyDown, this);
	Bind(wxEVT_LEAVE_WINDOW, &VideoDisplay::OnMouseLeave, this);
	Bind(wxEVT_LEFT_DCLICK, &VideoDisplay::OnMouseEvent, this);
	Bind(wxEVT_LEFT_DOWN, &VideoDisplay::OnMouseEvent, this);
	Bind(wxEVT_LEFT_UP, &VideoDisplay::OnMouseEvent, this);
	Bind(wxEVT_MIDDLE_DOWN, &VideoDisplay::OnMouseEvent, this);
	Bind(wxEVT_MIDDLE_UP, &VideoDisplay::OnMouseEvent, this);
	Bind(wxEVT_MOTION, &VideoDisplay::OnMouseEvent, this);
	Bind(wxEVT_MOUSEWHEEL, &VideoDisplay::OnMouseWheel, this);
	Bind(wxEVT_GESTURE_ZOOM, &VideoDisplay::OnGestureZoom, this);

	Bind(wxEVT_DPI_CHANGED, [this] (wxDPIChangedEvent &e) {
		double new_zoom = windowZoomValue * GetContentScaleFactor() / scale_factor;
		scale_factor = GetContentScaleFactor();
		SetWindowZoom(new_zoom);
		e.Skip();
	});

	SetCursor(wxNullCursor);

	c->videoDisplay = this;

	con->videoController->JumpToFrame(con->videoController->GetFrameN());

	SetLayoutDirection(wxLayout_LeftToRight);

	UpdateViewportSize(false, wxSize(1, 1));
}

VideoDisplay::~VideoDisplay () {
	Unload();
	con->videoController->Unbind(EVT_FRAME_READY, &VideoDisplay::UploadFrameData, this);
}

bool VideoDisplay::InitContext() {
	if (!IsShownOnScreen())
		return false;

	// If this display is in a minimized detached dialog IsShownOnScreen will
	// return true, but the client size is guaranteed to be 0
	if (GetClientSize() == wxSize(0, 0))
		return false;

	if (!glContext)
		glContext = std::make_unique<wxGLContext>(this);

	SetCurrent(*glContext);
	return true;
}

void VideoDisplay::UploadFrameData(FrameReadyEvent &evt) {
	pending_frame = evt.frame;
	Render();
}

void VideoDisplay::Render() try {
	if (!con->project->VideoProvider() || !InitContext() || (!videoOut && !pending_frame))
		return;

	if (!videoOut)
		videoOut = std::make_unique<VideoOutGL>();

	if (!tool)
		cmd::call("video/tool/cross", con);

	try {
		if (pending_frame) {
			videoOut->UploadFrameData(*pending_frame);
			pending_frame.reset();
		}
	}
	catch (const VideoOutInitException& err) {
		wxLogError(
			fmt_tl("Failed to initialize video display. Closing other running programs and updating your video card drivers may fix this.\nError message reported: %s",
			err.GetMessage()));
		con->project->CloseVideo();
		return;
	}
	catch (const VideoOutRenderException& err) {
		wxLogError(
			fmt_tl("Could not upload video frame to graphics card.\nError message reported: %s",
			err.GetMessage()));
		return;
	}

	if (!content_height || !content_width)
		PositionVideo();

	wxSize client_size = GetClientSize();
	client_size = wxSize(std::max(1, client_size.GetWidth()), std::max(1, client_size.GetHeight()));

	videoOut->Render(client_size.GetWidth() * scale_factor, client_size.GetHeight() * scale_factor,
		content_left, content_bottom, content_width, content_height);
	E(glViewport(0, 0, client_size.GetWidth() * scale_factor, client_size.GetHeight() * scale_factor));

	E(glMatrixMode(GL_PROJECTION));
	E(glLoadIdentity());
	E(glOrtho(0.0f, client_size.GetWidth(), client_size.GetHeight(), 0.0f, -1000.0f, 1000.0f));

	if (OPT_GET("Video/Overscan Mask")->GetBool()) {
		double ar = con->videoController->GetAspectRatioValue();

		// Based on BBC's guidelines: http://www.bbc.co.uk/guidelines/dq/pdf/tv/tv_standards_london.pdf
		// 16:9 or wider
		if (ar > 1.75) {
			DrawOverscanMask(.1f, .05f);
			DrawOverscanMask(0.035f, 0.035f);
		}
		// Less wide than 16:9 (use 4:3 standard)
		else {
			DrawOverscanMask(.067f, .05f);
			DrawOverscanMask(0.033f, 0.035f);
		}
	}

	if ((mouse_pos || !autohideTools->GetBool()) && tool)
		tool->Draw();

	SwapBuffers();
}
catch (const agi::Exception &err) {
	wxLogError(
		fmt_tl("An error occurred trying to render the video frame on the screen.\nError message reported: %s",
		err.GetMessage()));
	con->project->CloseVideo();
}

void VideoDisplay::DrawOverscanMask(float horizontal_percent, float vertical_percent) const {
	Vector2D v = Vector2D(content_width, content_height) / scale_factor;
	Vector2D size = Vector2D(horizontal_percent, vertical_percent) * v;

	// Clockwise from top-left
	Vector2D corners[] = {
		size,
		Vector2D(content_width / scale_factor - size.X(), size),
		v - size,
		Vector2D(size, content_height  / scale_factor - size.Y())
	};

	// Shift to compensate for black bars
	Vector2D pos = Vector2D(content_left, content_top) / scale_factor;
	for (auto& corner : corners)
		corner = corner + pos;

	int count = 0;
	std::vector<float> points;
	for (size_t i = 0; i < 4; ++i) {
		size_t prev = (i + 3) % 4;
		size_t next = (i + 1) % 4;
		count += SplineCurve(
				(corners[prev] + corners[i] * 4) / 5,
				corners[i], corners[i],
				(corners[next] + corners[i] * 4) / 5)
			.GetPoints(points);
	}

	OpenGLWrapper gl;
	gl.SetFillColour(wxColor(30, 70, 200), .5f);
	gl.SetLineColour(*wxBLACK, 0, 1);

	std::vector<int> vstart(1, 0);
	std::vector<int> vcount(1, count);
	gl.DrawMultiPolygon(points, vstart, vcount, pos, v, true);
}

void VideoDisplay::UpdateViewportSize(bool rescalePan, wxSize newSize) {
	// In free size mode, we ignore the argument and use the client size
	// to avoid the viewport and client sizes getting out of sync
	if (freeSize)
		newSize = GetClientSize() * scale_factor;
	assert(newSize != wxDefaultSize);
	if (newSize.GetWidth() < 1) newSize.SetWidth(1);
	if (newSize.GetHeight() < 1) newSize.SetHeight(1);
	if (rescalePan && viewportSize.GetHeight() >= 1) {
		double ratio = double(newSize.GetHeight()) / viewportSize.GetHeight();
		pan_x *= ratio;
		pan_y *= ratio;
	}
	viewportSize = newSize;
}

void VideoDisplay::PositionVideo(bool preserveContentSize) {
	auto provider = con->project->VideoProvider();
	if (!provider || !IsShownOnScreen()) return;

	int old_content_height = content_height;

	content_width = viewportSize.GetWidth();
	content_height = viewportSize.GetHeight();

	if (freeSize) {
		// Adjust aspect ratio if necessary
		int vidW = provider->GetWidth();
		int vidH = provider->GetHeight();

		AspectRatio arType = con->videoController->GetAspectRatioType();
		double displayAr = double(content_width) / content_height;
		double videoAr = arType == AspectRatio::Default ? double(vidW) / vidH : con->videoController->GetAspectRatioValue();

		// Window is wider than video, blackbox left/right
		if (displayAr - videoAr > 0.01) {
			content_width = content_height * videoAr;
		}
		// Video is wider than window, blackbox top/bottom
		else if (videoAr - displayAr > 0.01) {
			content_height = content_width / videoAr;
		}

		// Update windowZoomValue
		// We must use content_height before content zoom has been applied to it
		windowZoomValue = double(content_height) / vidH;
		zoomBox->ChangeValue(fmt_wx("%g%%", windowZoomValue * 100.));
		con->ass->Properties.video_zoom = windowZoomValue;
	}

	if (preserveContentSize)
		contentZoomValue = double(old_content_height) / content_height;

	// Apply content zoom
	content_width *= contentZoomValue;
	content_height *= contentZoomValue;

	// Center video in viewport
	double content_left_exact = double(viewportSize.GetWidth() - content_width) / 2;
	double content_top_exact = double(viewportSize.GetHeight() - content_height) / 2;

	// Don't allow panning too far out of bounds
	double max_pan_x = 0.5 * content_width + 0.4 * viewportSize.GetWidth();
	double max_pan_y = 0.5 * content_height + 0.4 * viewportSize.GetHeight();
	pan_x = mid(-max_pan_x, pan_x, max_pan_x);
	pan_y = mid(-max_pan_y, pan_y, max_pan_y);

	// Apply panning
	content_left_exact += pan_x;
	content_top_exact += pan_y;

	content_left = std::round(content_left_exact);
	content_top = std::round(content_top_exact);
	content_bottom = GetClientSize().GetHeight() * scale_factor - content_height - content_top;

	if (tool) {
		wxSize client_size = GetClientSize();
		tool->SetCanvasSize(client_size.GetWidth(), client_size.GetHeight());
		tool->SetDisplayArea(content_left / scale_factor, content_top / scale_factor,
		                     content_width / scale_factor, content_height / scale_factor);
	}

	Render();
}

void VideoDisplay::FitClientSizeToVideo() {
	auto provider = con->project->VideoProvider();

	if (!provider || !IsShownOnScreen()) return;

	wxSize newViewportSize(provider->GetWidth(), provider->GetHeight());
	newViewportSize *= windowZoomValue;
	if (con->videoController->GetAspectRatioType() != AspectRatio::Default)
		newViewportSize.SetWidth(newViewportSize.GetHeight() * con->videoController->GetAspectRatioValue());

	wxEventBlocker blocker(this);
	if (freeSize) {
		wxWindow *top = GetParent();
		while (!top->IsTopLevel()) top = top->GetParent();

		wxSize cs = GetClientSize();
		wxSize oldSize = top->GetSize();
		top->SetSize(top->GetSize() + newViewportSize / scale_factor - cs);
		SetClientSize(cs + top->GetSize() - oldSize);
	}
	else {
		SetMinClientSize(newViewportSize / scale_factor);
		SetMaxClientSize(newViewportSize / scale_factor);

		GetGrandParent()->Layout();
	}

	UpdateViewportSize(true, newViewportSize); // must be called after setting the client size
	PositionVideo();
}

void VideoDisplay::OnSizeEvent(wxSizeEvent &) {
	bool preserveContentSize = freeSize && IsContentZoomActive();
	if (freeSize) UpdateViewportSize(false);
	PositionVideo(preserveContentSize);
}

void VideoDisplay::OnMouseEvent(wxMouseEvent& event) {
	if (event.ButtonDown())
		SetFocus();

	if (event.Dragging() && event.MiddleIsDown()) {
		Pan(Vector2D(event.GetPosition()) - last_mouse_pos);
	}

	last_mouse_pos = mouse_pos = event.GetPosition();

	if (tool)
		tool->OnMouseEvent(event);
}

void VideoDisplay::OnMouseLeave(wxMouseEvent& event) {
	mouse_pos = Vector2D();
	if (tool)
		tool->OnMouseEvent(event);
}

void VideoDisplay::OnMouseWheel(wxMouseEvent& event) {
	if (int wheel = event.GetWheelRotation()) {
		if (ForwardMouseWheelEvent(this, event)) {
			const char *opt = "Scroll Action";
			if (event.CmdDown() && event.ShiftDown()) {
				return;
			} else if (event.CmdDown()) {
				opt = "Ctrl Scroll Action";
			} else if (event.ShiftDown()) {
				opt = "Shift Scroll Action";
			}

			int action = OPT_GET(std::string("Video/") + opt)->GetInt();
			int dir = 1;
			bool swap = false;
			switch (action) {
				case SCALE_VIDEO_REV:
					dir = -1;
					[[fallthrough]];
				case SCALE_VIDEO:
					SetWindowZoom(windowZoomValue + dir * .125 * (wheel / event.GetWheelDelta()));
					break;

				case ZOOM_VIDEO_REV:
					dir = -1;
					[[fallthrough]];
				case ZOOM_VIDEO:
					{
						double newZoomValue = contentZoomValue * (1 + dir * 0.125 * wheel / event.GetWheelDelta());
						wxPoint scaled_position = event.GetPosition() * scale_factor;
						ZoomAndPan(newZoomValue, GetZoomAnchorPoint(scaled_position), scaled_position);
					}
					break;

				case PAN_VIDEO_SWAP:
					swap = true;
					[[fallthrough]];
				case PAN_VIDEO:
					{
						double distance = 5 * static_cast<double>(wheel) / event.GetWheelDelta();
						Vector2D pan = event.GetWheelAxis() == wxMOUSE_WHEEL_HORIZONTAL ? Vector2D(-distance, 0) : Vector2D(0, distance);

						Pan(swap ? Vector2D(pan.Y(), pan.X()) : pan);
					}
					break;

				case NOTHING:
				default:
					break;
			}
		}
	}
}

void VideoDisplay::OnGestureZoom(wxZoomGestureEvent& event) {
	if (!isZoomGestureActive && !event.IsGestureStart()) {
		// On wxGTK, right-clicking generates a false GestureEnd event (without any preceding GestureStart event).
		// See https://github.com/wxWidgets/wxWidgets/issues/26211
		// Getting any other event before a GestureStart event is not valid, so ignore them.
		LOG_W("video/display") << "Ignoring zoom gesture event when gesture isn't active"
			<< " (ZoomFactor " << event.GetZoomFactor() << ", Position " << event.GetPosition().x << " " << event.GetPosition().y
			<< (event.IsGestureEnd() ? ", GestureEnd)" : ")");
		return;
	}

	wxPoint scaled_position = event.GetPosition() * scale_factor;
	if (event.IsGestureStart()) {
		isZoomGestureActive = true;
		contentZoomAtGestureStart = contentZoomValue;
		zoomGestureAnchorPoint = GetZoomAnchorPoint(scaled_position);
	} else if (event.IsGestureEnd()) {
		isZoomGestureActive = false;
	}
	ZoomAndPan(contentZoomAtGestureStart * event.GetZoomFactor(), zoomGestureAnchorPoint, scaled_position);
}

void VideoDisplay::Pan(Vector2D delta) {
	pan_x += delta.X() * scale_factor;
	pan_y += delta.Y() * scale_factor;
	PositionVideo();
}

void VideoDisplay::OnContextMenu(wxContextMenuEvent&) {
	if (!context_menu) context_menu = menu::GetMenu("video_context", (wxID_HIGHEST + 1) + 9000, con);
	SetCursor(wxNullCursor);
	menu::OpenPopupMenu(context_menu.get(), this);
}

void VideoDisplay::OnKeyDown(wxKeyEvent &event) {
	hotkey::check("Video", con, event);
}

void VideoDisplay::SetWindowZoom(double value) {
	if (value == 0) return;
	windowZoomValue = std::max(value, .125);
	size_t selIndex = windowZoomValue / .125 - 1;
	if (selIndex < zoomBox->GetCount())
		zoomBox->SetSelection(selIndex);
	zoomBox->ChangeValue(fmt_wx("%g%%", windowZoomValue * 100.));
	con->ass->Properties.video_zoom = windowZoomValue;
	FitClientSizeToVideo();
}

Vector2D VideoDisplay::GetZoomAnchorPoint(wxPoint position) {
	// See the doc comment of both this function and ZoomAndPan for an explanation of what the anchor point is.
	//
	// We represent the anchor point as an offset in logical pixels from the video center at `contentZoomValue=1.0`.
	//
	// When neither panning nor content zoom is active, the video center is the same as the viewport center,
	// so we can derive client position from the anchor point by simply adding the viewport center to it.
	// The viewport center is at `viewportSize / 2`, so the formula is
	//
	//     position = viewportSize / 2 + anchorPoint
	//
	// Panning shifts the video center by `pan`, so we have to add that to the viewport center:
	//
	//     position = viewportSize / 2 + pan + anchorPoint
	//
	// Finally, to apply scaling, we need to multiply the offset from the video center by the zoom value, so the final formula is
	//
	//     position = viewportSize / 2 + pan + anchorPoint * contentZoomValue
	//
	// Now, to obtain the anchor point from the position, we have to invert the formula.
	Vector2D viewportCenter = Vector2D(viewportSize.GetWidth(), viewportSize.GetHeight()) / 2;
	return (Vector2D(position) - viewportCenter - Vector2D(pan_x, pan_y)) / contentZoomValue;
}

void VideoDisplay::ZoomAndPan(double newZoomValue, Vector2D anchorPoint, wxPoint newPosition) {
	newZoomValue = std::max(0.125, std::min(10.0, newZoomValue));

	// Compute a pan value to maintain the formula derived above
	Vector2D viewportCenter = Vector2D(viewportSize.GetWidth(), viewportSize.GetHeight()) / 2;
	Vector2D newPan = Vector2D(newPosition) - viewportCenter - anchorPoint * newZoomValue;

	pan_x = newPan.X();
	pan_y = newPan.Y();
	contentZoomValue = newZoomValue;

	PositionVideo();
}

void VideoDisplay::ResetContentZoom() {
	pan_x = 0;
	pan_y = 0;
	contentZoomValue = 1;
	PositionVideo();
}

void VideoDisplay::SetWindowZoomFromBox(wxCommandEvent &) {
	int sel = zoomBox->GetSelection();
	if (sel != wxNOT_FOUND) {
		windowZoomValue = (sel + 1) * .125;
		con->ass->Properties.video_zoom = windowZoomValue;
		FitClientSizeToVideo();
	}
}

void VideoDisplay::SetWindowZoomFromBoxText(wxCommandEvent &) {
	wxString strValue = zoomBox->GetValue();
	if (strValue.EndsWith("%"))
		strValue.RemoveLast();

	double value;
	if (strValue.ToDouble(&value))
		SetWindowZoom(value / 100.);
}

void VideoDisplay::SetTool(std::unique_ptr<VisualToolBase> new_tool) {
	// Set the tool first to prevent repeated initialization from VideoDisplay::Render
	tool = std::move(new_tool);

	// Hide the tool bar first to eliminate unnecessary size changes
	toolBar->Show(false);
	toolBar->ClearTools();
	tool->SetToolbar(toolBar);

	// Update size as the new typesetting tool may have changed the subtoolbar size
	if (!freeSize)
		FitClientSizeToVideo();
	else {
		// FitClientSizeToVideo fits the window to the video, which we don't want to do
		GetGrandParent()->Layout();
		PositionVideo();
	}
}

bool VideoDisplay::ToolIsType(std::type_info const& type) const {
	VisualToolBase *toolp = tool.get();		// This shuts up a compiler warning
	return toolp && typeid(*toolp) == type;
}

Vector2D VideoDisplay::GetMousePosition() const {
	return last_mouse_pos ? tool->ToScriptCoords(last_mouse_pos) : last_mouse_pos;
}

void VideoDisplay::Unload() {
	if (glContext) {
		SetCurrent(*glContext);
	}
	videoOut.reset();
	tool.reset();
	glContext.reset();
	pending_frame.reset();
}
