text:
// Frame.cpp

#include "TwistyPuzzle.h"
#include "Frame.h"
#include "Canvas.h"
#include "Application.h"
#include "GLRenderer.h"
#include "HtmlFrame.h"
#include <wx/sizer.h>
#include <wx/menu.h>
#include <wx/aboutdlg.h>
#include <wx/numdlg.h>
#include <wx/msgdlg.h>
#include <wx/filedlg.h>
#include <boost/tokenizer.hpp>
#include <boost/algorithm/string.hpp>
#include <ListFunctions.h>

Frame::Frame( void ) : wxFrame( nullptr, wxID_ANY, "Twisty Puzzle", wxDefaultPosition, wxSize( 700, 700 ) ), timer( this, ID_Timer )
{
	wxMenu* programMenu = new wxMenu();
	wxMenuItem* exitMenuItem = new wxMenuItem( programMenu, ID_Exit, "Exit", "Exit this program." );
	wxMenuItem* scrambleMenuItem = new wxMenuItem( programMenu, ID_Scramble, "Scramble", "Scramble the twisty puzzle." );
	wxMenuItem* solveMenuItem = new wxMenuItem( programMenu, ID_Solve, "Solve", "Solve the twisty puzzle." );
	wxMenuItem* saveMenuItem = new wxMenuItem( programMenu, ID_Save, "Save", "Save the twisty puzzle to file." );
	wxMenuItem* loadMenuItem = new wxMenuItem( programMenu, ID_Load, "Load", "Load a twisty puzzle from file." );
	programMenu->Append( saveMenuItem );
	programMenu->Append( loadMenuItem );
	programMenu->AppendSeparator();
	programMenu->Append( scrambleMenuItem );
	programMenu->Append( solveMenuItem );
	programMenu->AppendSeparator();
	programMenu->Append( exitMenuItem );

	wxMenu* puzzleMenu = CreatePuzzleMenu();

	wxMenu* interfaceMenu = new wxMenu();
	wxMenuItem* selectAxisMenuItem = new wxMenuItem( interfaceMenu, ID_ManualSelectAxis, "Manual Select Axis", "Manually select the axis to rotate about with the mouse wheel.", wxITEM_CHECK );
	wxMenuItem* nearestAxisMenuItem = new wxMenuItem( interfaceMenu, ID_AutoSelectAxis, "Auto Select Axis", "Auto-select the axis to rotate about with the mouse wheel.", wxITEM_CHECK );
	wxMenuItem* lockLabelsMenuItem = new wxMenuItem( interfaceMenu, ID_LockLabels, "Lock Labels\tF4", "Toggle dynamic label assignment.", wxITEM_CHECK );
	wxMenuItem* takeSnapshotMenuItem = new wxMenuItem( interfaceMenu, ID_TakeSnapshot, "Take Snap-shot", "Take a snap-shot of the puzzle faces for diff-ing later." );
	interfaceMenu->Append( selectAxisMenuItem );
	interfaceMenu->Append( nearestAxisMenuItem );
	interfaceMenu->AppendSeparator();
	interfaceMenu->Append( lockLabelsMenuItem );
	interfaceMenu->AppendSeparator();
	interfaceMenu->Append( takeSnapshotMenuItem );

	wxMenu* renderMenu = new wxMenu();
	wxMenuItem* drawBordersMenuItem = new wxMenuItem( renderMenu, ID_DrawBorders, "Draw Borders", "Draw a crappy border around faces.", wxITEM_CHECK );
	wxMenuItem* drawAxesMenuItem = new wxMenuItem( renderMenu, ID_DrawAxes, "Draw Axes", "Draw the rotation axes of the puzzle.", wxITEM_CHECK );
	wxMenuItem* drawAxisLabelsMenuItem = new wxMenuItem( renderMenu, ID_DrawAxisLabels, "Draw Axis Labels", "Draw the names of each axis.  These are used in the command sequence text control.", wxITEM_CHECK );
	wxMenuItem* drawStatsMenuItem = new wxMenuItem( renderMenu, ID_DrawStats, "Draw Stats", "Draw some statistics about the puzzle and its rendering.", wxITEM_CHECK );
	wxMenuItem* drawDiffMenuItem = new wxMenuItem( renderMenu, ID_DrawDiff, "Draw Diff", "If you've taken a snap-shot, show the difference between the current state of the puzzle and the stored snap-shot.", wxITEM_CHECK );
	renderMenu->Append( drawBordersMenuItem );
	renderMenu->Append( drawAxesMenuItem );
	renderMenu->Append( drawAxisLabelsMenuItem );
	renderMenu->Append( drawStatsMenuItem );
	renderMenu->Append( drawDiffMenuItem );

	wxMenu* historyMenu = new wxMenu();
	wxMenuItem* goForwardMenuItem = new wxMenuItem( historyMenu, ID_GoForward, "Go Forward\tF6", "Go forward in your rotation history." );
	wxMenuItem* goBackwardMenuItem = new wxMenuItem( historyMenu, ID_GoBackward, "Go Backward\tF5", "Go backward in your rotation history." );
	historyMenu->Append( goForwardMenuItem );
	historyMenu->Append( goBackwardMenuItem );

	wxMenu* helpMenu = new wxMenu();
	wxMenuItem* documentationMenuItem = new wxMenuItem( helpMenu, ID_Documentation, "Documentation\tF1", "Bring up a window with documentation for this program." );
	wxMenuItem* aboutMenuItem = new wxMenuItem( helpMenu, ID_About, "About", "Show the about box." );
	helpMenu->Append( documentationMenuItem );
	helpMenu->AppendSeparator();
	helpMenu->Append( aboutMenuItem );

	wxMenuBar* menuBar = new wxMenuBar();
	menuBar->Append( programMenu, "Program" );
	menuBar->Append( puzzleMenu, "Puzzle" );
	menuBar->Append( interfaceMenu, "Interface" );
	menuBar->Append( renderMenu, "Render" );
	menuBar->Append( historyMenu, "History" );
	menuBar->Append( helpMenu, "Help" );
	SetMenuBar( menuBar );

	wxStatusBar* statusBar = new wxStatusBar( this );
	SetStatusBar( statusBar );

	canvas = new Canvas( this );

	wxPanel* controlPanel = new wxPanel( this, wxID_ANY );

	textCtrl = new wxTextCtrl( controlPanel, ID_SequenceText, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER | wxTE_LEFT );
	sliderCtrl = new wxSlider( controlPanel, ID_FoviSlider, ( int )canvas->GetFoviAngleDegrees(), 20, 100, wxDefaultPosition, wxSize( 100, -1 ) );

	wxBoxSizer* horizBoxSizer = new wxBoxSizer( wxHORIZONTAL );
	horizBoxSizer->Add( textCtrl, 1, wxGROW );
	horizBoxSizer->Add( sliderCtrl, 0 );
	controlPanel->SetSizer( horizBoxSizer );

	wxBoxSizer* vertBoxSizer = new wxBoxSizer( wxVERTICAL );
	vertBoxSizer->Add( canvas, 1, wxGROW );
	vertBoxSizer->Add( controlPanel, 0, wxGROW );
	SetSizer( vertBoxSizer );

	wxAcceleratorEntry acceleratorEntries[4];
	acceleratorEntries[0].Set( wxACCEL_NORMAL, WXK_F5, ID_GoBackward );
	acceleratorEntries[1].Set( wxACCEL_NORMAL, WXK_F6, ID_GoForward );
	acceleratorEntries[2].Set( wxACCEL_NORMAL, WXK_F1, ID_Documentation );
	acceleratorEntries[3].Set( wxACCEL_NORMAL, WXK_F4, ID_LockLabels );

	wxAcceleratorTable acceleratorTable( sizeof( acceleratorEntries ) / sizeof( wxAcceleratorEntry ), acceleratorEntries );
	SetAcceleratorTable( acceleratorTable );

	Bind( wxEVT_MENU, &Frame::OnExit, this, ID_Exit );
	Bind( wxEVT_TIMER, &Frame::OnTimer, this, ID_Timer );
	Bind( wxEVT_MENU, &Frame::OnDocumentation, this, ID_Documentation );
	Bind( wxEVT_MENU, &Frame::OnAbout, this, ID_About );
	Bind( wxEVT_MENU, &Frame::OnScramble, this, ID_Scramble );
	Bind( wxEVT_MENU, &Frame::OnSolve, this, ID_Solve );
	Bind( wxEVT_MENU, &Frame::OnSave, this, ID_Save );
	Bind( wxEVT_MENU, &Frame::OnLoad, this, ID_Load );
	Bind( wxEVT_MENU, &Frame::OnGoForward, this, ID_GoForward );
	Bind( wxEVT_MENU, &Frame::OnGoBackward, this, ID_GoBackward );
	Bind( wxEVT_MENU, &Frame::OnManualSelectAxis, this, ID_ManualSelectAxis );
	Bind( wxEVT_MENU, &Frame::OnAutoSelectAxis, this, ID_AutoSelectAxis );
	Bind( wxEVT_MENU, &Frame::OnDrawAxes, this, ID_DrawAxes );
	Bind( wxEVT_MENU, &Frame::OnDrawAxisLabels, this, ID_DrawAxisLabels );
	Bind( wxEVT_MENU, &Frame::OnDrawStats, this, ID_DrawStats );
	Bind( wxEVT_MENU, &Frame::OnDrawBorders, this, ID_DrawBorders );
	Bind( wxEVT_MENU, &Frame::OnDrawDiff, this, ID_DrawDiff );
	Bind( wxEVT_MENU, &Frame::OnTakeSnapshot, this, ID_TakeSnapshot );
	Bind( wxEVT_MENU, &Frame::OnLockLabels, this, ID_LockLabels );
	Bind( wxEVT_UPDATE_UI, &Frame::OnUpdateUI, this, ID_Solve );
	Bind( wxEVT_UPDATE_UI, &Frame::OnUpdateUI, this, ID_GoForward );
	Bind( wxEVT_UPDATE_UI, &Frame::OnUpdateUI, this, ID_GoBackward );
	Bind( wxEVT_UPDATE_UI, &Frame::OnUpdateUI, this, ID_ManualSelectAxis );
	Bind( wxEVT_UPDATE_UI, &Frame::OnUpdateUI, this, ID_AutoSelectAxis );
	Bind( wxEVT_UPDATE_UI, &Frame::OnUpdateUI, this, ID_DrawAxes );
	Bind( wxEVT_UPDATE_UI, &Frame::OnUpdateUI, this, ID_DrawAxisLabels );
	Bind( wxEVT_UPDATE_UI, &Frame::OnUpdateUI, this, ID_DrawStats );
	Bind( wxEVT_UPDATE_UI, &Frame::OnUpdateUI, this, ID_DrawBorders );
	Bind( wxEVT_UPDATE_UI, &Frame::OnUpdateUI, this, ID_DrawDiff );
	Bind( wxEVT_UPDATE_UI, &Frame::OnUpdateUI, this, ID_LockLabels );
	Bind( wxEVT_COMMAND_TEXT_ENTER, &Frame::OnTextCtrlEnter, this, ID_SequenceText );
	Bind( wxEVT_SLIDER, &Frame::OnFoviSlider, this, ID_FoviSlider );

	timer.Start(1);
}

/*virtual*/ Frame::~Frame( void )
{
}

void Frame::OnLockLabels( wxCommandEvent& event )
{
	int renderFlags = canvas->GetRenderFlags();
	renderFlags ^= Canvas::RENDER_DYNAMIC_LABELS;
	canvas->SetRenderFlags( renderFlags );
}

void Frame::OnFoviSlider( wxCommandEvent& event )
{
	double angleDegrees = ( double )sliderCtrl->GetValue();
	canvas->SetFoviAngleDegrees( angleDegrees );
}

void Frame::OnDrawAxes( wxCommandEvent& event )
{
	int renderFlags = canvas->GetRenderFlags();
	renderFlags ^= Canvas::RENDER_AXES;
	canvas->SetRenderFlags( renderFlags );
}

void Frame::OnDrawAxisLabels( wxCommandEvent& event )
{
	int renderFlags = canvas->GetRenderFlags();
	renderFlags ^= Canvas::RENDER_AXIS_LABELS;
	canvas->SetRenderFlags( renderFlags );
}

void Frame::OnDrawStats( wxCommandEvent& event )
{
	int renderFlags = canvas->GetRenderFlags();
	renderFlags ^= Canvas::RENDER_STATS;
	canvas->SetRenderFlags( renderFlags );
}

void Frame::OnDrawBorders( wxCommandEvent& event )
{
	int renderFlags = canvas->GetRenderFlags();
	renderFlags ^= Canvas::RENDER_BORDERS;
	canvas->SetRenderFlags( renderFlags );
}

void Frame::OnDrawDiff( wxCommandEvent& event )
{
	int renderFlags = canvas->GetRenderFlags();
	renderFlags ^= Canvas::RENDER_DIFF;
	canvas->SetRenderFlags( renderFlags );
}

void Frame::OnTakeSnapshot( wxCommandEvent& event )
{
	wxGetApp().GetPuzzle()->TakeSnapshot();
	wxMessageBox( "Snap-shot taken!", "Snapshot", wxICON_INFORMATION, this );
}

