Решение на Concurrent Tasks от Радослав Георгиев

Обратно към всички решения

Към профила на Радослав Георгиев

Резултати

  • 10 точки от тестове
  • 0 бонус точки
  • 10 точки общо
  • 10 успешни тест(а)
  • 0 неуспешни тест(а)

Код

package main
import (
"encoding/json"
"encoding/xml"
"fmt"
)
const (
bufferSize = 100
)
const (
_ = iota
TakeBook
ReturnBook
GetAvailability
)
type Library interface {
// Добавя книга от json
// Oтговаря с общия брой копия в библиотеката (не само наличните).
// Aко са повече от 4 - връща грешка
AddBookJSON(data []byte) (int, error)
// Добавя книга от xml
// Oтговаря с общия брой копия в библиотеката (не само наличните).
// Ако са повече от 4 - връщаме грешка
AddBookXML(data []byte) (int, error)
// Ангажира свободен "библиотекар" да ни обработва заявките.
// Библиотекарите са фиксиран брой - подават се като параметър на NewLibrary
// Блокира ако всички библиотекари са заети.
// Връщат се два канала:
// първият е само за писане - по него ще изпращаме заявките
// вторият е само за четене - по него ще получаваме отговорите.
// Ако затворим канала със заявките - освобождаваме библиотекаря.
Hello() (chan<- LibraryRequest, <-chan LibraryResponse)
}
type LibraryRequest interface {
// Тип на заявката:
// 1 - Borrow book
// 2 - Return book
// 3 - Get availability information about book
GetType() int
// Връща isbn на книгата, за която се отнася Request-a
GetISBN() string
}
type LibraryResponse interface {
// Ако книгата съществува/налична е - обект имплементиращ Stringer (повече информация по-долу)
// Aко книгата не съществува първият резултат е nil.
// Връща се и подобаващa грешка (виж по-долу) - ако такава е възникнала.
// Когато се е резултат на заявка от тип 2 (Return book) - не е нужно да я закачаме към отговора.
GetBook() (fmt.Stringer, error)
// available - Колко наличности от книгата имаме останали след изпълнението на заявката.
// Тоест, ако сме имали 3 копия от Х и това е отговор на Take заявка - тук ще има 2.
// registered - Колко копия от тази книга има регистрирани в библиотеката (макс 4).
GetAvailability() (available int, registered int)
}
type Book struct {
XMLName xml.Name `xml:"book"`
ISBN string `json:"isbn" xml:"isbn,attr"`
Title string `json:"title" xml:"title"`
Author struct {
FirstName string `json:"first_name" xml:"first_name"`
LastName string `json:"last_name" xml:"last_name"`
} `json:"author" xml:"author"`
Ratings []uint8 `json:"ratings" xml:"ratings>rating"`
}
type SimpleLibrary struct {
Books map[string]*Book
registeredCopyCount map[string]int
availableCopyCount map[string]int
librarians chan struct{}
}
type SimpleLibraryRequest struct {
requestType int
bookISBN string
}
type SimpleLibraryResponse struct {
book *Book
registeredCopyCount int
availableCopyCount int
err error
}
type BookError struct {
ISBN string
}
type TooManyCopiesBookError struct {
BookError
}
type NotFoundBookError struct {
BookError
}
type NotAvailableBookError struct {
BookError
}
type AllCopiesAvailableBookError struct {
BookError
}
func (b *Book) String() string {
return fmt.Sprintf("[%v] %v от %v %v", b.ISBN, b.Title, b.Author.FirstName, b.Author.LastName)
}
func (e *TooManyCopiesBookError) Error() string {
return fmt.Sprintf("Има 4 копия на книга %v", e.ISBN)
}
func (e *NotFoundBookError) Error() string {
return fmt.Sprintf("Непозната книга %v", e.ISBN)
}
func (e *NotAvailableBookError) Error() string {
return fmt.Sprintf("Няма наличност на книга %v", e.ISBN)
}
func (e *AllCopiesAvailableBookError) Error() string {
return fmt.Sprintf("Всички копия са налични %v", e.ISBN)
}
func (r *SimpleLibraryRequest) GetType() int {
return r.requestType
}
func (r *SimpleLibraryRequest) GetISBN() string {
return r.bookISBN
}
func (r *SimpleLibraryRequest) SetType(t int) {
r.requestType = t
}
func (r *SimpleLibraryRequest) SetISBN(isbn string) {
r.bookISBN = isbn
}
func (r *SimpleLibraryResponse) GetBook() (fmt.Stringer, error) {
return r.book, r.err
}
func (r *SimpleLibraryResponse) GetAvailability() (int, int) {
return r.availableCopyCount, r.registeredCopyCount
}
func (sl *SimpleLibrary) addBook(book *Book) (registeredCopyCount int, err error) {
if sl.registeredCopyCount[book.ISBN] >= 4 {
err = &TooManyCopiesBookError{BookError{book.ISBN}}
} else {
sl.Books[book.ISBN] = book
sl.registeredCopyCount[book.ISBN]++
sl.availableCopyCount[book.ISBN]++
}
registeredCopyCount = sl.registeredCopyCount[book.ISBN]
return
}
func (sl *SimpleLibrary) AddBookJSON(data []byte) (int, error) {
book := &Book{}
json.Unmarshal(data, book)
return sl.addBook(book)
}
func (sl *SimpleLibrary) AddBookXML(data []byte) (int, error) {
book := &Book{}
xml.Unmarshal(data, book)
return sl.addBook(book)
}
func (sl *SimpleLibrary) Hello() (chan<- LibraryRequest, <-chan LibraryResponse) {
requests := make(chan LibraryRequest, bufferSize)
responses := make(chan LibraryResponse, bufferSize)
<-sl.librarians
go func() {
for request := range requests {
go func() {
isbn := request.GetISBN()
book, isBookRegistered := sl.Books[isbn]
response := &SimpleLibraryResponse{}
if !isBookRegistered {
response.err = &NotFoundBookError{BookError{isbn}}
responses <- response
return
}
switch request.GetType() {
case TakeBook:
if sl.availableCopyCount[isbn] > 0 {
sl.availableCopyCount[isbn]--
response.book = book
} else {
response.err = &NotAvailableBookError{BookError{isbn}}
}
case ReturnBook:
if sl.availableCopyCount[isbn] < sl.registeredCopyCount[isbn] {
sl.availableCopyCount[isbn]++
response.book = book
} else {
response.err = &AllCopiesAvailableBookError{BookError{isbn}}
}
case GetAvailability:
response.book = book
}
response.registeredCopyCount = sl.registeredCopyCount[isbn]
response.availableCopyCount = sl.availableCopyCount[isbn]
responses <- response
}()
}
sl.librarians <- struct{}{}
}()
return requests, responses
}
func NewLibrary(librarians int) Library {
sl := &SimpleLibrary{
Books: make(map[string]*Book),
registeredCopyCount: make(map[string]int),
availableCopyCount: make(map[string]int),
librarians: make(chan struct{}, librarians),
}
for i := 0; i < librarians; i++ {
sl.librarians <- struct{}{}
}
return sl
}

