package main

import (
	"fmt"
	"math/rand"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/jorenchik/mdemory/src/compiler/api"
	"github.com/jorenchik/mdemory/src/compiler/comperror"
	"github.com/jorenchik/mdemory/src/compiler/parser"
	"github.com/therecipe/qt/core"
	"github.com/therecipe/qt/widgets"
)

type Page struct {
    start int;
    end   int;
}

type PaginationButton struct {
    button *widgets.QToolButton;
    pageIdx int;
}

var deckListLabel       *widgets.QLabel
var hMdemScroll         *widgets.QVBoxLayout
var mdemSpacer          *widgets.QSpacerItem
var spacerInitialized    bool = false
var workingPath          string = "/home/jorenchik/Code/mdemory/memorybase"
var questions         [] parser.Question
var paginationButtons []*widgets.QToolButton
var paginationLabel     *widgets.QLabel

var errorBox            *widgets.QWidget
var errorLabel          *widgets.QLabel 


var multispacePattern   *regexp.Regexp = regexp.MustCompile(`\s\s+`)

// var currentPage = 0
var PER_PAGE = 10
var pages []Page
var currentPage = -1
var prevButton  *widgets.QToolButton
var firstButton *widgets.QToolButton
var lastButton  *widgets.QToolButton
var nextButton  *widgets.QToolButton

type Mdem struct {
	wMdem           *widgets.QWidget;
	wFrontText      *widgets.QLabel;
	wBack           *widgets.QWidget;
	backLabels    []*widgets.QLabel;
	showButton      *widgets.QToolButton;
    labelCount      int;
}

var mdems []*Mdem

func (mdem *Mdem) showBacklabels() {
    for i := range(len(mdem.backLabels)) {
        if i < mdem.labelCount {
            if !mdem.backLabels[i].IsVisible() {
                mdem.backLabels[i].Show()
            }
        } else {
            if mdem.backLabels[i].IsVisible() {
                mdem.backLabels[i].Hide()
            }
        }
    }
}

func CreateMdem() *Mdem {

	// DefineMdem
	wMdem := widgets.NewQWidget(nil, 0)
	vMdem := widgets.NewQVBoxLayout()

	wMdem.SetLayout(vMdem)
	id := fmt.Sprintf("mdem_%d", 1)
	wMdem.SetObjectName(id)

	// InitFront
	wFront := widgets.NewQWidget(nil, 0)
	hFront := widgets.NewQHBoxLayout()
	wFront.SetMinimumHeight(60)
	wFront.SetLayout(hFront)
	wFront.SetProperty("first", core.NewQVariant1(true))
	wMdem.SetStyleSheet(fmt.Sprintf(`
        QWidget#%s > QWidget {
            border-right:  1px solid gray;
            border-bottom: 1px solid gray;
            border-left:   1px solid gray;
        }
        QWidget#%s > QWidget[first="true"] {
            border-top: 1px solid gray;
        }
    `, id, id))

	// AddFrontContent
	elFrontText := widgets.NewQLabel(nil, 0)
	hFront.AddWidget(elFrontText, 0, 0)
	hFront.AddStretch(1)
	showAnswer := widgets.NewQToolButton(nil)
	showAnswer.SetText("Show")
	hFront.AddWidget(showAnswer, 0, 0)

	wBack := widgets.NewQWidget(nil, 0)
	hBack := widgets.NewQVBoxLayout()
	wBack.SetLayout(hBack)
	vMdem.AddWidget(wBack, 0, 0)

	// AddBackContent
    var backLabels []*widgets.QLabel
    for range(20) {
        elBackText := widgets.NewQLabel(nil, 0)
        hBack.AddWidget(elBackText, 0, 0)
        backLabels = append(backLabels, elBackText)
    }

	vMdem.AddWidget(wFront, 0, 0)
	vMdem.AddWidget(wBack, 0, 0)
	vMdem.SetContentsMargins(0, 0, 0, 0)
	vMdem.SetSpacing(0)


	wBack.Hide()
	wMdem.Hide()
    mdem := Mdem{
		wMdem,
		elFrontText,
		wBack,
        backLabels,
        showAnswer,
        0,
	}

    mdem.showButton.ConnectClicked(func(checked bool) {
        if mdem.wBack.IsVisible() {
            mdem.wBack.Hide()
            mdem.showButton.SetText("Show")
        } else {
            mdem.wBack.Show()
            mdem.showButton.SetText("Hide")
        }
        mdem.showBacklabels()
    })

    return &mdem
}