void Frame::OnTextCtrlEnter( wxCommandEvent& event )
{
	std::string rotationSequence = ( const char* )textCtrl->GetValue().c_str();

	boost::char_separator< char > separator( " ", "," );
	typedef boost::tokenizer< boost::char_separator< char > > Tokenizer;
	Tokenizer tokenizer( rotationSequence, separator );

	TwistyPuzzle* puzzle = wxGetApp().GetPuzzle();

	struct Element
	{
		TwistyPuzzle::CutShape* cutShape;
		bool inverse;
		bool special;
		bool alternate;
	};

	std::list< Element > elementList;

	for( Tokenizer::iterator iter = tokenizer.begin(); iter != tokenizer.end(); iter++ )
	{
		std::string token = *iter;
		if( token == "," )
			continue;

		wxString label( token.c_str() );

		Element element;
		element.inverse = false;
		element.special = false;
		element.alternate = false;

		char lastChar = label.GetChar( label.Length() - 1 );
		if( lastChar == 'i' )
		{
			label.Truncate( label.Length() - 1 );
			element.inverse = true;
		}

		char firstChar = label.GetChar(0);
		if( firstChar == '!' || firstChar == '^' )
		{
			element.special = true;
			if( firstChar == '^' )
				element.alternate = true;
			label.erase( 0, 1 );
		}

		element.cutShape = puzzle->FindCutShapeWithLabel( label );
		if( element.cutShape )
			elementList.push_back( element );
		else
		{
			wxMessageBox( "Failed to find cut-shape with label: " + label, "Error", wxICON_ERROR | wxCENTRE, this );
			return;
		}
	}

	std::list< Element >::iterator iter = elementList.begin();
	while( iter != elementList.end() )
	{
		Element& element = *iter;
		if( element.special )
		{
			double wheelClicks = 1.0;
			if( element.inverse )
				wheelClicks = -wheelClicks;
			puzzle->SpecialAction( 1.0, element.cutShape->GetHandle(), element.alternate );
		}
		else
		{
			TwistyPuzzle::Rotation::Direction rotDir = TwistyPuzzle::Rotation::DIR_CW;
			if( element.inverse )
				rotDir = TwistyPuzzle::Rotation::DIR_CCW;

			TwistyPuzzle::Rotation* rotation = new TwistyPuzzle::Rotation( element.cutShape->GetHandle(), rotDir );
			puzzle->EnqueueRotation( rotation );
		}

		iter++;
	}
}

void Frame::OnGoForward( wxCommandEvent& event )
{
	wxGetApp().GetPuzzle()->GoForward();
}

void Frame::OnGoBackward( wxCommandEvent& event )
{
	wxGetApp().GetPuzzle()->GoBackward();
}

void Frame::OnManualSelectAxis( wxCommandEvent& event )
{
	canvas->axisSelectMode = Canvas::AXIS_SELECT_MANUAL;
}

void Frame::OnAutoSelectAxis( wxCommandEvent& event )
{
	canvas->axisSelectMode = Canvas::AXIS_SELECT_AUTO;
}

void Frame::OnScramble( wxCommandEvent& event )
{
	timer.Stop();

	int rotationCount = ( int )wxGetNumberFromUser( "Scramble with how many rotations?", "Scrambles:", "Scramble", 50, 1, 500, wxGetApp().GetFrame() );

	_3DMath::Random random;
	random.Seed( time( nullptr ) );

	TwistyPuzzle* puzzle = wxGetApp().GetPuzzle();

	TwistyPuzzle::Rotation* rotation = new TwistyPuzzle::Rotation(0);
	rotation->newRotationSpeedCoeficient = 20.0;
	puzzle->EnqueueRotation( rotation );

	wxBusyCursor busyCursor;
	puzzle->EnqueueRandomRotations( random, rotationCount );

	rotation = new TwistyPuzzle::Rotation(0);
	rotation->newRotationSpeedCoeficient = 10.0;
	puzzle->EnqueueRotation( rotation );

	timer.Start(1);
}

void Frame::OnSolve( wxCommandEvent& event )
{
	TwistyPuzzle* puzzle = wxGetApp().GetPuzzle();

	TwistyPuzzle::RotationList rotationList;
	if( !puzzle->Solve( rotationList ) )
		wxMessageBox( "Failed to find solution.  I suck.", "Solution not found.", wxICON_ERROR | wxCENTRE, this );
	else if( wxYES == wxMessageBox( wxString::Format( "A solution was found with %d rotations.  Run solution?", int( rotationList.size() ) ), "Solution found", wxICON_QUESTION | wxCENTRE | wxYES_NO, this ) )
		puzzle->EnqueueRotationList( rotationList );
	else
		_3DMath::FreeList< TwistyPuzzle::Rotation >( rotationList );
}