Лог от изпълнението

PASS
ok  	_/tmp/d20151207-5667-1g5xl7q	0.005s
PASS
ok  	_/tmp/d20151207-5667-1g5xl7q	0.003s
PASS
ok  	_/tmp/d20151207-5667-1g5xl7q	0.004s
PASS
ok  	_/tmp/d20151207-5667-1g5xl7q	0.004s
PASS
ok  	_/tmp/d20151207-5667-1g5xl7q	0.003s
PASS
ok  	_/tmp/d20151207-5667-1g5xl7q	0.004s
PASS
ok  	_/tmp/d20151207-5667-1g5xl7q	0.003s
PASS
ok  	_/tmp/d20151207-5667-1g5xl7q	0.003s
PASS
ok  	_/tmp/d20151207-5667-1g5xl7q	0.004s
PASS
ok  	_/tmp/d20151207-5667-1g5xl7q	0.005s

История (2 версии и 1 коментар)

Радослав обнови решението на 06.12.2015 17:44 (преди над 2 години)

+package main
+
+import (
+ "encoding/json"
+ "encoding/xml"
+ "fmt"
+)
+
+const (
+ _ = iota
+ TakeBook
+ ReturnBook
+ GetAvailability
+)
+
+type Library interface {
+
+ // Добавя книга от json
+ // Oтговаря с общия брой копия в библиотеката (не само наличните).
+ // Aко са повече от 4 - връща грешка
+ AddBookJSON(data []byte) (int, error)
+
+ // Добавя книга от xml
+ // Oтговаря с общия брой копия в библиотеката (не само наличните).
+ // Ако са повече от 4 - връщаме грешка
+ AddBookXML(data []byte) (int, error)
+
+ // Ангажира свободен "библиотекар" да ни обработва заявките.
+ // Библиотекарите са фиксиран брой - подават се като параметър на NewLibrary
+ // Блокира ако всички библиотекари са заети.
+ // Връщат се два канала:
+ // първият е само за писане - по него ще изпращаме заявките
+ // вторият е само за четене - по него ще получаваме отговорите.
+ // Ако затворим канала със заявките - освобождаваме библиотекаря.
+ Hello() (chan<- LibraryRequest, <-chan LibraryResponse)
+}
+
+type LibraryRequest interface {
+ // Тип на заявката:
+ // 1 - Borrow book
+ // 2 - Return book
+ // 3 - Get availability information about book
+ GetType() int
+
+ // Връща isbn на книгата, за която се отнася Request-a
+ GetISBN() string
+}
+
+type LibraryResponse interface {
+ // Ако книгата съществува/налична е - обект имплементиращ Stringer (повече информация по-долу)
+ // Aко книгата не съществува първият резултат е nil.
+ // Връща се и подобаващa грешка (виж по-долу) - ако такава е възникнала.
+ // Когато се е резултат на заявка от тип 2 (Return book) - не е нужно да я закачаме към отговора.
+ GetBook() (fmt.Stringer, error)
+
+ // available - Колко наличности от книгата имаме останали след изпълнението на заявката.
+ // Тоест, ако сме имали 3 копия от Х и това е отговор на Take заявка - тук ще има 2.
+ // registered - Колко копия от тази книга има регистрирани в библиотеката (макс 4).
+ GetAvailability() (available int, registered int)
+}
+
+type Book struct {
+ XMLName xml.Name `xml:"book"`
+ ISBN string `json:"isbn" xml:"isbn,attr"`
+ Title string `json:"title" xml:"title"`
+ Author struct {
+ FirstName string `json:"first_name" xml:"first_name"`
+ LastName string `json:"last_name" xml:"last_name"`
+ } `json:"author" xml:"author"`
+ Ratings []uint8 `json:"ratings" xml:"ratings>rating"`
+}
+
+type SimpleLibrary struct {
+ Books map[string]*Book
+ registeredCopyCount map[string]int
+ availableCopyCount map[string]int
+ librarians chan struct{}
+}
+
+type SimpleLibraryRequest struct {
+ requestType int
+ bookISBN string
+}
+
+type SimpleLibraryResponse struct {
+ book *Book
+ registeredCopyCount int
+ availableCopyCount int
+ err error
+}
+
+type BookError struct {
+ ISBN string
+}
+
+type TooManyCopiesBookError struct {
+ BookError
+}
+
+type NotFoundBookError struct {
+ BookError
+}
+
+type NotAvailableBookError struct {
+ BookError
+}
+
+type AllCopiesAvailableBookError struct {
+ BookError
+}
+
+func (b *Book) String() string {
+ return fmt.Sprintf("[%v] %v от %v %v", b.ISBN, b.Title, b.Author.FirstName, b.Author.LastName)
+}
+
+func (e *TooManyCopiesBookError) Error() string {
+ return fmt.Sprintf("Има 4 копия на книга %v", e.ISBN)
+}
+
+func (e *NotFoundBookError) Error() string {
+ return fmt.Sprintf("Непозната книга %v", e.ISBN)
+}
+
+func (e *NotAvailableBookError) Error() string {
+ return fmt.Sprintf("Няма наличност на книга %v", e.ISBN)
+}
+
+func (e *AllCopiesAvailableBookError) Error() string {
+ return fmt.Sprintf("Всички копия са налични %v", e.ISBN)
+}
+
+func (r *SimpleLibraryRequest) GetType() int {
+ return r.requestType
+}
+
+func (r *SimpleLibraryRequest) GetISBN() string {
+ return r.bookISBN
+}
+
+func (r *SimpleLibraryRequest) SetType(t int) {
+ r.requestType = t
+}
+
+func (r *SimpleLibraryRequest) SetISBN(isbn string) {
+ r.bookISBN = isbn
+}
+
+func (r *SimpleLibraryResponse) GetBook() (fmt.Stringer, error) {
+ return r.book, r.err
+}
+
+func (r *SimpleLibraryResponse) GetAvailability() (int, int) {
+ return r.availableCopyCount, r.registeredCopyCount
+}
+
+func (sl *SimpleLibrary) addBook(book *Book) (registeredCopyCount int, err error) {
+ if sl.registeredCopyCount[book.ISBN] >= 4 {
+ err = &TooManyCopiesBookError{BookError{book.ISBN}}
+ } else {
+ sl.Books[book.ISBN] = book
+ sl.registeredCopyCount[book.ISBN]++
+ sl.availableCopyCount[book.ISBN]++
+ }
+
+ registeredCopyCount = sl.registeredCopyCount[book.ISBN]
+ return
+}
+
+func (sl *SimpleLibrary) AddBookJSON(data []byte) (int, error) {
+ book := &Book{}
+ json.Unmarshal(data, book)
+ return sl.addBook(book)
+}
+
+func (sl *SimpleLibrary) AddBookXML(data []byte) (int, error) {
+ book := &Book{}
+ xml.Unmarshal(data, book)
+ return sl.addBook(book)
+}
+
+func (sl *SimpleLibrary) Hello() (chan<- LibraryRequest, <-chan LibraryResponse) {
+ requests := make(chan LibraryRequest, 100)
+ responses := make(chan LibraryResponse, 100)
+
+ <-sl.librarians
+
+ go func() {
+ for request := range requests {
+ go func() {
+ isbn := request.GetISBN()
+ book, isBookRegistered := sl.Books[isbn]
+ response := &SimpleLibraryResponse{}
+
+ if !isBookRegistered {
+ response.err = &NotFoundBookError{BookError{isbn}}
+ responses <- response
+ return
+ }
+
+ switch request.GetType() {
+ case TakeBook:
+ if sl.availableCopyCount[isbn] > 0 {
+ sl.availableCopyCount[isbn]--
+ response.book = book
+ } else {
+ response.err = &NotAvailableBookError{BookError{isbn}}
+ }
+
+ case ReturnBook:
+ if sl.availableCopyCount[isbn] < sl.registeredCopyCount[isbn] {
+ sl.availableCopyCount[isbn]++
+ response.book = book
+ } else {
+ response.err = &AllCopiesAvailableBookError{BookError{isbn}}
+ }
+
+ case GetAvailability:
+ response.book = book
+ }
+
+ response.registeredCopyCount = sl.registeredCopyCount[isbn]
+ response.availableCopyCount = sl.availableCopyCount[isbn]
+ responses <- response
+ }()
+ }
+
+ sl.librarians <- struct{}{}
+ }()
+
+ return requests, responses
+}
+
+func NewLibrary(librarians int) Library {
+ sl := &SimpleLibrary{
+ Books: make(map[string]*Book),
+ registeredCopyCount: make(map[string]int),
+ availableCopyCount: make(map[string]int),
+ librarians: make(chan struct{}, librarians),
+ }
+
+ for i := 0; i < librarians; i++ {
+ sl.librarians <- struct{}{}
+ }
+
+ return sl
+}