func CreateMdems(questions *[]parser.Question) {
    hMdemScroll.RemoveItem(mdemSpacer)

	for i := range mdems {
        if mdems[i].wMdem.IsVisible() {
            mdems[i].wMdem.Hide()
        }
	}

    if len(*questions) > len(mdems) {
        for range(len(*questions) - len(mdems)) {
            mdem := CreateMdem()
            mdems = append(mdems, mdem) 
            hMdemScroll.AddWidget(mdem.wMdem, 0, 0)
        }
    }

	// destroy widgets
    transformAnswer := func(answer string) string {
        answer = strings.ReplaceAll(answer, "\t", " ")
        answer = strings.ReplaceAll(answer, "\n", " ")
        answer = multispacePattern.ReplaceAllString(answer, ` `)
        return answer 
    }
	for i := range *questions {
		question := (*questions)[i]
		switch question.(type) {
		case parser.SingleAnswerQuestion:
			mdems[i].wFrontText.SetText(
				question.(parser.SingleAnswerQuestion).Question,
			)
            answer := question.(parser.SingleAnswerQuestion).Answer 
            answer = transformAnswer(answer)
            answer = fmt.Sprintf("- %s", answer)
			mdems[i].backLabels[0].SetText(answer)
            if mdems[i].wBack.IsVisible() {
                mdems[i].wBack.Hide()
            }
            mdems[i].labelCount = 1
		case parser.MultipleChoiceQuestion:
			choices := question.(parser.MultipleChoiceQuestion).Choices
			mdems[i].wFrontText.SetText(
				question.(parser.MultipleChoiceQuestion).Question,
			)
			for k := range choices {
                answer := choices[k].Answer
                answer = transformAnswer(answer)
                answer = fmt.Sprintf("- %s", answer)
				if k < len(mdems[i].backLabels) {
					mdems[i].backLabels[k].SetText(answer)
				} else {
					label := widgets.NewQLabel2(answer, nil, 0)
					mdems[i].backLabels = append(
						mdems[i].backLabels,
						label,
					)
					mdems[i].wBack.Layout().AddWidget(label)
				}
			}
            mdems[i].labelCount = len(choices)
		}
        if !mdems[i].wMdem.IsVisible() {
            mdems[i].wMdem.Show()
        }
	}

	hMdemScroll.AddItem(mdemSpacer)
}

func MakePages() {
    pages = nil
    for i := 0; i < (len(questions) / PER_PAGE) + 1; i++ {
        startingIndex := PER_PAGE * i 
        amount := PER_PAGE
        if (i == len(questions) / PER_PAGE) {
            amount = len(questions) % PER_PAGE
        }
        pages = append(
            pages,
            Page{startingIndex, startingIndex + amount},
        )
    }
}


var PAGINATION_COUNT int = 8
var DISTANCE int = PAGINATION_COUNT / 2 - 1

func SwitchPage(pageIdx int) {
    currentPage = pageIdx
    for i := range(paginationButtons) {
        paginationButtons[i].Hide()
    }
    l := 0
    paginationLabel.SetText(fmt.Sprintf("Page: %d", pageIdx + 1))
    for i := range(mdems) {
        if mdems[i].wBack.IsVisible() {
            mdems[i].wBack.Hide()
            mdems[i].showButton.SetText("Show")
        }

    }

    for k := -DISTANCE; k <= DISTANCE; k = k + 1 {
        if pageIdx + k >= 0 && pageIdx + k < len(pages) {
            button := paginationButtons[l]
            button.SetText(fmt.Sprintf("%d", pageIdx + k + 1))
            if pageIdx + k != pageIdx {
                button.Show()
            } else {
                button.Hide()
            }
            l++
        }
    }

    if pageIdx > 0 && len(pages) > 1 {
        firstButton.Show()
    } else {
        firstButton.Hide()
    }
    if pageIdx < len(pages) - 1 && len(pages) > 1 {
        lastButton.Show()
    } else {
        lastButton.Hide()
    }
    if len(pages) > 0 &&
       currentPage < len(pages) - 1 {
        nextButton.Show()
    } else {
        nextButton.Hide()
    }
    if len(pages) > 0 &&
       currentPage >= 1 {
        prevButton.Show()
    } else {
        prevButton.Hide()
    }

    page := pages[pageIdx]
    pageSlice := questions[page.start:page.end]
    CreateMdems(&pageSlice)
}