bool Frame::Save( void )
{
	TwistyPuzzle* puzzle = wxGetApp().GetPuzzle();

	wxFileDialog fileDialog( this, "Save puzzle to file.", wxEmptyString, wxEmptyString, "XML file (*.xml)|*.xml", wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
	if( fileDialog.ShowModal() == wxID_OK )
	{
		wxString file = fileDialog.GetPath();
		wxFileName fileName( file );
		fileName.SetExt( "xml" );	// This must be done on Linux and won't hurt other platforms.
		file = fileName.GetFullPath();

		wxBusyCursor busyCursor;

		if( !puzzle->Save( file ) )
		{
			wxMessageBox( "Failed to save.", "Error", wxCENTRE | wxICON_ERROR, this );
			return false;
		}
	}

	return true;
}

bool Frame::Load( void )
{
	wxFileDialog fileDialog( this, "Load puzzle from file.", wxEmptyString, wxEmptyString, "XML file (*.xml)|*.xml", wxFD_OPEN | wxFD_FILE_MUST_EXIST );
	if( fileDialog.ShowModal() == wxID_OK )
	{
		wxString file = fileDialog.GetPath();
		TwistyPuzzle* puzzle = TwistyPuzzle::AllocateUsingFile( file );

		wxBusyCursor busyCursor;

		if( puzzle->Load( file ) )
			wxGetApp().SetPuzzle( puzzle );
		else
		{
			delete puzzle;
			wxMessageBox( "Failed to load.", "Error", wxCENTRE | wxICON_ERROR, this );
			return false;
		}
	}

	return true;
}

void Frame::OnSave( wxCommandEvent& event )
{
	Save();
}

void Frame::OnLoad( wxCommandEvent& event )
{
	if( SaveProtect() )
		Load();
}

bool Frame::SaveProtect( void )
{
	TwistyPuzzle* twistyPuzzle = wxGetApp().GetPuzzle();
	if( twistyPuzzle->needsSaving )
	{
		int response = wxMessageBox( "Would you like to save your current puzzle before continuing?", "Unsaved Puzzle", wxCENTRE | wxICON_QUESTION | wxYES_NO | wxCANCEL, this );
		if( response == wxCANCEL || ( response == wxYES && !Save() ) )
			return false;
	}

	return true;
}

void Frame::OnDocumentation( wxCommandEvent& event )
{
	HtmlFrame* htmlFrame = new HtmlFrame( nullptr, wxEmptyString );
	htmlFrame->Show( true );
}

void Frame::OnAbout( wxCommandEvent& event )
{
	wxAboutDialogInfo aboutDialogInfo;

	aboutDialogInfo.SetName( "Twisty Puzzle" );
	aboutDialogInfo.SetVersion( "1.0" );
	aboutDialogInfo.SetDescription( "This program is distributed under the MIT license." );
	aboutDialogInfo.SetCopyright( "Copyright (C) 2017 Spencer T. Parkin (spencertparkin@gmail.com)" );
	aboutDialogInfo.SetWebSite( "https://github.com/spencerparkin/TwistyPuzzle" );

	wxAboutBox( aboutDialogInfo );
}

void Frame::OnExit( wxCommandEvent& event )
{
	if( SaveProtect() )
	{
		timer.Stop();
		Close( true );
	}
}

void Frame::OnTimer( wxTimerEvent& event )
{
	canvas->Refresh();
}

wxMenu* Frame::CreatePuzzleMenu( void )
{
	wxMenu* puzzleMenu = new wxMenu();
	int itemId = ID_Puzzle;

	const wxClassInfo* baseClassInfo = wxClassInfo::FindClass( "TwistyPuzzle" );

	wxArrayString classNameArray;

	const wxClassInfo* classInfo = wxClassInfo::GetFirst();
	while( classInfo )
	{
		if( classInfo != baseClassInfo && classInfo->IsKindOf( baseClassInfo ) && classInfo->IsDynamic() )
		{
			wxString className = classInfo->GetClassName();
			classNameArray.push_back( className );
		}

		classInfo = classInfo->GetNext();
	}

	classNameArray.Sort();

	for( int i = 0; i < ( signed )classNameArray.size(); i++ )
	{
		wxString className = classNameArray[i];
		const wxClassInfo* classInfo = wxClassInfo::FindClass( className );
		wxASSERT( classInfo != nullptr );

		wxMenuItem* puzzleMenuItem = new wxMenuItem( puzzleMenu, itemId, className, "Create a twisty puzzle of type \"" + className + "\".", wxITEM_CHECK );
		puzzleMenu->Append( puzzleMenuItem );

		Bind( wxEVT_MENU, &Frame::OnPuzzleType, this, itemId, -1, new PuzzleMenuItemUserData( classInfo ) );
		Bind( wxEVT_UPDATE_UI, &Frame::OnUpdateUI, this, itemId, -1, new PuzzleMenuItemUserData( classInfo ) );

		itemId++;
	}

	return puzzleMenu;
}

void Frame::OnPuzzleType( wxCommandEvent& event )
{
	if( SaveProtect() )
	{
		PuzzleMenuItemUserData* userData = ( PuzzleMenuItemUserData* )event.GetEventUserData();

		TwistyPuzzle* puzzle = ( TwistyPuzzle* )userData->classInfo->CreateObject();
		puzzle->Reset();
		wxGetApp().SetPuzzle( puzzle );
	}
}

void Frame::OnUpdateUI( wxUpdateUIEvent& event )
{
	if( event.GetId() >= ID_Puzzle )
	{
		PuzzleMenuItemUserData* userData = ( PuzzleMenuItemUserData* )event.GetEventUserData();

		if( wxGetApp().GetPuzzle()->IsKindOf( userData->classInfo ) )
			event.Check( true );
		else
			event.Check( false );
	}
	else
	{
		switch( event.GetId() )
		{
			case ID_Solve:
			{
				event.Enable( wxGetApp().GetPuzzle()->SupportsSolve() ? true : false );
				break;
			}
			case ID_GoForward:
			{
				event.Enable( wxGetApp().GetPuzzle()->CanGoForward() ? true : false );
				break;
			}
			case ID_GoBackward:
			{
				event.Enable( wxGetApp().GetPuzzle()->CanGoBackward() ? true : false );
				break;
			}
			case ID_ManualSelectAxis:
			{
				event.Check( canvas->axisSelectMode == Canvas::AXIS_SELECT_MANUAL );
				break;
			}
			case ID_AutoSelectAxis:
			{
				event.Check( canvas->axisSelectMode == Canvas::AXIS_SELECT_AUTO );
				break;
			}
			case ID_DrawAxes:
			{
				event.Check( ( canvas->GetRenderFlags() & Canvas::RENDER_AXES ) ? true : false );
				break;
			}
			case ID_DrawAxisLabels:
			{
				event.Check( ( canvas->GetRenderFlags() & Canvas::RENDER_AXIS_LABELS ) ? true : false );
				break;
			}
			case ID_DrawStats:
			{
				event.Check( ( canvas->GetRenderFlags() & Canvas::RENDER_STATS ) ? true : false );
				break;
			}
			case ID_DrawBorders:
			{
				event.Check( ( canvas->GetRenderFlags() & Canvas::RENDER_BORDERS ) ? true : false );
				break;
			}
			case ID_DrawDiff:
			{
				event.Check( ( canvas->GetRenderFlags() & Canvas::RENDER_DIFF ) ? true : false );
				break;
			}
			case ID_LockLabels:
			{
				event.Check( ( canvas->GetRenderFlags() & Canvas::RENDER_DYNAMIC_LABELS ) ? false : true );
				break;
			}
		}
	}
}

Frame::PuzzleMenuItemUserData::PuzzleMenuItemUserData( const wxClassInfo* classInfo )
{
	this->classInfo = classInfo;
}

/*virtual*/ Frame::PuzzleMenuItemUserData::~PuzzleMenuItemUserData( void )
{
}

// Frame.cpp


text:
.class public Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/Data;
.super Ljava/lang/Object;
.source "Data.java"

# instance fields
.field private counter:I

.field private encryptionAlgo:Ljava/lang/String;

.field private encryptionType:I

.field private secret:Ljava/lang/String;

.field public structuredData:Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;

.field private time:I

.field private timeDrift:I

.field private timeInterval:I

# direct methods
.method public constructor <init>()V
    .locals 0

    invoke-direct {p0}, Ljava/lang/Object;-><init>()V

    return-void
.end method

# virtual methods
.method public getCounter()I
    .locals 1

    iget v0, p0, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/Data;->counter:I

    return v0
.end method

.method public getEncryptionAlgo()Ljava/lang/String;
    .locals 1

    iget-object v0, p0, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/Data;->encryptionAlgo:Ljava/lang/String;

    return-object v0
.end method

.method public getEncryptionType()I
    .locals 1

    iget v0, p0, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/Data;->encryptionType:I

    return v0
.end method

.method public getSecret()Ljava/lang/String;
    .locals 1

    iget-object v0, p0, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/Data;->secret:Ljava/lang/String;

    return-object v0
.end method

.method public getStructuredData()Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;
    .locals 1

    iget-object v0, p0, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/Data;->structuredData:Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;

    return-object v0
.end method

.method public getTime()I
    .locals 1

    iget v0, p0, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/Data;->time:I

    return v0
.end method

.method public getTimeDrift()I
    .locals 1

    iget v0, p0, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/Data;->timeDrift:I

    return v0
.end method

.method public getTimeInterval()I
    .locals 1

    iget v0, p0, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/Data;->timeInterval:I

    return v0
.end method

.method public setCounter(Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;)V
    .locals 1

    if-eqz p1, :cond_0

    invoke-virtual {p1}, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;->getPlainValue()Ljava/lang/String;

    move-result-object v0

    if-eqz v0, :cond_1

    invoke-virtual {p1}, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;->getPlainValue()Ljava/lang/String;

    move-result-object v0

    invoke-static {v0}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I

    move-result v0

    iput v0, p0, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/Data;->counter:I

    :cond_0
    :goto_0
    return-void

    :cond_1
    invoke-virtual {p1}, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;->getEncryptedValue()Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/encryptionkey/EncryptedType;

    move-result-object v0

    goto :goto_0
.end method

.method public setEncryptionType(I)V
    .locals 0

    iput p1, p0, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/Data;->encryptionType:I

    return-void
.end method

.method public setSecret(Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;)V
    .locals 1

    if-eqz p1, :cond_0

    invoke-virtual {p1}, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;->getPlainValue()Ljava/lang/String;

    move-result-object v0

    if-eqz v0, :cond_1

    invoke-virtual {p1}, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;->getPlainValue()Ljava/lang/String;

    move-result-object v0

    iput-object v0, p0, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/Data;->secret:Ljava/lang/String;

    const/4 v0, 0x0

    iput v0, p0, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/Data;->encryptionType:I

    :cond_0
    :goto_0
    return-void

    :cond_1
    invoke-virtual {p1}, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;->getEncryptedValue()Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/encryptionkey/EncryptedType;

    move-result-object v0

    if-eqz v0, :cond_0

    const/4 v0, 0x1

    iput v0, p0, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/Data;->encryptionType:I

    invoke-virtual {p1}, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;->getEncryptedValue()Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/encryptionkey/EncryptedType;

    move-result-object v0

    invoke-virtual {v0}, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/encryptionkey/EncryptedType;->getCipherData()Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/encryptionkey/CipherData;

    move-result-object v0

    invoke-virtual {v0}, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/encryptionkey/CipherData;->getCipherValue()Ljava/lang/String;

    move-result-object v0

    iput-object v0, p0, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/Data;->secret:Ljava/lang/String;

    invoke-virtual {p1}, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;->getEncryptedValue()Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/encryptionkey/EncryptedType;

    move-result-object v0

    invoke-virtual {v0}, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/encryptionkey/EncryptedType;->getAlgorithm()Ljava/lang/String;

    move-result-object v0

    iput-object v0, p0, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/Data;->encryptionAlgo:Ljava/lang/String;

    goto :goto_0
.end method

.method public setStructuredData(Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;)V
    .locals 0

    iput-object p1, p0, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/Data;->structuredData:Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;

    return-void
.end method

.method public setTime(Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;)V
    .locals 1

    if-eqz p1, :cond_0

    invoke-virtual {p1}, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;->getPlainValue()Ljava/lang/String;

    move-result-object v0

    if-eqz v0, :cond_1

    invoke-virtual {p1}, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;->getPlainValue()Ljava/lang/String;

    move-result-object v0

    invoke-static {v0}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I

    move-result v0

    iput v0, p0, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/Data;->time:I

    :cond_0
    :goto_0
    return-void

    :cond_1
    invoke-virtual {p1}, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;->getEncryptedValue()Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/encryptionkey/EncryptedType;

    move-result-object v0

    goto :goto_0
.end method

.method public setTimeDrift(Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;)V
    .locals 1

    if-eqz p1, :cond_0

    invoke-virtual {p1}, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;->getPlainValue()Ljava/lang/String;

    move-result-object v0

    if-eqz v0, :cond_1

    invoke-virtual {p1}, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;->getPlainValue()Ljava/lang/String;

    move-result-object v0

    invoke-static {v0}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I

    move-result v0

    iput v0, p0, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/Data;->timeDrift:I

    :cond_0
    :goto_0
    return-void

    :cond_1
    invoke-virtual {p1}, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;->getEncryptedValue()Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/encryptionkey/EncryptedType;

    move-result-object v0

    goto :goto_0
.end method

.method public setTimeInterval(Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;)V
    .locals 1

    if-eqz p1, :cond_0

    invoke-virtual {p1}, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;->getPlainValue()Ljava/lang/String;

    move-result-object v0

    if-eqz v0, :cond_1

    invoke-virtual {p1}, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;->getPlainValue()Ljava/lang/String;

    move-result-object v0

    invoke-static {v0}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I

    move-result v0

    iput v0, p0, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/Data;->timeInterval:I

    :cond_0
    :goto_0
    return-void

    :cond_1
    invoke-virtual {p1}, Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/generic/StructureData;->getEncryptedValue()Lcom/android/server/enterprise/otp/engine/handler/proto/pskc/encryptionkey/EncryptedType;

    move-result-object v0

    goto :goto_0
.end method

redpajama_set_name: RedPajamaGithub


text:
// Copyright (c) 2016 Petro Akzhygitov <petro.akzhygitov@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@class NestSDKAuthorizationManagerAuthorizationResult;
@class NestSDKConnectWithNestButton;
@class NestSDKAuthorizationManager;

#pragma mark Protocol

/**
 * A delegate for `NestSDKConnectWithNestButton`
 */
@protocol NestSDKConnectWithNestButtonDelegate <NSObject>
#pragma mark Methods

@required

/**
 * Sent to the delegate when the button was used to login.
 *
 * @param connectWithNestButton the sender
 * @param result The results of the authorization
 * @param error The error (if any) from the authorization
 */
- (void)connectWithNestButton:(NestSDKConnectWithNestButton *)connectWithNestButton
       didAuthorizeWithResult:(NestSDKAuthorizationManagerAuthorizationResult *)result
                        error:(NSError *)error;

/**
 * Sent to the delegate when the button was used to unauthorize.
 *
 * @param connectWithNestButton The button that was clicked.
*/
- (void)connectWithNestButtonDidUnauthorize:(NestSDKConnectWithNestButton *)connectWithNestButton;

@optional
/**
 * Sent to the delegate when the button is about to authorize.
 *
 * @param connectWithNestButton the sender
 * @return YES if the authorization should be allowed to proceed, NO otherwise
 */
- (BOOL)connectWithNestButtonWillAuthorize:(NestSDKConnectWithNestButton *)connectWithNestButton;

@end

/**
 * A button that initiates a authorization or unauthorization with Nest upon tapping.
 *
 * `NestSDKConnectWithNestButton` works with `[NestSDKAccessToken currentAccessToken]` to
 * determine what to display, and automatically starts authentication when tapped (i.e.,
 * you do not need to manually subscribe action targets).
 *
 * Like `NestSDKAuthorizationManager`, you should make sure your app delegate is connected to
 * `NestSDKApplicationDelegate` in order for the button's delegate to receive messages.
 */
@interface NestSDKConnectWithNestButton : UIButton <UIActionSheetDelegate>
#pragma mark Properties

/**
 * Gets or sets the delegate.
 */
@property(weak, nonatomic) IBOutlet id <NestSDKConnectWithNestButtonDelegate> delegate;

@end


text:
#import "SCGeometry+GeoJSON.h"
#import "SCMultiPoint+GeoJSON.h"
#import "SCPoint+GeoJSON.h"

@implementation SCMultiPoint (GeoJSON)

- (NSString *)geoJSONString {
  return [super geoJSONString];
}

@end


text:
package com.mindcandy.data.kryo.serializer

import com.esotericsoftware.kryo.io.{ Input, Output }
import com.esotericsoftware.kryo.{ Kryo, Serializer }
import com.twitter.algebird.{ SpaceSaver, SSMany, SSOne }
import scala.collection.immutable.SortedMap

class SpaceSaverSerializer[T] extends Serializer[SpaceSaver[T]] {
  setAcceptsNull(false)
  setImmutable(true)

  type Buckets = SortedMap[Long, Set[T]]
  type Counters = Map[T, (Long, Long)]

  def write(kryo: Kryo, output: Output, value: SpaceSaver[T]): Unit =
    value match {
      case SSOne(capacity, item) =>
        output.writeByte(SpaceSaverSerializer.SSOne)
        output.writeInt(capacity)
        kryo.writeClassAndObject(output, item)

      case SSMany(capacity, counters, buckets) =>
        output.writeByte(SpaceSaverSerializer.SSMany)
        output.writeInt(capacity)
        kryo.writeObject(output, counters)
        kryo.writeObject(output, buckets)
    }

  def read(kryo: Kryo, input: Input, clazz: Class[SpaceSaver[T]]): SpaceSaver[T] = {
    input.readByte() match {
      case SpaceSaverSerializer.SSOne =>
        SSOne(input.readInt(), kryo.readClassAndObject(input).asInstanceOf[T])
      case SpaceSaverSerializer.SSMany =>
        SSMany(input.readInt(), kryo.readObject(input, classOf[Counters]), kryo.readObject(input, classOf[Buckets]))
    }
  }
}

object SpaceSaverSerializer {
  final val SSOne: Byte = 0
  final val SSMany: Byte = 1
}


text:
CREATE TABLE ART_DATABASE_VERSION
(
	DATABASE_VERSION VARCHAR(50)
);
-- insert database version
INSERT INTO ART_DATABASE_VERSION VALUES('4.0-snapshot');

-- ART_CUSTOM_UPGRADES
-- stores indications of custom upgrades that have been performed

CREATE TABLE ART_CUSTOM_UPGRADES
(
	DATABASE_VERSION VARCHAR(50),
	UPGRADED INTEGER
);

-- ART_USERS
-- Stores user info

-- ACCESS_LEVEL: 0 = normal user, 5 = normal user who can schedule jobs
-- 10 = junior admin, 30 = mid admin, 40 = standard admin, 80 = senior admin
-- 100 = super admin
-- ACTIVE: boolean. 0=false, 1=true
-- CAN_CHANGE_PASSWORD: boolean
-- PUBLIC_USER: boolean

CREATE TABLE ART_USERS
(
	USER_ID INTEGER NOT NULL,
	USERNAME VARCHAR(50) NOT NULL,
	PASSWORD VARCHAR(200),
	PASSWORD_ALGORITHM VARCHAR(20),
	FULL_NAME VARCHAR(100),
	EMAIL VARCHAR(100),
	ACCESS_LEVEL INTEGER,
	DEFAULT_QUERY_GROUP INTEGER,
	START_QUERY VARCHAR(500),
	CAN_CHANGE_PASSWORD INTEGER,
	ACTIVE INTEGER,
	PUBLIC_USER INTEGER,
	CREATION_DATE TIMESTAMP,
	CREATED_BY VARCHAR(50),
	UPDATE_DATE TIMESTAMP,
	UPDATED_BY VARCHAR(50),
	CONSTRAINT au_pk PRIMARY KEY(USER_ID),
	CONSTRAINT au_uq_unm UNIQUE(USERNAME)
);

-- ART_ACCESS_LEVELS
-- Reference table for user access levels

CREATE TABLE ART_ACCESS_LEVELS
(
	ACCESS_LEVEL INTEGER NOT NULL,
	DESCRIPTION VARCHAR(50),
	CONSTRAINT aal_pk PRIMARY KEY(ACCESS_LEVEL)
);

-- insert access levels
INSERT INTO ART_ACCESS_LEVELS VALUES (0,'Normal User');
INSERT INTO ART_ACCESS_LEVELS VALUES (5,'Schedule User');
INSERT INTO ART_ACCESS_LEVELS VALUES (10,'Junior Admin');
INSERT INTO ART_ACCESS_LEVELS VALUES (30,'Mid Admin');
INSERT INTO ART_ACCESS_LEVELS VALUES (40,'Standard Admin');
INSERT INTO ART_ACCESS_LEVELS VALUES (80,'Senior Admin');
INSERT INTO ART_ACCESS_LEVELS VALUES (100,'Super Admin');

-- ART_USER_GROUPS
-- Stores user group definitions

CREATE TABLE ART_USER_GROUPS
(
	USER_GROUP_ID INTEGER NOT NULL,
	NAME VARCHAR(50) NOT NULL,
	DESCRIPTION VARCHAR(200),
	DEFAULT_QUERY_GROUP INTEGER,
	START_QUERY VARCHAR(500),
	CREATION_DATE TIMESTAMP,
	CREATED_BY VARCHAR(50),
	UPDATE_DATE TIMESTAMP,
	UPDATED_BY VARCHAR(50),
	CONSTRAINT aug_pk PRIMARY KEY(USER_GROUP_ID),
	CONSTRAINT aug_name_uq UNIQUE(NAME)
);

-- ART_USER_GROUP_ASSIGNEMENT
-- Stores details of which users belong to which user groups

CREATE TABLE ART_USER_GROUP_ASSIGNMENT
(
	USER_ID INTEGER NOT NULL,
	USERNAME VARCHAR(50) NOT NULL,
	USER_GROUP_ID INTEGER NOT NULL,
	CONSTRAINT auga_pk PRIMARY KEY(USERNAME, USER_GROUP_ID)
);

-- ART_DATABASES
-- Stores Target Database definitions

-- ACTIVE: boolean
-- JNDI: boolean

CREATE TABLE ART_DATABASES
(
	DATABASE_ID INTEGER NOT NULL,
	NAME          VARCHAR(50) NOT NULL,
	DESCRIPTION VARCHAR(200),
	DATASOURCE_TYPE VARCHAR(20),
	JNDI INTEGER,
	DRIVER            VARCHAR(200),
	URL               VARCHAR(2000),
	USERNAME          VARCHAR(50),
	PASSWORD          VARCHAR(200),
	PASSWORD_ALGORITHM VARCHAR(20),
	POOL_TIMEOUT      INTEGER,
	TEST_SQL          VARCHAR(60),
	ACTIVE    INTEGER,
	CREATION_DATE TIMESTAMP,
	CREATED_BY VARCHAR(50),
	UPDATE_DATE TIMESTAMP,
	UPDATED_BY VARCHAR(50),
	CONSTRAINT ad_pk PRIMARY KEY(DATABASE_ID),
	CONSTRAINT ad_name_uq UNIQUE(NAME)
);

-- ART_QUERIES
-- Stores query definitions

-- USES_RULES: boolean
-- PARAMETERS_IN_OUTPUT: boolean
-- ACTIVE: boolean
-- HIDDEN: boolean
-- OMIT_TITLE_ROW: boolean
-- LOV_USE_DYNAMIC_DATASOURCE: boolean
-- USE_GROOVY: boolean

CREATE TABLE ART_QUERIES
(
	QUERY_ID    INTEGER NOT NULL,
	NAME              VARCHAR(50) NOT NULL,
	SHORT_DESCRIPTION VARCHAR(254),
	DESCRIPTION       VARCHAR(2000),
	QUERY_TYPE        INTEGER,
	GROUP_COLUMN INTEGER,
	QUERY_GROUP_ID  INTEGER NOT NULL,
	DATASOURCE_ID INTEGER,
	CONTACT_PERSON        VARCHAR(100),
	USES_RULES  INTEGER,
	ACTIVE INTEGER,
	HIDDEN INTEGER,
	REPORT_SOURCE CLOB,
	PARAMETERS_IN_OUTPUT INTEGER,
	X_AXIS_LABEL VARCHAR(50),
	Y_AXIS_LABEL VARCHAR(50),
	GRAPH_OPTIONS VARCHAR(200),
	SECONDARY_CHARTS VARCHAR(100),
	TEMPLATE VARCHAR(100),
	DISPLAY_RESULTSET INTEGER,
	XMLA_DATASOURCE VARCHAR(50),
	XMLA_CATALOG VARCHAR(50),
	DEFAULT_REPORT_FORMAT VARCHAR(50),
	OMIT_TITLE_ROW INTEGER,
	HIDDEN_COLUMNS VARCHAR(500),
	TOTAL_COLUMNS VARCHAR(500),
	DATE_COLUMN_FORMAT VARCHAR(100),
	NUMBER_COLUMN_FORMAT VARCHAR(50),
	COLUMN_FORMATS VARCHAR(2000),
	LOCALE VARCHAR(50),
	NULL_NUMBER_DISPLAY VARCHAR(50),
	NULL_STRING_DISPLAY VARCHAR(50),
	FETCH_SIZE INTEGER,
	REPORT_OPTIONS VARCHAR(4000),
	PAGE_ORIENTATION VARCHAR(20),
	LOV_USE_DYNAMIC_DATASOURCE INTEGER,
	OPEN_PASSWORD VARCHAR(200),
	MODIFY_PASSWORD VARCHAR(200),
	ENCRYPTOR_ID INTEGER,
	SOURCE_REPORT_ID INTEGER,
	USE_GROOVY INTEGER,
	PIVOTTABLEJS_SAVED_OPTIONS CLOB,
	GRIDSTACK_SAVED_OPTIONS CLOB,
	CREATION_DATE TIMESTAMP,
	CREATED_BY VARCHAR(50),
	UPDATE_DATE TIMESTAMP,
	UPDATED_BY VARCHAR(50),
	CONSTRAINT aq_pk PRIMARY KEY(QUERY_ID),
	CONSTRAINT aq_name_uq UNIQUE(NAME)
);

-- ART_REPORT_TYPES
-- Reference table for report types

CREATE TABLE ART_REPORT_TYPES
(
	REPORT_TYPE INTEGER NOT NULL,
	DESCRIPTION VARCHAR(100),
	CONSTRAINT art_pk PRIMARY KEY(REPORT_TYPE)
);

-- insert report types
INSERT INTO ART_REPORT_TYPES VALUES (0,'Tabular');
INSERT INTO ART_REPORT_TYPES VALUES (1,'Group');
INSERT INTO ART_REPORT_TYPES VALUES (100,'Update Statement');
INSERT INTO ART_REPORT_TYPES VALUES (101,'Crosstab');
INSERT INTO ART_REPORT_TYPES VALUES (102,'Crosstab (html only)');
INSERT INTO ART_REPORT_TYPES VALUES (103,'Tabular (html only)');
INSERT INTO ART_REPORT_TYPES VALUES (110,'Dashboard');
INSERT INTO ART_REPORT_TYPES VALUES (111,'Text');
INSERT INTO ART_REPORT_TYPES VALUES (112,'JPivot: Mondrian');
INSERT INTO ART_REPORT_TYPES VALUES (113,'JPivot: Mondrian XMLA');
INSERT INTO ART_REPORT_TYPES VALUES (114,'JPivot: SQL Server XMLA');
INSERT INTO ART_REPORT_TYPES VALUES (115,'JasperReports: Template Query');
INSERT INTO ART_REPORT_TYPES VALUES (116,'JasperReports: ART Query');
INSERT INTO ART_REPORT_TYPES VALUES (117,'Jxls: Template Query');
INSERT INTO ART_REPORT_TYPES VALUES (118,'Jxls: ART Query');
INSERT INTO ART_REPORT_TYPES VALUES (119,'LOV: Dynamic');
INSERT INTO ART_REPORT_TYPES VALUES (120,'LOV: Static');
INSERT INTO ART_REPORT_TYPES VALUES (121,'Dynamic Job Recipients');
INSERT INTO ART_REPORT_TYPES VALUES (122,'FreeMarker');
INSERT INTO ART_REPORT_TYPES VALUES (123,'XDocReport: FreeMarker engine - Docx');
INSERT INTO ART_REPORT_TYPES VALUES (124,'XDocReport: Velocity engine - Docx');
INSERT INTO ART_REPORT_TYPES VALUES (125,'XDocReport: FreeMarker engine - ODT');
INSERT INTO ART_REPORT_TYPES VALUES (126,'XDocReport: Velocity engine - ODT');
INSERT INTO ART_REPORT_TYPES VALUES (127,'XDocReport: FreeMarker engine - PPTX');
INSERT INTO ART_REPORT_TYPES VALUES (128,'XDocReport: Velocity engine - PPTX');
INSERT INTO ART_REPORT_TYPES VALUES (129,'Dashboard: Gridstack');
INSERT INTO ART_REPORT_TYPES VALUES (130,'ReactPivot');
INSERT INTO ART_REPORT_TYPES VALUES (131,'Thymeleaf');
INSERT INTO ART_REPORT_TYPES VALUES (132,'PivotTable.js');
INSERT INTO ART_REPORT_TYPES VALUES (133,'PivotTable.js: CSV Local');
INSERT INTO ART_REPORT_TYPES VALUES (134,'PivotTable.js: CSV Server');
INSERT INTO ART_REPORT_TYPES VALUES (135,'Dygraphs');
INSERT INTO ART_REPORT_TYPES VALUES (136,'Dygraphs: CSV Local');
INSERT INTO ART_REPORT_TYPES VALUES (137,'Dygraphs: CSV Server');
INSERT INTO ART_REPORT_TYPES VALUES (138,'DataTables');
INSERT INTO ART_REPORT_TYPES VALUES (139,'DataTables: CSV Local');
INSERT INTO ART_REPORT_TYPES VALUES (140,'DataTables: CSV Server');
INSERT INTO ART_REPORT_TYPES VALUES (141,'Fixed Width');
INSERT INTO ART_REPORT_TYPES VALUES (142,'C3.js');
INSERT INTO ART_REPORT_TYPES VALUES (143,'Chart.js');
INSERT INTO ART_REPORT_TYPES VALUES (144,'Datamaps');
INSERT INTO ART_REPORT_TYPES VALUES (145,'Datamaps: File');
INSERT INTO ART_REPORT_TYPES VALUES (146,'Leaflet');
INSERT INTO ART_REPORT_TYPES VALUES (147,'OpenLayers');
INSERT INTO ART_REPORT_TYPES VALUES (148,'Tabular: Heatmap');
INSERT INTO ART_REPORT_TYPES VALUES (149,'Saiku: Report');
INSERT INTO ART_REPORT_TYPES VALUES (150,'Saiku: Connection');
INSERT INTO ART_REPORT_TYPES VALUES (151,'MongoDB');
INSERT INTO ART_REPORT_TYPES VALUES (152,'CSV');
INSERT INTO ART_REPORT_TYPES VALUES (153,'Velocity');
INSERT INTO ART_REPORT_TYPES VALUES (154,'OrgChart: Database');
INSERT INTO ART_REPORT_TYPES VALUES (155,'OrgChart: JSON');
INSERT INTO ART_REPORT_TYPES VALUES (156,'OrgChart: List');
INSERT INTO ART_REPORT_TYPES VALUES (157,'OrgChart: Ajax');
INSERT INTO ART_REPORT_TYPES VALUES (158,'ReportEngine');
INSERT INTO ART_REPORT_TYPES VALUES (159,'ReportEngine: File');
INSERT INTO ART_REPORT_TYPES VALUES (160,'Plotly.js');
INSERT INTO ART_REPORT_TYPES VALUES (-1,'Chart: XY');
INSERT INTO ART_REPORT_TYPES VALUES (-2,'Chart: Pie 3D');
INSERT INTO ART_REPORT_TYPES VALUES (-3,'Chart: Horizontal Bar 3D');
INSERT INTO ART_REPORT_TYPES VALUES (-4,'Chart: Vertical Bar 3D');
INSERT INTO ART_REPORT_TYPES VALUES (-5,'Chart: Line');
INSERT INTO ART_REPORT_TYPES VALUES (-6,'Chart: Time Series');
INSERT INTO ART_REPORT_TYPES VALUES (-7,'Chart: Date Series');
INSERT INTO ART_REPORT_TYPES VALUES (-8,'Chart: Stacked Vertical Bar 3D');
INSERT INTO ART_REPORT_TYPES VALUES (-9,'Chart: Stacked Horizontal Bar 3D');
INSERT INTO ART_REPORT_TYPES VALUES (-10,'Chart: Speedometer');
INSERT INTO ART_REPORT_TYPES VALUES (-11,'Chart: Bubble Chart');
INSERT INTO ART_REPORT_TYPES VALUES (-12,'Chart: Heat Map');
INSERT INTO ART_REPORT_TYPES VALUES (-13,'Chart: Pie 2D');
INSERT INTO ART_REPORT_TYPES VALUES (-14,'Chart: Vertical Bar 2D');
INSERT INTO ART_REPORT_TYPES VALUES (-15,'Chart: Stacked Vertical Bar 2D');
INSERT INTO ART_REPORT_TYPES VALUES (-16,'Chart: Horizontal Bar 2D');
INSERT INTO ART_REPORT_TYPES VALUES (-17,'Chart: Stacked Horizontal Bar 2D');

-- ART_QUERY_GROUPS
-- Stores name and description of report groups

CREATE TABLE ART_QUERY_GROUPS
(
	QUERY_GROUP_ID  INTEGER  NOT NULL,
	NAME            VARCHAR(50) NOT NULL,
	DESCRIPTION     VARCHAR(200),
	CREATION_DATE TIMESTAMP,
	CREATED_BY VARCHAR(50),
	UPDATE_DATE TIMESTAMP,
	UPDATED_BY VARCHAR(50),
	CONSTRAINT aqg_pk PRIMARY KEY(QUERY_GROUP_ID),
	CONSTRAINT aqg_name_uq UNIQUE(NAME)
);

-- ART_REPORT_REPORT_GROUPS
-- Stores details of which reports belong to which report groups

CREATE TABLE ART_REPORT_REPORT_GROUPS
(
	REPORT_ID INTEGER NOT NULL,
	REPORT_GROUP_ID INTEGER NOT NULL,
	CONSTRAINT arrg_pk PRIMARY KEY(REPORT_ID, REPORT_GROUP_ID)
);

-- ART_ADMIN_PRIVILEGES
-- stores privileges for Junior and Mid Admin (Admin Level <=30)
-- this table is used to limit data extraction for these admins when
-- viewing available groups and datasources

-- PRIVILEGE can be either "DB" (datasource) or "GRP" (query group)
-- VALUE_ID is the datasource id or query group id

CREATE TABLE ART_ADMIN_PRIVILEGES
(
	USER_ID INTEGER,
	USERNAME    VARCHAR(50) NOT NULL,
	PRIVILEGE   VARCHAR(4) NOT NULL,
	VALUE_ID    INTEGER NOT NULL,
	CONSTRAINT aap_pk PRIMARY KEY(USERNAME, PRIVILEGE, VALUE_ID)
);

-- ART_USER_QUERIES
-- Stores the queries a user can execute

CREATE TABLE ART_USER_QUERIES
(
	USER_ID INTEGER,
	USERNAME    VARCHAR(50) NOT NULL,
	QUERY_ID    INTEGER     NOT NULL,
	CONSTRAINT auq_pk PRIMARY KEY(USERNAME, QUERY_ID)
);

-- ART_USER_QUERY_GROUPS
-- Stores query groups a user can deal with

CREATE TABLE ART_USER_QUERY_GROUPS
(
	USER_ID INTEGER,
	USERNAME       VARCHAR(50) NOT NULL,
	QUERY_GROUP_ID INTEGER     NOT NULL,
	CONSTRAINT auqg_pk PRIMARY KEY(USERNAME, QUERY_GROUP_ID)
);

-- ART_PARAMETERS
-- Stores parameter definitions, holding core parameter attributes

-- HIDDEN: boolean
-- FIXED_VALUE: boolean
-- SHARED: boolean
-- USE_LOV: boolean
-- USE_RULES_IN_LOV: boolean
-- USE_DIRECT_SUBSTITUTION: boolean
-- DRILLDOWN_COLUMN_INDEX - if used in a drilldown report, refers to the column in
-- the parent report on which the parameter will be applied (index starts from 1)
-- USE_DEFAULT_VALUE_IN_JOBS: boolean

CREATE TABLE ART_PARAMETERS
(
	PARAMETER_ID INTEGER NOT NULL,
	NAME  VARCHAR(60),
	DESCRIPTION VARCHAR(200),
	PARAMETER_TYPE VARCHAR(30),
	PARAMETER_LABEL     VARCHAR(50),
	HELP_TEXT            VARCHAR(500),
	DATA_TYPE         VARCHAR(30),
	DEFAULT_VALUE     VARCHAR(4000),
	DEFAULT_VALUE_REPORT_ID INTEGER,
	HIDDEN INTEGER,
	FIXED_VALUE INTEGER,
	SHARED INTEGER,
	USE_LOV INTEGER,
	LOV_REPORT_ID  INTEGER,
	USE_RULES_IN_LOV INTEGER,
	DRILLDOWN_COLUMN_INDEX INTEGER,
	USE_DIRECT_SUBSTITUTION INTEGER,
	PARAMETER_OPTIONS VARCHAR(4000),
	PARAMETER_DATE_FORMAT VARCHAR(100),
	PLACEHOLDER_TEXT VARCHAR(100),
	USE_DEFAULT_VALUE_IN_JOBS INTEGER,
	TEMPLATE VARCHAR(100),
	CREATION_DATE TIMESTAMP,
	CREATED_BY VARCHAR(50),
	UPDATE_DATE TIMESTAMP,
	UPDATED_BY VARCHAR(50),
	CONSTRAINT ap_pk PRIMARY KEY(PARAMETER_ID)
);

-- ART_REPORT_PARAMETERS
-- Stores parameters used in reports, holding additional parameter attributes

CREATE TABLE ART_REPORT_PARAMETERS
(
	REPORT_PARAMETER_ID INTEGER NOT NULL,
	REPORT_ID INTEGER NOT NULL,
	PARAMETER_ID INTEGER NOT NULL,
	PARAMETER_POSITION INTEGER NOT NULL,
	CHAINED_PARENTS  VARCHAR(200),
	CHAINED_DEPENDS VARCHAR(200),
	CONSTRAINT arp_pk PRIMARY KEY(REPORT_PARAMETER_ID)
);

-- ART_QUERY_FIELDS
-- Stores query parameters

-- FIELD_POSITION is the order the parameter is displayed to users
-- FIELD_CLASS stores the data type of the parameter
-- PARAM_TYPE: M for MULTI param, I for INLINE param
-- PARAM_LABEL stores the column name for non-labelled MULTI params
-- or the parameter label for INLINE params or labelled multi params
-- USE_LOV is set to Y if the param values are provided by an LOV query
-- CHAINED_PARAM_POSITION is the position of the chained param
-- CHAINED_VALUE_POSITION - allow chained parameter value to come from
-- a different parameter from the previous one in the chained parameter sequence
-- DRILLDOWN_COLUMN - if used in a drill down report, refers to the column in
-- the parent report on which the parameter will be applied

CREATE TABLE ART_QUERY_FIELDS
(
	QUERY_ID                INTEGER     NOT NULL,
	FIELD_POSITION          INTEGER     NOT NULL,
	NAME                    VARCHAR(25),
	SHORT_DESCRIPTION       VARCHAR(40),
	DESCRIPTION             VARCHAR(120),
	PARAM_TYPE VARCHAR(1) NOT NULL,
	PARAM_LABEL     VARCHAR(55),
	PARAM_DATA_TYPE         VARCHAR(15) NOT NULL,
	DEFAULT_VALUE           VARCHAR(80),
	USE_LOV       VARCHAR(1),
	APPLY_RULES_TO_LOV        VARCHAR(1),
	LOV_QUERY_ID  INTEGER,
	CHAINED_PARAM_POSITION  INTEGER,
	CHAINED_VALUE_POSITION INTEGER,
	DRILLDOWN_COLUMN INTEGER,
	DIRECT_SUBSTITUTION VARCHAR(1),
	MIGRATED INTEGER,
	UPDATE_DATE TIMESTAMP,
	CONSTRAINT aqf_pk PRIMARY KEY(QUERY_ID, FIELD_POSITION)
);

-- ART_ALL_SOURCES
-- Stores source code for queries (sql, mdx, xml, html, text)

CREATE TABLE ART_ALL_SOURCES
(
	OBJECT_ID              INTEGER      NOT NULL,
	LINE_NUMBER            INTEGER      NOT NULL,
	SOURCE_INFO              VARCHAR(4000),
	CONSTRAINT aas_pk PRIMARY KEY(OBJECT_ID, LINE_NUMBER)
);

-- ART_RULES
-- Stores Rule definitions

CREATE TABLE ART_RULES
(
	RULE_ID INTEGER NOT NULL,
	RULE_NAME VARCHAR(50) NOT NULL,
	DESCRIPTION VARCHAR(200),
	DATA_TYPE VARCHAR(30),
	CREATION_DATE TIMESTAMP,
	CREATED_BY VARCHAR(50),
	UPDATE_DATE TIMESTAMP,
	UPDATED_BY VARCHAR(50),
	CONSTRAINT ar_pk PRIMARY KEY(RULE_ID),
	CONSTRAINT ar_rname_uq UNIQUE(RULE_NAME)
);

-- ART_QUERY_RULES
-- Stores rules-query relationships

CREATE TABLE ART_QUERY_RULES
(
	QUERY_RULE_ID INTEGER NOT NULL,
	QUERY_ID INTEGER NOT NULL,
	RULE_ID INTEGER,
	RULE_NAME VARCHAR(50) NOT NULL,
	FIELD_NAME VARCHAR(100),
	FIELD_DATA_TYPE VARCHAR(15),
	CONSTRAINT aqr_pk PRIMARY KEY(QUERY_ID, RULE_NAME)
);

-- ART_USER_RULES
-- Stores rule values for users
-- RULE_TYPE can be EXACT or LOOKUP

CREATE TABLE ART_USER_RULES
(
	RULE_VALUE_KEY VARCHAR(50) NOT NULL,
	USER_ID INTEGER NOT NULL,
	USERNAME VARCHAR(50) NOT NULL,
	RULE_ID INTEGER,
	RULE_NAME VARCHAR(50) NOT NULL,
	RULE_VALUE VARCHAR(100) NOT NULL,
	RULE_TYPE VARCHAR(6)
);

-- ART_USER_GROUP_RULES
-- Stores rule values for user groups
-- RULE_TYPE can be EXACT or LOOKUP

CREATE TABLE ART_USER_GROUP_RULES
(
	RULE_VALUE_KEY VARCHAR(50) NOT NULL,
	USER_GROUP_ID INTEGER NOT NULL,
	RULE_ID INTEGER,
	RULE_NAME VARCHAR(50) NOT NULL,
	RULE_VALUE VARCHAR(100) NOT NULL,
	RULE_TYPE VARCHAR(6)
);

-- ART_JOBS
-- Stores scheduled jobs

-- OUTPUT_FORMAT: html, pdf, xls etc
-- LAST_FILE_NAME: Contains result of last job execution
-- MIGRATED_TO_QUARTZ is present to allow seamless migration of jobs when
-- upgrading from ART versions before 1.11 (before quartz was used as the scheduling engine)
-- ACTIVE: boolean. 0=false, 1=true

CREATE TABLE ART_JOBS
(
	JOB_ID INTEGER NOT NULL,
	JOB_NAME VARCHAR(50),
	QUERY_ID	    INTEGER NOT NULL,
	USER_ID INTEGER,
	USERNAME          VARCHAR(50) NOT NULL,
	OUTPUT_FORMAT            VARCHAR(50) NOT NULL,
	JOB_TYPE VARCHAR(50),
	JOB_SECOND VARCHAR(100),
	JOB_MINUTE	    VARCHAR(100),
	JOB_HOUR		    VARCHAR(100),
	JOB_DAY		    VARCHAR(100),
	JOB_MONTH		    VARCHAR(100),
	JOB_WEEKDAY	    VARCHAR(100),
	JOB_YEAR VARCHAR(100),
	TIME_ZONE VARCHAR(50),
	MAIL_TOS          VARCHAR(254),
	MAIL_FROM         VARCHAR(80),
	MAIL_CC VARCHAR(254),
	MAIL_BCC VARCHAR(254),
	SUBJECT	    VARCHAR(1000),
	MESSAGE CLOB,
	CACHED_DATASOURCE_ID INTEGER,
	CACHED_TABLE_NAME VARCHAR(30),
	START_DATE TIMESTAMP,
	END_DATE TIMESTAMP,
	NEXT_RUN_DATE TIMESTAMP NULL,
	LAST_FILE_NAME    VARCHAR(200),
	LAST_RUN_MESSAGE VARCHAR(100),
	LAST_RUN_DETAILS    VARCHAR(4000),
	LAST_START_DATE   TIMESTAMP NULL,
	LAST_END_DATE     TIMESTAMP NULL,
	ACTIVE INTEGER,
	ENABLE_AUDIT INTEGER,
	ALLOW_SHARING INTEGER,
	ALLOW_SPLITTING INTEGER,
	RECIPIENTS_QUERY_ID INTEGER,
	RUNS_TO_ARCHIVE INTEGER,
	MIGRATED_TO_QUARTZ VARCHAR(1),
	FIXED_FILE_NAME VARCHAR(1000),
	SUB_DIRECTORY VARCHAR(100),
	BATCH_FILE VARCHAR(50),
	FTP_SERVER_ID INTEGER,
	EMAIL_TEMPLATE VARCHAR(100),
	EXTRA_SCHEDULES CLOB,
	HOLIDAYS CLOB,
	QUARTZ_CALENDAR_NAMES VARCHAR(100),
	SCHEDULE_ID INTEGER,
	SMTP_SERVER_ID INTEGER,
	JOB_OPTIONS CLOB,
	ERROR_EMAIL_TO VARCHAR(500),
	PRE_RUN_REPORT VARCHAR(50),
	POST_RUN_REPORT VARCHAR(50),
	CREATION_DATE TIMESTAMP,
	CREATED_BY VARCHAR(50),
	UPDATE_DATE TIMESTAMP,
	UPDATED_BY VARCHAR(50),
	CONSTRAINT aj_pk PRIMARY KEY(JOB_ID)
);

-- ART_JOBS_PARAMETERS
-- store jobs parameters

-- PARAM_TYPE: M = multi, I = inline
-- PARAM_NAME: the html element name of the parameter

CREATE TABLE ART_JOBS_PARAMETERS
(
	JOB_ID        INTEGER NOT NULL,
	PARAM_TYPE	VARCHAR(1) NOT NULL,
	PARAM_NAME		    VARCHAR(60),
	PARAM_VALUE		    VARCHAR(4000)
);

-- ART_JOBS_AUDIT
-- stores logs of every job execution when job auditing is enabled

-- USERNAME: user for whom the job is run
-- JOB_AUDIT_KEY: unique identifier for a job audit record
-- ACTION: S = job started, E = job ended, X = Error occurred while running job

CREATE TABLE ART_JOBS_AUDIT
(
	JOB_ID            INTEGER NOT NULL,
	USER_ID INTEGER,
	USERNAME VARCHAR(50),
	JOB_AUDIT_KEY VARCHAR(100),
	JOB_ACTION   VARCHAR(1),
	START_DATE TIMESTAMP NULL,
	END_DATE TIMESTAMP NULL
);

-- ART_LOGS
-- Stores log information e.g. logins and report execution

-- LOG_TYPE: login = successful login, loginerr = unsuccessful login attempt
-- report = interactive report execution, upload = template file uploaded when
-- creating query that uses a template file
-- TOTAL_TIME: total execution time in secs, including fetch time and display time
-- FETCH_TIME: time elapsed from when the query is submitted to when the
-- database returns 1st row

CREATE TABLE ART_LOGS
(
	LOG_DATE TIMESTAMP NOT NULL,
	USERNAME VARCHAR(50) NOT NULL,
	LOG_TYPE VARCHAR(50) NOT NULL,
	IP VARCHAR(50),
	QUERY_ID INTEGER,
	TOTAL_TIME INTEGER,
	FETCH_TIME INTEGER,
	MESSAGE VARCHAR(500)
);

-- ART_USER_JOBS
-- Stores users who have been given access to a job's output

-- USER_GROUP_ID: used to indicate if job was shared via user group. To enable
-- deletion of split job records where access was granted via user group,
-- when a user is removed from a group.
-- LAST_FILE_NAME: contains file name for individualized output (split job),
-- or NULL if file name to use comes from ART_JOBS table

CREATE TABLE ART_USER_JOBS
(
	JOB_ID INTEGER NOT NULL,
	USER_ID INTEGER,
	USERNAME VARCHAR(50) NOT NULL,
	USER_GROUP_ID INTEGER,
	LAST_FILE_NAME VARCHAR(4000),
	LAST_RUN_MESSAGE VARCHAR(100),
	LAST_RUN_DETAILS VARCHAR(4000),
	LAST_START_DATE TIMESTAMP NULL,
	LAST_END_DATE TIMESTAMP NULL,
	CONSTRAINT auj_pk PRIMARY KEY(JOB_ID, USERNAME)
);

-- ART_JOB_SCHEDULES
-- Stores job schedules to enable re-use of schedules when creating jobs

CREATE TABLE ART_JOB_SCHEDULES
(
	SCHEDULE_ID INTEGER NOT NULL,
	SCHEDULE_NAME VARCHAR(50) NOT NULL,
	DESCRIPTION VARCHAR(200),
	JOB_SECOND VARCHAR(100),
	JOB_MINUTE	    VARCHAR(100),
	JOB_HOUR		    VARCHAR(100),
	JOB_DAY		    VARCHAR(100),
	JOB_MONTH		    VARCHAR(100),
	JOB_WEEKDAY	    VARCHAR(100),
	JOB_YEAR VARCHAR(100),
	TIME_ZONE VARCHAR(50),
	EXTRA_SCHEDULES CLOB,
	HOLIDAYS CLOB,
	CREATION_DATE TIMESTAMP,
	CREATED_BY VARCHAR(50),
	UPDATE_DATE TIMESTAMP,
	UPDATED_BY VARCHAR(50),
	CONSTRAINT ajs_pk PRIMARY KEY(SCHEDULE_ID),
	CONSTRAINT ajs_sname_uq UNIQUE(SCHEDULE_NAME)
);

-- ART_USER_GROUP_QUERIES
-- Stores which queries certain user groups can access (users who are members of
-- the group can access the queries)

CREATE TABLE ART_USER_GROUP_QUERIES
(
	USER_GROUP_ID INTEGER NOT NULL,
	QUERY_ID INTEGER NOT NULL,
	CONSTRAINT augq_pk PRIMARY KEY(USER_GROUP_ID, QUERY_ID)
);

-- ART_USER_GROUP_GROUPS
-- Stores which query groups certain user groups can access (users who are members
-- of the group can access the query groups)

CREATE TABLE ART_USER_GROUP_GROUPS
(
	USER_GROUP_ID INTEGER NOT NULL,
	QUERY_GROUP_ID INTEGER NOT NULL,
	CONSTRAINT augg_pk PRIMARY KEY(USER_GROUP_ID, QUERY_GROUP_ID)
);

-- ART_USER_GROUP_JOBS
-- Stores which jobs have been shared with certain user groups (users who are
-- members of the group can access the job output)

CREATE TABLE ART_USER_GROUP_JOBS
(
	USER_GROUP_ID INTEGER NOT NULL,
	JOB_ID INTEGER NOT NULL,
	CONSTRAINT augj_pk PRIMARY KEY(USER_GROUP_ID, JOB_ID)
);

-- ART_DRILLDOWN_QUERIES
-- Stores details of drill down queries

-- OPEN_IN_NEW_WINDOW: boolean

CREATE TABLE ART_DRILLDOWN_QUERIES
(
	DRILLDOWN_ID INTEGER NOT NULL,
	QUERY_ID INTEGER NOT NULL,
	DRILLDOWN_QUERY_ID INTEGER NOT NULL,
	DRILLDOWN_QUERY_POSITION INTEGER NOT NULL,
	DRILLDOWN_TITLE VARCHAR(50),
	DRILLDOWN_TEXT VARCHAR(50),
	OUTPUT_FORMAT VARCHAR(50),
	OPEN_IN_NEW_WINDOW INTEGER,
	CONSTRAINT adq_pk PRIMARY KEY(QUERY_ID, DRILLDOWN_QUERY_POSITION)
);

-- ART_JOB_ARCHIVES
-- Stored details of past runs for publish jobs

-- JOB_SHARED: N = job not shared, Y = job shared, S = split job

CREATE TABLE ART_JOB_ARCHIVES
(
	ARCHIVE_ID VARCHAR(100) NOT NULL,
	JOB_ID INTEGER NOT NULL,
	USER_ID INTEGER,
	USERNAME VARCHAR(50) NOT NULL,
	ARCHIVE_FILE_NAME VARCHAR(4000),
	START_DATE TIMESTAMP NULL,
	END_DATE TIMESTAMP NULL,
	JOB_SHARED VARCHAR(1),
	CONSTRAINT aja_pk PRIMARY KEY(ARCHIVE_ID)
);

-- ART_LOGGED_IN_USERS
-- Stores approximate indication of the currently logged in users

CREATE TABLE ART_LOGGED_IN_USERS
(
	LOGGED_IN_USERS_ID VARCHAR(100) NOT NULL,
	USER_ID INTEGER NOT NULL,
	USERNAME VARCHAR(50) NOT NULL,
	LOGIN_DATE TIMESTAMP NULL,
	IP_ADDRESS VARCHAR(50),
	CONSTRAINT alu_pk PRIMARY KEY(LOGGED_IN_USERS_ID)
);

-- ART_FTP_SERVERS
-- Stores configurations for ftp servers

-- ACTIVE: boolean

CREATE TABLE ART_FTP_SERVERS
(
	FTP_SERVER_ID INTEGER NOT NULL,
	NAME VARCHAR(50),
	DESCRIPTION VARCHAR(200),
	ACTIVE INTEGER,
	CONNECTION_TYPE VARCHAR(20),
	SERVER VARCHAR(100),
	PORT INTEGER,
	FTP_USER VARCHAR(50),
	PASSWORD VARCHAR(100),
	REMOTE_DIRECTORY VARCHAR(200),
	CREATION_DATE TIMESTAMP,
	CREATED_BY VARCHAR(50),
	UPDATE_DATE TIMESTAMP,
	UPDATED_BY VARCHAR(50),
	CONSTRAINT afs_pk PRIMARY KEY(FTP_SERVER_ID),
	CONSTRAINT afs_name_uq UNIQUE(NAME)
);

-- ART_ENCRYPTORS
-- Stores configurations for file encryptors

-- ACTIVE: boolean

CREATE TABLE ART_ENCRYPTORS
(
	ENCRYPTOR_ID INTEGER NOT NULL,
	NAME VARCHAR(50),
	DESCRIPTION VARCHAR(200),
	ACTIVE INTEGER,
	ENCRYPTOR_TYPE VARCHAR(50),
	AESCRYPT_PASSWORD VARCHAR(200),
	OPENPGP_PUBLIC_KEY_FILE VARCHAR(100),
	OPENPGP_PUBLIC_KEY_STRING CLOB,
	OPENPGP_SIGNING_KEY_FILE VARCHAR(100),
	OPENPGP_SIGNING_KEY_PASSPHRASE VARCHAR(1000),
	OPEN_PASSWORD VARCHAR(200),
	MODIFY_PASSWORD VARCHAR(200),
	CREATION_DATE TIMESTAMP,
	CREATED_BY VARCHAR(50),
	UPDATE_DATE TIMESTAMP,
	UPDATED_BY VARCHAR(50),
	CONSTRAINT ae_pk PRIMARY KEY(ENCRYPTOR_ID),
	CONSTRAINT ae_name_uq UNIQUE(NAME)
);

-- ART_HOLIDAYS
-- Stores configuration of schedule holidays

CREATE TABLE ART_HOLIDAYS
(
	HOLIDAY_ID INTEGER NOT NULL,
	NAME VARCHAR(50),
	DESCRIPTION VARCHAR(200),
	HOLIDAY_DEFINITION CLOB,
	CREATION_DATE TIMESTAMP,
	CREATED_BY VARCHAR(50),
	UPDATE_DATE TIMESTAMP,
	UPDATED_BY VARCHAR(50),
	CONSTRAINT ah_pk PRIMARY KEY(HOLIDAY_ID),
	CONSTRAINT ah_name_uq UNIQUE(NAME)
);

-- ART_SCHEDULE_HOLIDAY_MAP
-- Stores schedule-holiday records

CREATE TABLE ART_SCHEDULE_HOLIDAY_MAP
(
	SCHEDULE_ID INTEGER NOT NULL,
	HOLIDAY_ID INTEGER NOT NULL,
	CONSTRAINT ashm_pk PRIMARY KEY(SCHEDULE_ID, HOLIDAY_ID)
);

-- ART_JOB_HOLIDAY_MAP
-- Stores job-holiday records

CREATE TABLE ART_JOB_HOLIDAY_MAP
(
	JOB_ID INTEGER NOT NULL,
	HOLIDAY_ID INTEGER NOT NULL,
	CONSTRAINT ajhm_pk PRIMARY KEY(JOB_ID, HOLIDAY_ID)
);

-- ART_DESTINATIONS
-- Stores job destination details

-- ACTIVE: boolean
-- CREATE_DIRECTORIES: boolean

CREATE TABLE ART_DESTINATIONS
(
	DESTINATION_ID INTEGER NOT NULL,
	NAME VARCHAR(50),
	DESCRIPTION VARCHAR(200),
	ACTIVE INTEGER,
	DESTINATION_TYPE VARCHAR(50),
	SERVER VARCHAR(100),
	PORT INTEGER,
	DESTINATION_USER VARCHAR(50),
	DESTINATION_PASSWORD VARCHAR(200),
	USER_DOMAIN VARCHAR(100),
	DESTINATION_PATH VARCHAR(1000),
	SUB_DIRECTORY VARCHAR(100),
	CREATE_DIRECTORIES INTEGER,
	DESTINATION_OPTIONS VARCHAR(4000),
	GOOGLE_JSON_KEY_FILE VARCHAR(100),
	CREATION_DATE TIMESTAMP,
	CREATED_BY VARCHAR(50),
	UPDATE_DATE TIMESTAMP,
	UPDATED_BY VARCHAR(50),
	CONSTRAINT adst_pk PRIMARY KEY(DESTINATION_ID),
	CONSTRAINT adst_name_uq UNIQUE(NAME)
);

-- ART_JOB_DESTINATION_MAP
-- Stores job-destination records

CREATE TABLE ART_JOB_DESTINATION_MAP
(
	JOB_ID INTEGER NOT NULL,
	DESTINATION_ID INTEGER NOT NULL,
	CONSTRAINT ajdm_pk PRIMARY KEY(JOB_ID, DESTINATION_ID)
);

-- ART_SMTP_SERVERS
-- Stores configurations for smtp servers

-- ACTIVE: boolean
-- USE_STARTTLS: boolean
-- USE_SMTP_AUTHENTICATION: boolean

CREATE TABLE ART_SMTP_SERVERS
(
	SMTP_SERVER_ID INTEGER NOT NULL,
	NAME VARCHAR(50),
	DESCRIPTION VARCHAR(200),
	ACTIVE INTEGER,
	SERVER VARCHAR(100),
	PORT INTEGER,
	USE_STARTTLS INTEGER,
	USE_SMTP_AUTHENTICATION INTEGER,
	USERNAME VARCHAR(100),
	PASSWORD VARCHAR(200),
	SMTP_FROM VARCHAR(100),
	CREATION_DATE TIMESTAMP,
	CREATED_BY VARCHAR(50),
	UPDATE_DATE TIMESTAMP,
	UPDATED_BY VARCHAR(50),
	CONSTRAINT art_ss_pk PRIMARY KEY(SMTP_SERVER_ID),
	CONSTRAINT art_ss_name_uq UNIQUE(NAME)
);

-- ART_SETTINGS
-- Stores application settings

-- SMTP_USE_STARTTLS: boolean
-- USE_SMTP_AUTHENTICATION: boolean
-- USE_LDAP_ANONYMOUS_BIND: boolean
-- PDF_FONT_EMBEDDED: boolean
-- HEADER_IN_PUBLIC_SESSION: boolean
-- SCHEDULING_ENABLED: boolean
-- DIRECT_REPORT_EMAILING: boolean

CREATE TABLE ART_SETTINGS
(
	SMTP_SERVER VARCHAR(100),
	SMTP_PORT INTEGER,
	SMTP_USE_STARTTLS INTEGER,
	USE_SMTP_AUTHENTICATION INTEGER,
	SMTP_USERNAME VARCHAR(100),
	SMTP_PASSWORD VARCHAR(200),
	SMTP_FROM VARCHAR(100),
	ART_AUTHENTICATION_METHOD VARCHAR(50),
	WINDOWS_DOMAIN_CONTROLLER VARCHAR(100),
	ALLOWED_WINDOWS_DOMAINS VARCHAR(200),
	DB_AUTHENTICATION_DRIVER VARCHAR(100),
	DB_AUTHENTICATION_URL VARCHAR(500),
	LDAP_SERVER VARCHAR(100),
	LDAP_PORT INTEGER,
	LDAP_ENCRYPTION_METHOD VARCHAR(50),
	LDAP_URL VARCHAR(500),
	LDAP_BASE_DN VARCHAR(500),
	USE_LDAP_ANONYMOUS_BIND INTEGER,
	LDAP_BIND_DN VARCHAR(500),
	LDAP_BIND_PASSWORD VARCHAR(200),
	LDAP_USER_ID_ATTRIBUTE VARCHAR(50),
	LDAP_AUTHENTICATION_METHOD VARCHAR(50),
	LDAP_REALM VARCHAR(200),
	CAS_LOGOUT_URL VARCHAR(100),
	MAX_ROWS_DEFAULT INTEGER,
	MAX_ROWS_SPECIFIC VARCHAR(500),
	PDF_FONT_NAME VARCHAR(50),
	PDF_FONT_FILE VARCHAR(500),
	PDF_FONT_DIRECTORY VARCHAR(500),
	PDF_FONT_ENCODING VARCHAR(50),
	PDF_FONT_EMBEDDED INTEGER,
	ADMIN_EMAIL VARCHAR(100),
	APP_DATE_FORMAT VARCHAR(50),
	APP_TIME_FORMAT VARCHAR(50),
	REPORT_FORMATS VARCHAR(200),
	MAX_RUNNING_REPORTS INTEGER,
	HEADER_IN_PUBLIC_SESSION INTEGER,
	MONDRIAN_CACHE_EXPIRY INTEGER,
	SCHEDULING_ENABLED INTEGER,
	RSS_LINK VARCHAR(500),
	MAX_FILE_UPLOAD_SIZE INTEGER,
	ART_BASE_URL VARCHAR(500),
	SYSTEM_LOCALE VARCHAR(50),
	LOGS_DATASOURCE_ID INTEGER,
	ERROR_EMAIL_TO VARCHAR(500),
	ERROR_EMAIL_FROM VARCHAR(100),
	ERROR_EMAIL_SUBJECT_PATTERN VARCHAR(50),
	ERROR_EMAIL_LEVEL VARCHAR(10),
	ERROR_EMAIL_LOGGER VARCHAR(200),
	ERROR_EMAIL_SUPPRESS_AFTER VARCHAR(30),
	ERROR_EMAIL_EXPIRE_AFTER VARCHAR(20),
	ERROR_EMAIL_DIGEST_FREQUENCY VARCHAR(20),
	PASSWORD_MIN_LENGTH INTEGER,
	PASSWORD_MIN_LOWERCASE INTEGER,
	PASSWORD_MIN_UPPERCASE INTEGER,
	PASSWORD_MIN_NUMERIC INTEGER,
	PASSWORD_MIN_SPECIAL INTEGER,
	JWT_TOKEN_EXPIRY INTEGER,
	DIRECT_REPORT_EMAILING INTEGER,
	UPDATE_DATE TIMESTAMP,
	UPDATED_BY VARCHAR(50)
);

-- ART_SAVED_PARAMETERS
-- Stores report parameter selections for users

CREATE TABLE ART_SAVED_PARAMETERS
(
	USER_ID INTEGER NOT NULL,
	REPORT_ID INTEGER NOT NULL,
	PARAM_NAME VARCHAR(60) NOT NULL,
	PARAM_VALUE VARCHAR(4000)
);

-- ART_USER_PARAM_DEFAULTS
-- Stores user parameter default values

CREATE TABLE ART_USER_PARAM_DEFAULTS
(
	PARAM_DEFAULT_KEY VARCHAR(50) NOT NULL,
	USER_ID INTEGER NOT NULL,
	PARAMETER_ID INTEGER NOT NULL,
	PARAM_VALUE VARCHAR(4000)
);

-- ART_USER_GROUP_PARAM_DEFAULTS
-- Stores user group parameter default values

CREATE TABLE ART_USER_GROUP_PARAM_DEFAULTS
(
	PARAM_DEFAULT_KEY VARCHAR(50) NOT NULL,
	USER_GROUP_ID INTEGER NOT NULL,
	PARAMETER_ID INTEGER NOT NULL,
	PARAM_VALUE VARCHAR(4000)
);

-- ART_USER_FIXED_PARAM_VAL
-- Stores user fixed parameter values

CREATE TABLE ART_USER_FIXED_PARAM_VAL
(
	FIXED_VALUE_KEY VARCHAR(50) NOT NULL,
	USER_ID INTEGER NOT NULL,
	PARAMETER_ID INTEGER NOT NULL,
	PARAM_VALUE VARCHAR(4000)
);

-- ART_USER_GROUP_FIXED_PARAM_VAL
-- Stores user group fixed parameter values

CREATE TABLE ART_USER_GROUP_FIXED_PARAM_VAL
(
	FIXED_VALUE_KEY VARCHAR(50) NOT NULL,
	USER_GROUP_ID INTEGER NOT NULL,
	PARAMETER_ID INTEGER NOT NULL,
	PARAM_VALUE VARCHAR(4000)
);

-- ART_ROLES
-- Stores roles

CREATE TABLE ART_ROLES
(
	ROLE_ID INTEGER NOT NULL,
	NAME VARCHAR(50) NOT NULL,
	DESCRIPTION VARCHAR(200),
	CREATION_DATE TIMESTAMP,
	CREATED_BY VARCHAR(50),
	UPDATE_DATE TIMESTAMP,
	UPDATED_BY VARCHAR(50),
	CONSTRAINT art_rls_pk PRIMARY KEY(ROLE_ID),
	CONSTRAINT art_rls_uq_nm UNIQUE(NAME)
);

-- insert default roles
INSERT INTO ART_ROLES (ROLE_ID, NAME) VALUES(1, 'Admin');
INSERT INTO ART_ROLES (ROLE_ID, NAME) VALUES(2, 'User');

-- ART_PERMISSIONS
-- Stores permissions

CREATE TABLE ART_PERMISSIONS
(
	PERMISSION_ID INTEGER NOT NULL,
	NAME VARCHAR(100) NOT NULL,
	CONSTRAINT art_pm_pk PRIMARY KEY(PERMISSION_ID),
	CONSTRAINT art_pm_uq_nm UNIQUE(NAME)
);

-- insert permissions
INSERT INTO ART_PERMISSIONS VALUES(1, 'view_reports');
INSERT INTO ART_PERMISSIONS VALUES(2, 'save_reports');
INSERT INTO ART_PERMISSIONS VALUES(3, 'self_service_dashboards');
INSERT INTO ART_PERMISSIONS VALUES(4, 'schedule_jobs');
INSERT INTO ART_PERMISSIONS VALUES(5, 'view_jobs');
INSERT INTO ART_PERMISSIONS VALUES(6, 'configure_jobs');
INSERT INTO ART_PERMISSIONS VALUES(7, 'view_logs');
INSERT INTO ART_PERMISSIONS VALUES(8, 'configure_users');
INSERT INTO ART_PERMISSIONS VALUES(9, 'configure_art_database');
INSERT INTO ART_PERMISSIONS VALUES(10, 'configure_settings');
INSERT INTO ART_PERMISSIONS VALUES(11, 'configure_user_groups');
INSERT INTO ART_PERMISSIONS VALUES(12, 'configure_datasources');
INSERT INTO ART_PERMISSIONS VALUES(13, 'configure_reports');
INSERT INTO ART_PERMISSIONS VALUES(14, 'configure_caches');
INSERT INTO ART_PERMISSIONS VALUES(15, 'configure_connections');
INSERT INTO ART_PERMISSIONS VALUES(16, 'configure_loggers');
INSERT INTO ART_PERMISSIONS VALUES(17, 'configure_report_groups');
INSERT INTO ART_PERMISSIONS VALUES(18, 'configure_schedules');
INSERT INTO ART_PERMISSIONS VALUES(19, 'configure_holidays');
INSERT INTO ART_PERMISSIONS VALUES(20, 'configure_destinations');
INSERT INTO ART_PERMISSIONS VALUES(21, 'configure_admin_rights');
INSERT INTO ART_PERMISSIONS VALUES(22, 'configure_access_rights');
INSERT INTO ART_PERMISSIONS VALUES(23, 'configure_user_group_membership');
INSERT INTO ART_PERMISSIONS VALUES(24, 'configure_report_group_membership');
INSERT INTO ART_PERMISSIONS VALUES(25, 'configure_smtp_servers');
INSERT INTO ART_PERMISSIONS VALUES(26, 'configure_encryptors');
INSERT INTO ART_PERMISSIONS VALUES(27, 'migrate_records');
INSERT INTO ART_PERMISSIONS VALUES(28, 'configure_roles');
INSERT INTO ART_PERMISSIONS VALUES(29, 'configure_permissions');

-- ART_ROLE_PERMISSION_MAP
-- Stores role - permission mappings

CREATE TABLE ART_ROLE_PERMISSION_MAP
(
	ROLE_ID INTEGER NOT NULL,
	PERMISSION_ID INTEGER NOT NULL,
	CONSTRAINT art_rpm_pk PRIMARY KEY(ROLE_ID, PERMISSION_ID)
);

-- insert default role permissions
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 1);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 2);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 3);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 4);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 5);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 6);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 7);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 8);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 9);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 10);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 11);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 12);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 13);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 14);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 15);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 16);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 17);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 18);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 19);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 20);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 21);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 22);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 23);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 24);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 25);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 26);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 27);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 28);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(1, 29);
INSERT INTO ART_ROLE_PERMISSION_MAP VALUES(2, 1);