Радослав обнови решението на 06.12.2015 17:52 (преди над 2 години)

package main
import (
"encoding/json"
"encoding/xml"
"fmt"
)
const (
+ bufferSize = 100
+)
+
+const (
_ = iota
TakeBook
ReturnBook
GetAvailability
)
type Library interface {
// Добавя книга от json
// Oтговаря с общия брой копия в библиотеката (не само наличните).
// Aко са повече от 4 - връща грешка
AddBookJSON(data []byte) (int, error)
// Добавя книга от xml
// Oтговаря с общия брой копия в библиотеката (не само наличните).
// Ако са повече от 4 - връщаме грешка
AddBookXML(data []byte) (int, error)
// Ангажира свободен "библиотекар" да ни обработва заявките.
// Библиотекарите са фиксиран брой - подават се като параметър на NewLibrary
// Блокира ако всички библиотекари са заети.
// Връщат се два канала:
// първият е само за писане - по него ще изпращаме заявките
// вторият е само за четене - по него ще получаваме отговорите.
// Ако затворим канала със заявките - освобождаваме библиотекаря.
Hello() (chan<- LibraryRequest, <-chan LibraryResponse)
}
type LibraryRequest interface {
// Тип на заявката:
// 1 - Borrow book
// 2 - Return book
// 3 - Get availability information about book
GetType() int
// Връща isbn на книгата, за която се отнася Request-a
GetISBN() string
}
type LibraryResponse interface {
// Ако книгата съществува/налична е - обект имплементиращ Stringer (повече информация по-долу)
// Aко книгата не съществува първият резултат е nil.
// Връща се и подобаващa грешка (виж по-долу) - ако такава е възникнала.
// Когато се е резултат на заявка от тип 2 (Return book) - не е нужно да я закачаме към отговора.
GetBook() (fmt.Stringer, error)
// available - Колко наличности от книгата имаме останали след изпълнението на заявката.
// Тоест, ако сме имали 3 копия от Х и това е отговор на Take заявка - тук ще има 2.
// registered - Колко копия от тази книга има регистрирани в библиотеката (макс 4).
GetAvailability() (available int, registered int)
}
type Book struct {
XMLName xml.Name `xml:"book"`
ISBN string `json:"isbn" xml:"isbn,attr"`
Title string `json:"title" xml:"title"`
Author struct {
FirstName string `json:"first_name" xml:"first_name"`
LastName string `json:"last_name" xml:"last_name"`
} `json:"author" xml:"author"`
Ratings []uint8 `json:"ratings" xml:"ratings>rating"`
}
type SimpleLibrary struct {
Books map[string]*Book
registeredCopyCount map[string]int
availableCopyCount map[string]int
librarians chan struct{}
}
type SimpleLibraryRequest struct {
requestType int
bookISBN string
}
type SimpleLibraryResponse struct {
book *Book
registeredCopyCount int
availableCopyCount int
err error
}
type BookError struct {
ISBN string
}
type TooManyCopiesBookError struct {
BookError
}
type NotFoundBookError struct {
BookError
}
type NotAvailableBookError struct {
BookError
}
type AllCopiesAvailableBookError struct {
BookError
}
func (b *Book) String() string {
return fmt.Sprintf("[%v] %v от %v %v", b.ISBN, b.Title, b.Author.FirstName, b.Author.LastName)
}
func (e *TooManyCopiesBookError) Error() string {
return fmt.Sprintf("Има 4 копия на книга %v", e.ISBN)
}
func (e *NotFoundBookError) Error() string {
return fmt.Sprintf("Непозната книга %v", e.ISBN)
}
func (e *NotAvailableBookError) Error() string {
return fmt.Sprintf("Няма наличност на книга %v", e.ISBN)
}
func (e *AllCopiesAvailableBookError) Error() string {
return fmt.Sprintf("Всички копия са налични %v", e.ISBN)
}
func (r *SimpleLibraryRequest) GetType() int {
return r.requestType
}
func (r *SimpleLibraryRequest) GetISBN() string {
return r.bookISBN
}
func (r *SimpleLibraryRequest) SetType(t int) {
r.requestType = t
}
func (r *SimpleLibraryRequest) SetISBN(isbn string) {
r.bookISBN = isbn
}
func (r *SimpleLibraryResponse) GetBook() (fmt.Stringer, error) {
return r.book, r.err
}
func (r *SimpleLibraryResponse) GetAvailability() (int, int) {
return r.availableCopyCount, r.registeredCopyCount
}
func (sl *SimpleLibrary) addBook(book *Book) (registeredCopyCount int, err error) {
if sl.registeredCopyCount[book.ISBN] >= 4 {
err = &TooManyCopiesBookError{BookError{book.ISBN}}
} else {
sl.Books[book.ISBN] = book
sl.registeredCopyCount[book.ISBN]++
sl.availableCopyCount[book.ISBN]++
}
registeredCopyCount = sl.registeredCopyCount[book.ISBN]
return
}
func (sl *SimpleLibrary) AddBookJSON(data []byte) (int, error) {
book := &Book{}
json.Unmarshal(data, book)
return sl.addBook(book)
}
func (sl *SimpleLibrary) AddBookXML(data []byte) (int, error) {
book := &Book{}
xml.Unmarshal(data, book)
return sl.addBook(book)
}
func (sl *SimpleLibrary) Hello() (chan<- LibraryRequest, <-chan LibraryResponse) {
- requests := make(chan LibraryRequest, 100)
- responses := make(chan LibraryResponse, 100)
+ requests := make(chan LibraryRequest, bufferSize)
+ responses := make(chan LibraryResponse, bufferSize)
<-sl.librarians
go func() {
for request := range requests {
go func() {
isbn := request.GetISBN()
book, isBookRegistered := sl.Books[isbn]
response := &SimpleLibraryResponse{}
if !isBookRegistered {
response.err = &NotFoundBookError{BookError{isbn}}
responses <- response
return
}
switch request.GetType() {
case TakeBook:
if sl.availableCopyCount[isbn] > 0 {
sl.availableCopyCount[isbn]--
response.book = book
} else {
response.err = &NotAvailableBookError{BookError{isbn}}
}
case ReturnBook:
if sl.availableCopyCount[isbn] < sl.registeredCopyCount[isbn] {
sl.availableCopyCount[isbn]++
response.book = book
} else {
response.err = &AllCopiesAvailableBookError{BookError{isbn}}
}
case GetAvailability:
response.book = book
}
response.registeredCopyCount = sl.registeredCopyCount[isbn]
response.availableCopyCount = sl.availableCopyCount[isbn]
responses <- response
}()
}
sl.librarians <- struct{}{}
}()
return requests, responses
}
func NewLibrary(librarians int) Library {
sl := &SimpleLibrary{
Books: make(map[string]*Book),
registeredCopyCount: make(map[string]int),
availableCopyCount: make(map[string]int),
librarians: make(chan struct{}, librarians),
}
for i := 0; i < librarians; i++ {
sl.librarians <- struct{}{}
}
return sl
}

