internal/bookmark.go (view raw)
1package bible
2
3import (
4 "fmt"
5 "os"
6 "path/filepath"
7
8 "github.com/pelletier/go-toml/v2"
9)
10
11const (
12 defaultBookmarkFile = "bookmark.toml"
13)
14
15// BookmarkManager handles reading and writing bookmarks
16type BookmarkManager struct {
17 FilePath string
18}
19
20// NewBookmarkManager creates a new bookmark manager
21func NewBookmarkManager(filePath string) *BookmarkManager {
22 if filePath == "" {
23 filePath = defaultBookmarkFile
24 }
25 return &BookmarkManager{FilePath: filePath}
26}
27
28// ReadBookmark reads a bookmark from the file
29func (bm *BookmarkManager) ReadBookmark() (*Bookmark, error) {
30 data, err := os.ReadFile(bm.FilePath)
31 if err != nil {
32 if os.IsNotExist(err) {
33 // Return default bookmark if file doesn't exist
34 return &Bookmark{
35 Book: Matthew,
36 Chapter: 1,
37 FirstVerse: 1,
38 SecondVerse: 12,
39 }, nil
40 }
41 return nil, err
42 }
43
44 var bookmark Bookmark
45 if err := toml.Unmarshal(data, &bookmark); err != nil {
46 return nil, fmt.Errorf("failed to parse bookmark file: %w", err)
47 }
48
49 // Validate bookmark values
50 if bookmark.Book < Matthew || bookmark.Book > John {
51 bookmark.Book = Matthew
52 }
53 if bookmark.Chapter < 1 {
54 bookmark.Chapter = 1
55 }
56 if bookmark.FirstVerse < 1 {
57 bookmark.FirstVerse = 1
58 }
59 if bookmark.SecondVerse < bookmark.FirstVerse {
60 bookmark.SecondVerse = bookmark.FirstVerse
61 }
62
63 return &bookmark, nil
64}
65
66// WriteBookmark writes a bookmark to the file
67func (bm *BookmarkManager) WriteBookmark(bookmark *Bookmark) error {
68 data, err := toml.Marshal(bookmark)
69 if err != nil {
70 return fmt.Errorf("failed to marshal bookmark: %w", err)
71 }
72
73 // Create directory if it doesn't exist
74 dir := filepath.Dir(bm.FilePath)
75 if dir != "" && dir != "." {
76 if err := os.MkdirAll(dir, 0755); err != nil {
77 return fmt.Errorf("failed to create directory: %w", err)
78 }
79 }
80
81 if err := os.WriteFile(bm.FilePath, data, 0644); err != nil {
82 return fmt.Errorf("failed to write bookmark file: %w", err)
83 }
84
85 return nil
86}
87
88// AdjustForLookahead adjusts the bookmark if less than 12 verses remain after it
89func (bm *BookmarkManager) AdjustForLookahead(bookmark *Bookmark, bible *Bible) *Bookmark {
90 versesInChapter := bible.CountVersesInChapter(int(bookmark.Book), bookmark.Chapter)
91 versesRemaining := versesInChapter - bookmark.SecondVerse
92
93 // If we're not at the end of the chapter and less than 12 verses remain for NEXT snippet
94 if versesRemaining > 0 && versesRemaining < 12 {
95 return &Bookmark{
96 Book: bookmark.Book,
97 Chapter: bookmark.Chapter,
98 FirstVerse: bookmark.FirstVerse,
99 SecondVerse: versesInChapter,
100 }
101 }
102
103 // Otherwise return the original bookmark
104 return bookmark
105}
106
107// AdvanceBookmark calculates the next bookmark based on the current one
108func (bm *BookmarkManager) AdvanceBookmark(current *Bookmark, bible *Bible) *Bookmark {
109 versesInChapter := bible.CountVersesInChapter(int(current.Book), current.Chapter)
110
111 // Case 1: We are at the end of the chapter, move to the next chapter
112 if current.SecondVerse >= versesInChapter {
113 nextChapter := current.Chapter + 1
114
115 // Check if next chapter exists in current book
116 if bible.CountVersesInChapter(int(current.Book), nextChapter) > 0 {
117 return &Bookmark{
118 Book: current.Book,
119 Chapter: nextChapter,
120 FirstVerse: 1,
121 SecondVerse: min(12, bible.CountVersesInChapter(int(current.Book), nextChapter)),
122 }
123 }
124
125 // End of book, move to next book or loop to Matthew
126 nextBookInt := int(current.Book) + 1
127 if nextBookInt > int(John) {
128 nextBookInt = int(Matthew)
129 }
130
131 nextBook := BookIndex(nextBookInt)
132 return &Bookmark{
133 Book: nextBook,
134 Chapter: 1,
135 FirstVerse: 1,
136 SecondVerse: min(12, bible.CountVersesInChapter(int(nextBook), 1)),
137 }
138 }
139
140 // Case 2: We are in the middle of a chapter, advance verses
141 newFirstVerse := current.SecondVerse + 1
142 newSecondVerse := min(newFirstVerse+11, versesInChapter)
143
144 return &Bookmark{
145 Book: current.Book,
146 Chapter: current.Chapter,
147 FirstVerse: newFirstVerse,
148 SecondVerse: newSecondVerse,
149 }
150}
151
152func min(a, b int) int {
153 if a < b {
154 return a
155 }
156 return b
157}