-- ART_USER_ROLE_MAP
-- Stores user - role mappings

CREATE TABLE ART_USER_ROLE_MAP
(
	USER_ID INTEGER NOT NULL,
	ROLE_ID INTEGER NOT NULL,
	CONSTRAINT art_urm_pk PRIMARY KEY(USER_ID, ROLE_ID)
);

-- ART_USER_GROUP_ROLE_MAP
-- Stores user group - role mappings

CREATE TABLE ART_USER_GROUP_ROLE_MAP
(
	USER_GROUP_ID INTEGER NOT NULL,
	ROLE_ID INTEGER NOT NULL,
	CONSTRAINT art_ugrm_pk PRIMARY KEY(USER_GROUP_ID, ROLE_ID)
);

-- ART_USER_PERMISSION_MAP
-- Stores user - permission mappings

CREATE TABLE ART_USER_PERMISSION_MAP
(
	USER_ID INTEGER NOT NULL,
	PERMISSION_ID INTEGER NOT NULL,
	CONSTRAINT art_upm_pk PRIMARY KEY(USER_ID, PERMISSION_ID)
);

-- ART_USER_GROUP_PERM_MAP
-- Stores user group - permission mappings

CREATE TABLE ART_USER_GROUP_PERM_MAP
(
	USER_GROUP_ID INTEGER NOT NULL,
	PERMISSION_ID INTEGER NOT NULL,
	CONSTRAINT art_ugpm_pk PRIMARY KEY(USER_GROUP_ID, PERMISSION_ID)
);