Just for completeness' sake, прилагам крайното решение, където съм направил обработката на заявките от един библиотекар последователна (една след друга, както е логично). Също така съм добавил mutex sync, който контролира навлизането в критичните секции на кода, където се достъпват стойности от тип map (тъй като те не са thread-safe):

package main

import (
    "encoding/json"
    "encoding/xml"
    "fmt"
)

const (
    bufferSize = 100
)

const (
    _ = iota
    TakeBook
    ReturnBook
    GetAvailability
)

type Library interface {

    // Добавя книга от json
    // Oтговаря с общия брой копия в библиотеката (не само наличните).
    // Aко са повече от 4 - връща грешка
    AddBookJSON(data []byte) (int, error)

    // Добавя книга от xml
    // Oтговаря с общия брой копия в библиотеката (не само наличните).
    // Ако са повече от 4 - връщаме грешка
    AddBookXML(data []byte) (int, error)

    // Ангажира свободен "библиотекар" да ни обработва заявките.
    // Библиотекарите са фиксиран брой - подават се като параметър на NewLibrary
    // Блокира ако всички библиотекари са заети.
    // Връщат се два канала:
    // първият е само за писане -  по него ще изпращаме заявките
    // вторият е само за четене - по него ще получаваме отговорите.
    // Ако затворим канала със заявките - освобождаваме библиотекаря.
    Hello() (chan<- LibraryRequest, <-chan LibraryResponse)
}