func OpenModelFile(
    model *widgets.QFileSystemModel,
    index *core.QModelIndex,
) {
    if model.IsDir(index) {
        return
    }
    if errorBox.IsVisible() {
        errorBox.Hide()
    }
    filePath := model.FilePath(index)
    fileContents, err := os.ReadFile(filePath)
    if err != nil {
        widgets.QMessageBox_Critical(
            nil,
            "Compilation error",
            err.Error(),
            widgets.QMessageBox__Ok,
            widgets.QMessageBox__Ok,
        )
        return
    }
    deckListLabel.SetText(fmt.Sprintf("Mdem: %s", filePath))
    start := time.Now().UnixMicro()
    questions, err = api.Compile(string(fileContents))
    if err != nil {
        var errText string
        switch err.(type) {
            case comperror.PositionErr:
                errText = fmt.Sprintf(
                    "%s on line %d, column %d",
                    err.(comperror.PositionErr).Message,
                    err.(comperror.PositionErr).Row,
                    err.(comperror.PositionErr).Column,
                )
            default:
                errText = err.Error()
        }
        errorLabel.SetText(errText)
        errorBox.Show()
        for i := range(mdems) {
            if mdems[i].wMdem.IsVisible() {
                mdems[i].wMdem.Hide()
            }
        }
        return
    }
    duration := float32(time.Now().UnixMicro()-start) / 1000
    fmt.Printf("Compilation took %.3fms", duration)

    MakePages()
    SwitchPage(0)
}