text:
import {
    arraysEqual,
    ArrowFunction,
    AssignmentDeclarationKind,
    BinaryExpression,
    buildLinkParts,
    ClassExpression,
    CompletionEntry,
    CompletionEntryDetails,
    Completions,
    ConstructorDeclaration,
    contains,
    Declaration,
    DocCommentTemplateOptions,
    emptyArray,
    Expression,
    ExpressionStatement,
    find,
    findAncestor,
    flatMap,
    flatten,
    forEach,
    forEachAncestor,
    forEachReturnStatement,
    forEachUnique,
    FunctionDeclaration,
    FunctionExpression,
    getAssignmentDeclarationKind,
    getJSDocCommentsAndTags,
    getJSDocTags,
    getLineStartPositionForPosition,
    getTokenAtPosition,
    hasJSDocNodes,
    hasJSFileExtension,
    intersperse,
    isArrowFunction,
    isBlock,
    isConstructorDeclaration,
    isExpression,
    isFunctionExpression,
    isFunctionLike,
    isFunctionLikeDeclaration,
    isFunctionTypeNode,
    isIdentifier,
    isJSDoc,
    isJSDocParameterTag,
    isWhiteSpaceSingleLine,
    JSDoc,
    JSDocAugmentsTag,
    JSDocCallbackTag,
    JSDocComment,
    JSDocImplementsTag,
    JSDocParameterTag,
    JSDocPropertyTag,
    JSDocSeeTag,
    JSDocTag,
    JSDocTagInfo,
    JSDocTemplateTag,
    JSDocTypedefTag,
    JSDocTypeTag,
    lastOrUndefined,
    length,
    lineBreakPart,
    map,
    mapDefined,
    MethodDeclaration,
    MethodSignature,
    Node,
    ParameterDeclaration,
    parameterNamePart,
    ParenthesizedExpression,
    PropertyAssignment,
    PropertyDeclaration,
    propertyNamePart,
    PropertySignature,
    punctuationPart,
    ScriptElementKind,
    SourceFile,
    spacePart,
    startsWith,
    SymbolDisplayPart,
    SyntaxKind,
    TextInsertion,
    textPart,
    typeAliasNamePart,
    TypeChecker,
    typeParameterNamePart,
    VariableStatement,
} from "./_namespaces/ts";