type LibraryRequest interface {
    // Тип на заявката:
    // 1 - Borrow book
    // 2 - Return book
    // 3 - Get availability information about book
    GetType() int

    // Връща isbn на книгата, за която се отнася Request-a
    GetISBN() string
}

type LibraryResponse interface {
    // Ако книгата съществува/налична е - обект имплементиращ Stringer (повече информация по-долу)
    // Aко книгата не съществува първият резултат е nil.
    // Връща се и подобаващa грешка (виж по-долу) - ако такава е възникнала.
    // Когато се е резултат на заявка от тип 2 (Return book) - не е нужно да я закачаме към отговора.
    GetBook() (fmt.Stringer, error)

    // available - Колко наличности от книгата имаме останали след изпълнението на заявката.
    // Тоест, ако сме имали 3 копия от Х и това е отговор на Take заявка - тук ще има 2.
    // registered - Колко копия от тази книга има регистрирани в библиотеката (макс 4).
    GetAvailability() (available int, registered int)
}

type Book struct {
    XMLName xml.Name `xml:"book"`
    ISBN    string   `json:"isbn" xml:"isbn,attr"`
    Title   string   `json:"title" xml:"title"`
    Author  struct {
        FirstName string `json:"first_name" xml:"first_name"`
        LastName  string `json:"last_name" xml:"last_name"`
    } `json:"author" xml:"author"`
    Ratings []uint8 `json:"ratings" xml:"ratings>rating"`
}