func main() {
	// InitApp
	app := widgets.NewQApplication(len(os.Args), os.Args)
	window := widgets.NewQMainWindow(nil, 0)
	hSplitter := widgets.NewQSplitter(nil)
	window.SetWindowTitle("MDemory")
	window.SetMinimumSize2(400, 300)

	leftWidget := widgets.NewQWidget(nil, 0)
	leftLayout := widgets.NewQVBoxLayout()
	mdemLabel := widgets.NewQLabel2("Mdems", nil, 0)
	model := widgets.NewQFileSystemModel(nil)
	mdemList := widgets.NewQTreeView(nil)
	leftWidget.SetLayout(leftLayout)
	leftLayout.AddWidget(mdemLabel, 0, 0)
	model.SetRootPath(workingPath)
	mdemList.SetModel(model)

	rootIndex := model.Index2("/home/jorenchik/Code/mdemory/memorybase", 0)
	fmt.Printf("Root index: %s\n", model.FileName(rootIndex))
	mdemList.SetRootIndex(rootIndex)

	mdemList.HideColumn(1)
	mdemList.HideColumn(2)
	mdemList.HideColumn(3)
	mdemList.ConnectDoubleClicked(
        func(index *core.QModelIndex) {
            OpenModelFile(model, index)
        },
    )

	deckLabel := widgets.NewQLabel2("Decks", nil, 0)
	deckList := widgets.NewQListView(nil)
	leftLayout.AddWidget(mdemList, 1, 0)
	leftLayout.AddWidget(deckLabel, 0, 0)
	leftLayout.AddWidget(deckList, 1, 0)

	rightWidget := widgets.NewQWidget(nil, 0)
	rightLayout := widgets.NewQVBoxLayout()
	rightWidget.SetLayout(rightLayout)

	top := widgets.NewQWidget(nil, 0)
	hTop := widgets.NewQHBoxLayout()
	deckListLabel = widgets.NewQLabel(nil, 0)
	top.SetLayout(hTop)
	rightLayout.AddWidget(top, 0, 0)

	hTop.AddWidget(deckListLabel, 0, 0)
	hTop.AddStretch(1)
	refresh := widgets.NewQToolButton(nil)
	practice := widgets.NewQToolButton(nil)
	shuffle := widgets.NewQToolButton(nil)
	hTop.AddWidget(refresh, 0, 0)
	hTop.AddWidget(shuffle, 0, 0)
	hTop.AddWidget(practice, 0, 0)
	refresh.SetText("Refresh")
	shuffle.SetText("Shuffle")
    shuffle.ConnectClicked(
        func(checked bool) {
            for i := range questions {
                j := rand.Intn(i + 1)
                questions[i], questions[j] = questions[j], questions[i]
            }
            SwitchPage(0)
        },
    )
	practice.SetText("Practice")

	mdemScroll := widgets.NewQScrollArea(nil)
	mdemContainer := widgets.NewQWidget(nil, 0)
	hMdemScroll = widgets.NewQVBoxLayout()
	mdemScroll.SetWidget(mdemContainer)
	mdemScroll.SetWidgetResizable(true)
	mdemContainer.SetLayout(hMdemScroll)

    errorBox = widgets.NewQWidget(nil, 0)
    hErrorBox := widgets.NewQHBoxLayout2(nil)
    errorBox.SetLayout(hErrorBox)
    errorLabel = widgets.NewQLabel(nil, 0)
    errorLabel.SetText("Error")
    errorBox.Layout().AddWidget(errorLabel)
    hErrorBox.AddStretch(1)
    hMdemScroll.AddWidget(errorBox, 0, 0)
    errorBox.SetObjectName("error")
    errorBox.SetStyleSheet(`
        QWidget#error {
            border: 1px solid #dc143c;
            background-color: white;
            padding: 10px 0px;
        }
    `)
    errorBox.Hide()

	for i := 0; i < 40; i++ {
        mdem := CreateMdem()
		mdems = append(
			mdems,
            mdem,
		)
        hMdemScroll.AddWidget(mdem.wMdem, 0, 0)
	}
    mdemSpacer = widgets.NewQSpacerItem(
        40,
        40,
        widgets.QSizePolicy__Minimum,
        widgets.QSizePolicy__Expanding,
    )
    hMdemScroll.AddItem(mdemSpacer)

	// CreateMdems
	rightLayout.AddWidget(mdemScroll, 1, 0)

	// MakePagination
	pagination := widgets.NewQWidget(nil, 0)
	hPagination := widgets.NewQHBoxLayout()
	pagination.SetLayout(hPagination)

	firstButton = widgets.NewQToolButton(nil)
	firstButton.SetText("<<")
	hPagination.AddWidget(firstButton, 0, 0)
    firstButton.Hide()
    firstButton.ConnectClicked(func(checked bool) {
        if len(pages) > 0 {
            SwitchPage(0)
        }
    })

	prevButton = widgets.NewQToolButton(nil)
	prevButton.SetText("<")
	hPagination.AddWidget(prevButton, 0, 0)
    prevButton.Hide()
    prevButton.ConnectClicked(func(checked bool) {
        if len(pages) > 0 {
            SwitchPage(currentPage - 1)
        }
    })

	for i := 0; i < PAGINATION_COUNT; i++ {
		elButton := widgets.NewQToolButton(nil)
		elButton.SetText(fmt.Sprintf("%d", i+1))
		hPagination.AddWidget(elButton, 0, 0)
        elButton.Hide()
        elButton.ConnectClicked(func(checked bool) {
            pageNum, err := strconv.ParseInt(elButton.Text(), 10, 64)
            _ = err
            pageIdx := int(pageNum) - 1
            if (pageIdx < len(pages)) {
                SwitchPage(pageIdx)
            }
        })
        paginationButtons = append(
            paginationButtons,
            elButton,
        )
	}
    
	nextButton = widgets.NewQToolButton(nil)
	nextButton.SetText(">")
	hPagination.AddWidget(nextButton, 0, 0)
    nextButton.Hide()
    nextButton.ConnectClicked(func(checked bool) {
        if len(pages) > 0 {
            SwitchPage(currentPage + 1)
        }
    })

	lastButton = widgets.NewQToolButton(nil)
	lastButton.SetText(">>")
	hPagination.AddWidget(lastButton, 0, 0)
    lastButton.Hide()
    lastButton.ConnectClicked(func(checked bool) {
        if len(pages) > 0 {
            SwitchPage(len(pages) - 1)
        }
    })

	hPagination.AddStretch(1)
    paginationLabel = widgets.NewQLabel(nil, 0)
    hPagination.AddWidget(paginationLabel, 0, 0)

	rightLayout.AddWidget(pagination, 0, 0)

	hSplitter.AddWidget(leftWidget)
	hSplitter.AddWidget(rightWidget)
	hSplitter.SetStretchFactor(0, 1)
	hSplitter.SetStretchFactor(1, 3)
	window.SetCentralWidget(hSplitter)

	window.Show()
	app.Exec()
}