const jsDocTagNames = [
    "abstract",
    "access",
    "alias",
    "argument",
    "async",
    "augments",
    "author",
    "borrows",
    "callback",
    "class",
    "classdesc",
    "constant",
    "constructor",
    "constructs",
    "copyright",
    "default",
    "deprecated",
    "description",
    "emits",
    "enum",
    "event",
    "example",
    "exports",
    "extends",
    "external",
    "field",
    "file",
    "fileoverview",
    "fires",
    "function",
    "generator",
    "global",
    "hideconstructor",
    "host",
    "ignore",
    "implements",
    "inheritdoc",
    "inner",
    "instance",
    "interface",
    "kind",
    "lends",
    "license",
    "link",
    "listens",
    "member",
    "memberof",
    "method",
    "mixes",
    "module",
    "name",
    "namespace",
    "override",
    "package",
    "param",
    "private",
    "property",
    "protected",
    "public",
    "readonly",
    "requires",
    "returns",
    "see",
    "since",
    "static",
    "summary",
    "template",
    "this",
    "throws",
    "todo",
    "tutorial",
    "type",
    "typedef",
    "var",
    "variation",
    "version",
    "virtual",
    "yields"
];
let jsDocTagNameCompletionEntries: CompletionEntry[];
let jsDocTagCompletionEntries: CompletionEntry[];