type SimpleLibrary struct {
    books                                   map[string]*Book
    registeredCopyCount, availableCopyCount map[string]int
    librarians, sync                        chan struct{}
}

type SimpleLibraryRequest struct {
    requestType int
    bookISBN    string
}

type SimpleLibraryResponse struct {
    book                                    *Book
    registeredCopyCount, availableCopyCount int
    err                                     error
}

type BookError struct {
    ISBN string
}

type TooManyCopiesBookError struct {
    BookError
}

type NotFoundBookError struct {
    BookError
}

type NotAvailableBookError struct {
    BookError
}

type AllCopiesAvailableBookError struct {
    BookError
}

func (b *Book) String() string {
    return fmt.Sprintf("[%v] %v от %v %v", b.ISBN, b.Title, b.Author.FirstName, b.Author.LastName)
}

func (e *TooManyCopiesBookError) Error() string {
    return fmt.Sprintf("Има 4 копия на книга %v", e.ISBN)
}

func (e *NotFoundBookError) Error() string {
    return fmt.Sprintf("Непозната книга %v", e.ISBN)
}

func (e *NotAvailableBookError) Error() string {
    return fmt.Sprintf("Няма наличност на книга %v", e.ISBN)
}

func (e *AllCopiesAvailableBookError) Error() string {
    return fmt.Sprintf("Всички копия са налични %v", e.ISBN)
}