/** @internal */
export function getJsDocCommentsFromDeclarations(declarations: readonly Declaration[], checker?: TypeChecker): SymbolDisplayPart[] {
    // Only collect doc comments from duplicate declarations once:
    // In case of a union property there might be same declaration multiple times
    // which only varies in type parameter
    // Eg. const a: Array<string> | Array<number>; a.length
    // The property length will have two declarations of property length coming
    // from Array<T> - Array<string> and Array<number>
    const parts: SymbolDisplayPart[][] = [];
    forEachUnique(declarations, declaration => {
        for (const jsdoc of getCommentHavingNodes(declaration)) {
            const inheritDoc = isJSDoc(jsdoc) && jsdoc.tags && find(jsdoc.tags, t => t.kind === SyntaxKind.JSDocTag && (t.tagName.escapedText === "inheritDoc" || t.tagName.escapedText === "inheritdoc"));
            // skip comments containing @typedefs since they're not associated with particular declarations
            // Exceptions:
            // - @typedefs are themselves declarations with associated comments
            // - @param or @return indicate that the author thinks of it as a 'local' @typedef that's part of the function documentation
            if (jsdoc.comment === undefined && !inheritDoc
                || isJSDoc(jsdoc)
                   && declaration.kind !== SyntaxKind.JSDocTypedefTag && declaration.kind !== SyntaxKind.JSDocCallbackTag
                   && jsdoc.tags
                   && jsdoc.tags.some(t => t.kind === SyntaxKind.JSDocTypedefTag || t.kind === SyntaxKind.JSDocCallbackTag)
                   && !jsdoc.tags.some(t => t.kind === SyntaxKind.JSDocParameterTag || t.kind === SyntaxKind.JSDocReturnTag)) {
                continue;
            }
            let newparts = jsdoc.comment ? getDisplayPartsFromComment(jsdoc.comment, checker) : [];
            if (inheritDoc && inheritDoc.comment) {
                newparts = newparts.concat(getDisplayPartsFromComment(inheritDoc.comment, checker));
            }
            if (!contains(parts, newparts, isIdenticalListOfDisplayParts)) {
                parts.push(newparts);
            }
        }
    });
    return flatten(intersperse(parts, [lineBreakPart()]));
}

function isIdenticalListOfDisplayParts(parts1: SymbolDisplayPart[], parts2: SymbolDisplayPart[]) {
    return arraysEqual(parts1, parts2, (p1, p2) => p1.kind === p2.kind && p1.text === p2.text);
}

function getCommentHavingNodes(declaration: Declaration): readonly (JSDoc | JSDocTag)[] {
    switch (declaration.kind) {
        case SyntaxKind.JSDocParameterTag:
        case SyntaxKind.JSDocPropertyTag:
            return [declaration as JSDocPropertyTag];
        case SyntaxKind.JSDocCallbackTag:
        case SyntaxKind.JSDocTypedefTag:
            return [(declaration as JSDocTypedefTag), (declaration as JSDocTypedefTag).parent];
        default:
            return getJSDocCommentsAndTags(declaration);
    }
}

/** @internal */
export function getJsDocTagsFromDeclarations(declarations?: Declaration[], checker?: TypeChecker): JSDocTagInfo[] {
    // Only collect doc comments from duplicate declarations once.
    const infos: JSDocTagInfo[] = [];
    forEachUnique(declarations, declaration => {
        const tags = getJSDocTags(declaration);
        // skip comments containing @typedefs since they're not associated with particular declarations
        // Exceptions:
        // - @param or @return indicate that the author thinks of it as a 'local' @typedef that's part of the function documentation
        if (tags.some(t => t.kind === SyntaxKind.JSDocTypedefTag || t.kind === SyntaxKind.JSDocCallbackTag)
            && !tags.some(t => t.kind === SyntaxKind.JSDocParameterTag || t.kind === SyntaxKind.JSDocReturnTag)) {
            return;
        }
        for (const tag of tags) {
            infos.push({ name: tag.tagName.text, text: getCommentDisplayParts(tag, checker) });
        }
    });
    return infos;
}

function getDisplayPartsFromComment(comment: string | readonly JSDocComment[], checker: TypeChecker | undefined): SymbolDisplayPart[] {
    if (typeof comment === "string") {
        return [textPart(comment)];
    }
    return flatMap(
        comment,
        node => node.kind === SyntaxKind.JSDocText ? [textPart(node.text)] : buildLinkParts(node, checker)
    ) as SymbolDisplayPart[];
}

function getCommentDisplayParts(tag: JSDocTag, checker?: TypeChecker): SymbolDisplayPart[] | undefined {
    const { comment, kind } = tag;
    const namePart = getTagNameDisplayPart(kind);
    switch (kind) {
        case SyntaxKind.JSDocImplementsTag:
            return withNode((tag as JSDocImplementsTag).class);
        case SyntaxKind.JSDocAugmentsTag:
            return withNode((tag as JSDocAugmentsTag).class);
        case SyntaxKind.JSDocTemplateTag:
            const templateTag = tag as JSDocTemplateTag;
            const displayParts: SymbolDisplayPart[] = [];
            if (templateTag.constraint) {
                displayParts.push(textPart(templateTag.constraint.getText()));
            }
            if (length(templateTag.typeParameters)) {
                if (length(displayParts)) {
                    displayParts.push(spacePart());
                }
                const lastTypeParameter = templateTag.typeParameters[templateTag.typeParameters.length - 1];
                forEach(templateTag.typeParameters, tp => {
                    displayParts.push(namePart(tp.getText()));
                    if (lastTypeParameter !== tp) {
                        displayParts.push(...[punctuationPart(SyntaxKind.CommaToken), spacePart()]);
                    }
                });
            }
            if (comment) {
                displayParts.push(...[spacePart(), ...getDisplayPartsFromComment(comment, checker)]);
            }
            return displayParts;
        case SyntaxKind.JSDocTypeTag:
            return withNode((tag as JSDocTypeTag).typeExpression);
        case SyntaxKind.JSDocTypedefTag:
        case SyntaxKind.JSDocCallbackTag:
        case SyntaxKind.JSDocPropertyTag:
        case SyntaxKind.JSDocParameterTag:
        case SyntaxKind.JSDocSeeTag:
            const { name } = tag as JSDocTypedefTag | JSDocCallbackTag | JSDocPropertyTag | JSDocParameterTag | JSDocSeeTag;
            return name ? withNode(name)
                : comment === undefined ? undefined
                : getDisplayPartsFromComment(comment, checker);
        default:
            return comment === undefined ? undefined : getDisplayPartsFromComment(comment, checker);
    }

    function withNode(node: Node) {
        return addComment(node.getText());
    }

    function addComment(s: string) {
        if (comment) {
            if (s.match(/^https?$/)) {
                return [textPart(s), ...getDisplayPartsFromComment(comment, checker)];
            }
            else {
                return [namePart(s), spacePart(), ...getDisplayPartsFromComment(comment, checker)];
            }
        }
        else {
            return [textPart(s)];
        }
    }
}

function getTagNameDisplayPart(kind: SyntaxKind): (text: string) => SymbolDisplayPart {
    switch (kind) {
        case SyntaxKind.JSDocParameterTag:
            return parameterNamePart;
        case SyntaxKind.JSDocPropertyTag:
            return propertyNamePart;
        case SyntaxKind.JSDocTemplateTag:
            return typeParameterNamePart;
        case SyntaxKind.JSDocTypedefTag:
        case SyntaxKind.JSDocCallbackTag:
            return typeAliasNamePart;
        default:
            return textPart;
    }
}

/** @internal */
export function getJSDocTagNameCompletions(): CompletionEntry[] {
    return jsDocTagNameCompletionEntries || (jsDocTagNameCompletionEntries = map(jsDocTagNames, tagName => {
        return {
            name: tagName,
            kind: ScriptElementKind.keyword,
            kindModifiers: "",
            sortText: Completions.SortText.LocationPriority,
        };
    }));
}

/** @internal */
export const getJSDocTagNameCompletionDetails = getJSDocTagCompletionDetails;

/** @internal */
export function getJSDocTagCompletions(): CompletionEntry[] {
    return jsDocTagCompletionEntries || (jsDocTagCompletionEntries = map(jsDocTagNames, tagName => {
        return {
            name: `@${tagName}`,
            kind: ScriptElementKind.keyword,
            kindModifiers: "",
            sortText: Completions.SortText.LocationPriority
        };
    }));
}

/** @internal */
export function getJSDocTagCompletionDetails(name: string): CompletionEntryDetails {
    return {
        name,
        kind: ScriptElementKind.unknown, // TODO: should have its own kind?
        kindModifiers: "",
        displayParts: [textPart(name)],
        documentation: emptyArray,
        tags: undefined,
        codeActions: undefined,
    };
}

/** @internal */
export function getJSDocParameterNameCompletions(tag: JSDocParameterTag): CompletionEntry[] {
    if (!isIdentifier(tag.name)) {
        return emptyArray;
    }
    const nameThusFar = tag.name.text;
    const jsdoc = tag.parent;
    const fn = jsdoc.parent;
    if (!isFunctionLike(fn)) return [];

    return mapDefined(fn.parameters, param => {
        if (!isIdentifier(param.name)) return undefined;

        const name = param.name.text;
        if (jsdoc.tags!.some(t => t !== tag && isJSDocParameterTag(t) && isIdentifier(t.name) && t.name.escapedText === name) // TODO: GH#18217
            || nameThusFar !== undefined && !startsWith(name, nameThusFar)) {
            return undefined;
        }

        return { name, kind: ScriptElementKind.parameterElement, kindModifiers: "", sortText: Completions.SortText.LocationPriority };
    });
}

/** @internal */
export function getJSDocParameterNameCompletionDetails(name: string): CompletionEntryDetails {
    return {
        name,
        kind: ScriptElementKind.parameterElement,
        kindModifiers: "",
        displayParts: [textPart(name)],
        documentation: emptyArray,
        tags: undefined,
        codeActions: undefined,
    };
}

/**
 * Checks if position points to a valid position to add JSDoc comments, and if so,
 * returns the appropriate template. Otherwise returns an empty string.
 * Valid positions are
 *      - outside of comments, statements, and expressions, and
 *      - preceding a:
 *          - function/constructor/method declaration
 *          - class declarations
 *          - variable statements
 *          - namespace declarations
 *          - interface declarations
 *          - method signatures
 *          - type alias declarations
 *
 * Hosts should ideally check that:
 * - The line is all whitespace up to 'position' before performing the insertion.
 * - If the keystroke sequence "/\*\*" induced the call, we also check that the next
 * non-whitespace character is '*', which (approximately) indicates whether we added
 * the second '*' to complete an existing (JSDoc) comment.
 * @param fileName The file in which to perform the check.
 * @param position The (character-indexed) position in the file where the check should
 * be performed.
 *
 * @internal
 */
export function getDocCommentTemplateAtPosition(newLine: string, sourceFile: SourceFile, position: number, options?: DocCommentTemplateOptions): TextInsertion | undefined {
    const tokenAtPos = getTokenAtPosition(sourceFile, position);
    const existingDocComment = findAncestor(tokenAtPos, isJSDoc);
    if (existingDocComment && (existingDocComment.comment !== undefined || length(existingDocComment.tags))) {
        // Non-empty comment already exists.
        return undefined;
    }

    const tokenStart = tokenAtPos.getStart(sourceFile);
    // Don't provide a doc comment template based on a *previous* node. (But an existing empty jsdoc comment will likely start before `position`.)
    if (!existingDocComment && tokenStart < position) {
        return undefined;
    }

    const commentOwnerInfo = getCommentOwnerInfo(tokenAtPos, options);
    if (!commentOwnerInfo) {
        return undefined;
    }

    const { commentOwner, parameters, hasReturn } = commentOwnerInfo;
    const commentOwnerJsDoc = hasJSDocNodes(commentOwner) && commentOwner.jsDoc ? commentOwner.jsDoc : undefined;
    const lastJsDoc = lastOrUndefined(commentOwnerJsDoc);
    if (commentOwner.getStart(sourceFile) < position
        || lastJsDoc
            && existingDocComment
            && lastJsDoc !== existingDocComment) {
        return undefined;
    }

    const indentationStr = getIndentationStringAtPosition(sourceFile, position);
    const isJavaScriptFile = hasJSFileExtension(sourceFile.fileName);
    const tags =
        (parameters ? parameterDocComments(parameters || [], isJavaScriptFile, indentationStr, newLine) : "") +
        (hasReturn ? returnsDocComment(indentationStr, newLine) : "");

    // A doc comment consists of the following
    // * The opening comment line
    // * the first line (without a param) for the object's untagged info (this is also where the caret ends up)
    // * the '@param'-tagged lines
    // * the '@returns'-tag
    // * TODO: other tags.
    // * the closing comment line
    // * if the caret was directly in front of the object, then we add an extra line and indentation.
    const openComment = "/**";
    const closeComment = " */";

    // If any of the existing jsDoc has tags, ignore adding new ones.
    const hasTag = (commentOwnerJsDoc || []).some(jsDoc => !!jsDoc.tags);

    if (tags && !hasTag) {
        const preamble = openComment + newLine + indentationStr + " * ";
        const endLine = tokenStart === position ? newLine + indentationStr : "";
        const result = preamble + newLine + tags + indentationStr + closeComment + endLine;
        return { newText: result, caretOffset: preamble.length };
    }
    return { newText: openComment + closeComment, caretOffset: 3 };
}

function getIndentationStringAtPosition(sourceFile: SourceFile, position: number): string {
    const { text } = sourceFile;
    const lineStart = getLineStartPositionForPosition(position, sourceFile);
    let pos = lineStart;
    for (; pos <= position && isWhiteSpaceSingleLine(text.charCodeAt(pos)); pos++);
    return text.slice(lineStart, pos);
}

function parameterDocComments(parameters: readonly ParameterDeclaration[], isJavaScriptFile: boolean, indentationStr: string, newLine: string): string {
    return parameters.map(({ name, dotDotDotToken }, i) => {
        const paramName = name.kind === SyntaxKind.Identifier ? name.text : "param" + i;
        const type = isJavaScriptFile ? (dotDotDotToken ? "{...any} " : "{any} ") : "";
        return `${indentationStr} * @param ${type}${paramName}${newLine}`;
    }).join("");
}

function returnsDocComment(indentationStr: string, newLine: string) {
    return `${indentationStr} * @returns${newLine}`;
}

interface CommentOwnerInfo {
    readonly commentOwner: Node;
    readonly parameters?: readonly ParameterDeclaration[];
    readonly hasReturn?: boolean;
}
function getCommentOwnerInfo(tokenAtPos: Node, options: DocCommentTemplateOptions | undefined): CommentOwnerInfo | undefined {
    return forEachAncestor(tokenAtPos, n => getCommentOwnerInfoWorker(n, options));
}
function getCommentOwnerInfoWorker(commentOwner: Node, options: DocCommentTemplateOptions | undefined): CommentOwnerInfo | undefined | "quit" {
    switch (commentOwner.kind) {
        case SyntaxKind.FunctionDeclaration:
        case SyntaxKind.FunctionExpression:
        case SyntaxKind.MethodDeclaration:
        case SyntaxKind.Constructor:
        case SyntaxKind.MethodSignature:
        case SyntaxKind.ArrowFunction:
            const host = commentOwner as ArrowFunction | FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | MethodSignature;
            return { commentOwner, parameters: host.parameters, hasReturn: hasReturn(host, options) };

        case SyntaxKind.PropertyAssignment:
            return getCommentOwnerInfoWorker((commentOwner as PropertyAssignment).initializer, options);

        case SyntaxKind.ClassDeclaration:
        case SyntaxKind.InterfaceDeclaration:
        case SyntaxKind.EnumDeclaration:
        case SyntaxKind.EnumMember:
        case SyntaxKind.TypeAliasDeclaration:
            return { commentOwner };

        case SyntaxKind.PropertySignature: {
            const host = commentOwner as PropertySignature;
            return host.type && isFunctionTypeNode(host.type)
                ? { commentOwner, parameters: host.type.parameters, hasReturn: hasReturn(host.type, options) }
                : { commentOwner };
        }

        case SyntaxKind.VariableStatement: {
            const varStatement = commentOwner as VariableStatement;
            const varDeclarations = varStatement.declarationList.declarations;
            const host = varDeclarations.length === 1 && varDeclarations[0].initializer
                ? getRightHandSideOfAssignment(varDeclarations[0].initializer)
                : undefined;
            return host
                ? { commentOwner, parameters: host.parameters, hasReturn: hasReturn(host, options) }
                : { commentOwner };
        }

        case SyntaxKind.SourceFile:
            return "quit";

        case SyntaxKind.ModuleDeclaration:
            // If in walking up the tree, we hit a a nested namespace declaration,
            // then we must be somewhere within a dotted namespace name; however we don't
            // want to give back a JSDoc template for the 'b' or 'c' in 'namespace a.b.c { }'.
            return commentOwner.parent.kind === SyntaxKind.ModuleDeclaration ? undefined : { commentOwner };

        case SyntaxKind.ExpressionStatement:
            return getCommentOwnerInfoWorker((commentOwner as ExpressionStatement).expression, options);
        case SyntaxKind.BinaryExpression: {
            const be = commentOwner as BinaryExpression;
            if (getAssignmentDeclarationKind(be) === AssignmentDeclarationKind.None) {
                return "quit";
            }
            return isFunctionLike(be.right)
                ? { commentOwner, parameters: be.right.parameters, hasReturn: hasReturn(be.right, options) }
                : { commentOwner };
        }
        case SyntaxKind.PropertyDeclaration:
            const init = (commentOwner as PropertyDeclaration).initializer;
            if (init && (isFunctionExpression(init) || isArrowFunction(init))) {
                return { commentOwner, parameters: init.parameters, hasReturn: hasReturn(init, options) };
            }
    }
}

function hasReturn(node: Node, options: DocCommentTemplateOptions | undefined) {
    return !!options?.generateReturnInDocTemplate &&
        (isFunctionTypeNode(node) || isArrowFunction(node) && isExpression(node.body)
            || isFunctionLikeDeclaration(node) && node.body && isBlock(node.body) && !!forEachReturnStatement(node.body, n => n));
}

function getRightHandSideOfAssignment(rightHandSide: Expression): FunctionExpression | ArrowFunction | ConstructorDeclaration | undefined {
    while (rightHandSide.kind === SyntaxKind.ParenthesizedExpression) {
        rightHandSide = (rightHandSide as ParenthesizedExpression).expression;
    }

    switch (rightHandSide.kind) {
        case SyntaxKind.FunctionExpression:
        case SyntaxKind.ArrowFunction:
            return (rightHandSide as FunctionExpression);
        case SyntaxKind.ClassExpression:
            return find((rightHandSide as ClassExpression).members, isConstructorDeclaration);
    }
}