func (r *SimpleLibraryRequest) GetType() int {
    return r.requestType
}

func (r *SimpleLibraryRequest) GetISBN() string {
    return r.bookISBN
}

func (r *SimpleLibraryRequest) SetType(t int) {
    r.requestType = t
}

func (r *SimpleLibraryRequest) SetISBN(isbn string) {
    r.bookISBN = isbn
}

func (r *SimpleLibraryResponse) GetBook() (fmt.Stringer, error) {
    return r.book, r.err
}

func (r *SimpleLibraryResponse) GetAvailability() (int, int) {
    return r.availableCopyCount, r.registeredCopyCount
}

func (sl *SimpleLibrary) addBook(book *Book) (registeredCopyCount int, err error) {
    if sl.registeredCopyCount[book.ISBN] >= 4 {
        err = &TooManyCopiesBookError{BookError{book.ISBN}}
    } else {
        sl.books[book.ISBN] = book
        sl.registeredCopyCount[book.ISBN]++
        sl.availableCopyCount[book.ISBN]++
    }

    registeredCopyCount = sl.registeredCopyCount[book.ISBN]
    return
}

func (sl *SimpleLibrary) AddBookJSON(data []byte) (int, error) {
    book := &Book{}
    json.Unmarshal(data, book)
    return sl.addBook(book)
}

func (sl *SimpleLibrary) AddBookXML(data []byte) (int, error) {
    book := &Book{}
    xml.Unmarshal(data, book)
    return sl.addBook(book)
}

func (sl *SimpleLibrary) Hello() (chan<- LibraryRequest, <-chan LibraryResponse) {
    requests := make(chan LibraryRequest, bufferSize)
    responses := make(chan LibraryResponse, bufferSize)

    <-sl.librarians

    go func() {
        for request := range requests {
            isbn := request.GetISBN()

            <-sl.sync
            book, isBookRegistered := sl.books[isbn]
            sl.sync <- struct{}{}

            response := &SimpleLibraryResponse{}

            if !isBookRegistered {
                response.err = &NotFoundBookError{BookError{isbn}}
                responses <- response
                return
            }

            <-sl.sync

            switch request.GetType() {
            case TakeBook:
                if sl.availableCopyCount[isbn] > 0 {
                    sl.availableCopyCount[isbn]--
                    response.book = book
                } else {
                    response.err = &NotAvailableBookError{BookError{isbn}}
                }

            case ReturnBook:
                if sl.availableCopyCount[isbn] < sl.registeredCopyCount[isbn] {
                    sl.availableCopyCount[isbn]++
                    response.book = book
                } else {
                    response.err = &AllCopiesAvailableBookError{BookError{isbn}}
                }

            case GetAvailability:
                response.book = book
            }

            response.registeredCopyCount = sl.registeredCopyCount[isbn]
            response.availableCopyCount = sl.availableCopyCount[isbn]
            sl.sync <- struct{}{}

            responses <- response
        }

        sl.librarians <- struct{}{}
    }()

    return requests, responses
}

func NewLibrary(librarians int) Library {
    sl := &SimpleLibrary{
        books:               make(map[string]*Book),
        registeredCopyCount: make(map[string]int),
        availableCopyCount:  make(map[string]int),
        librarians:          make(chan struct{}, librarians),
        sync:                make(chan struct{}, 1),
    }

    for i := 0; i < librarians; i++ {
        sl.librarians <- struct{}{}
    }

    sl.sync <- struct{}{}
    return sl
}