DROP DATABASE IF EXISTS LibraryMiniWorld;

CREATE DATABASE IF NOT EXISTS LibraryMiniWorld;
USE LibraryMiniWorld;

CREATE TABLE Person (
  PersonID INTEGER PRIMARY KEY AUTO_INCREMENT,
  Phone VARCHAR(20) NOT NULL,
  Name VARCHAR(50) NOT NULL,
  Email VARCHAR(100) NOT NULL,
  Address VARCHAR(255) NOT NULL
);

CREATE TABLE Customer (
  PersonID INTEGER PRIMARY KEY,
  JoinDate DATE NOT NULL,
  FOREIGN KEY (PersonID) REFERENCES Person(PersonID)
);

CREATE TABLE LibraryCard (
  CardNo INTEGER PRIMARY KEY AUTO_INCREMENT,
  CustomerID INTEGER NOT NULL,
  IssueDate DATE NOT NULL,
  ExpiryDate DATE NOT NULL,
  FOREIGN KEY (CustomerID) REFERENCES Customer(PersonID)
);

CREATE TABLE Employee (
  PersonID INTEGER PRIMARY KEY,
  StartDate DATE NOT NULL,
  Salary INTEGER NOT NULL,
  EmployeeType VARCHAR(20) NOT NULL, -- Either "Librarian" or "Manager"
  FOREIGN KEY (PersonID) REFERENCES Person(PersonID)
);

CREATE TABLE Manager (
  EmployeeID INTEGER PRIMARY KEY,
  FOREIGN KEY (EmployeeID) REFERENCES Employee(PersonID)
);

CREATE TABLE Librarian (
  EmployeeID INTEGER PRIMARY KEY,
  FOREIGN KEY (EmployeeID) REFERENCES Employee(PersonID)
);

CREATE TABLE Manages (
  ManagerID INTEGER NOT NULL,
  LibrarianID INTEGER NOT NULL,
  FOREIGN KEY (ManagerID) REFERENCES Manager(EmployeeID),
  FOREIGN KEY (LibrarianID) REFERENCES Librarian(EmployeeID),
  PRIMARY KEY (ManagerID, LibrarianID)
);

CREATE TABLE Media (
  MediaID INTEGER PRIMARY KEY AUTO_INCREMENT,
  DeweyNumber VARCHAR(20) NOT NULL,
  Name VARCHAR(100) NOT NULL,
  MediaType VARCHAR(20) NOT NULL, -- Either "Text", "Audio", or "Video"
  CreatedYear YEAR NOT NULL
);

CREATE TABLE Genre (
  GenreID INTEGER PRIMARY KEY AUTO_INCREMENT,
  Name VARCHAR(50) NOT NULL UNIQUE
);

CREATE TABLE Tag (
  TagID INTEGER PRIMARY KEY AUTO_INCREMENT,
  Name VARCHAR(50) NOT NULL UNIQUE
);

CREATE TABLE Creator (
  CreatorID INTEGER PRIMARY KEY AUTO_INCREMENT,
  Name VARCHAR(50) NOT NULL
);

CREATE TABLE MediaGenre (
  MediaID INTEGER NOT NULL,
  GenreID INTEGER NOT NULL,
  FOREIGN KEY (MediaID) REFERENCES Media(MediaID),
  FOREIGN KEY (GenreID) REFERENCES Genre(GenreID),
  PRIMARY KEY (MediaID, GenreID)
);

CREATE TABLE MediaTag (
  MediaID INTEGER NOT NULL,
  TagID INTEGER NOT NULL,
  FOREIGN KEY (MediaID) REFERENCES Media(MediaID),
  FOREIGN KEY (TagID) REFERENCES Tag(TagID),
  PRIMARY KEY (MediaID, TagID)
);

CREATE TABLE MediaCreator (
  MediaID INTEGER NOT NULL,
  CreatorID INTEGER NOT NULL,
  FOREIGN KEY (MediaID) REFERENCES Media(MediaID),
  FOREIGN KEY (CreatorID) REFERENCES Creator(CreatorID),
  PRIMARY KEY (MediaID, CreatorID)
);

CREATE TABLE Text (
  MediaID INTEGER PRIMARY KEY,
  Pages INTEGER NOT NULL,
  FOREIGN KEY (MediaID) REFERENCES Media(MediaID)
);

CREATE TABLE Audio (
  MediaID INTEGER PRIMARY KEY,
  Playtime INTEGER NOT NULL,
  FOREIGN KEY (MediaID) REFERENCES Media(MediaID)
);

CREATE TABLE Video (
  MediaID INTEGER PRIMARY KEY,
  Runtime INTEGER NOT NULL,
  FOREIGN KEY (MediaID) REFERENCES Media(MediaID)
);

CREATE TABLE MediaInstance (
  InstanceID INTEGER PRIMARY KEY AUTO_INCREMENT,
  LibrarianID INTEGER NOT NULL,
  MediaID INTEGER NOT NULL,
  DateAdded DATE NOT NULL,
  FOREIGN KEY (MediaID) REFERENCES Media(MediaID),
  FOREIGN KEY (LibrarianID) REFERENCES Librarian(EmployeeID)
);

CREATE TABLE CustomerRate (
  CustomerID INTEGER NOT NULL,
  MediaID INTEGER NOT NULL,
  Stars INTEGER NOT NULL CHECK (Stars >= 0 AND Stars <= 5),
  Review VARCHAR(690),
  FOREIGN KEY (CustomerID) REFERENCES Customer(PersonID),
  FOREIGN KEY (MediaID) REFERENCES Media(MediaID),
  PRIMARY KEY (CustomerID, MediaID)
);

CREATE TABLE Borrow (
  LibraryCardNo INTEGER NOT NULL,
  InstanceID INTEGER NOT NULL,
  LibrarianID INTEGER NOT NULL,
  IsReturned BOOLEAN NOT NULL,
  BorrowDate DATE NOT NULL,
  ReturnDate DATE NOT NULL,
  CONSTRAINT BorrowDateBeforeReturn CHECK (BorrowDate <= ReturnDate),
  FOREIGN KEY (LibraryCardNo) REFERENCES LibraryCard(CardNo),
  FOREIGN KEY (InstanceID) REFERENCES MediaInstance(InstanceID),
  FOREIGN KEY (LibrarianID) REFERENCES Librarian(EmployeeID),
  PRIMARY KEY (InstanceID, BorrowDate)
);

ALTER TABLE Creator ADD FULLTEXT (Name);
ALTER TABLE Tag ADD FULLTEXT (Name);
ALTER TABLE Genre ADD FULLTEXT (Name);
ALTER TABLE Media ADD FULLTEXT (Name, MediaType, DeweyNumber);


CREATE VIEW MediaQuery
AS 
  SELECT me.*,
    GROUP_CONCAT(DISTINCT(c.name)) AS Creators, 
    GROUP_CONCAT(DISTINCT(t.name)) AS Tags, 
    GROUP_CONCAT(DISTINCT(g.name)) AS Genres, 
    COUNT(DISTINCT(mi.InstanceID)) AS Copies, 
    COUNT(DISTINCT(mi.InstanceID)) - COUNT(DISTINCT(b.InstanceID)) AS AvailableCopies
  FROM Media AS me 
  INNER JOIN MediaInstance AS mi ON me.MediaID = mi.MediaID
    LEFT JOIN MediaGenre AS mg ON me.MediaID = mg.MediaID
      LEFT JOIN Genre AS g ON mg.GenreID = g.GenreID
    LEFT JOIN MediaTag AS mt ON me.MediaID = mt.MediaID
      LEFT JOIN Tag AS t ON mt.TagID = t.TagID
    LEFT JOIN MediaCreator AS mc ON me.MediaID = mc.MediaID
      LEFT JOIN Creator AS c ON mc.CreatorID = c.CreatorID
  LEFT JOIN Borrow AS b ON b.InstanceID = mi.InstanceID AND b.IsReturned
  GROUP BY me.MediaID;


DELIMITER //
CREATE TRIGGER BorrowCheck
BEFORE INSERT ON Borrow
FOR EACH ROW
BEGIN
  IF NEW.InstanceID IN 
    (SELECT InstanceID FROM Borrow AS b 
      WHERE New.BorrowDate <= b.ReturnDate 
      AND New.ReturnDate >= b.BorrowDate 
      AND NOT b.IsReturned) 
  THEN
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Media instance is already borrowed';
  END IF;
END;
//

DELIMITER //
CREATE TRIGGER BorrowNowOrFuture
BEFORE INSERT ON Borrow
FOR EACH ROW
BEGIN
  IF NEW.BorrowDate < CURDATE() AND NEW.ReturnDate >= CURDATE() THEN
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Borrow date cannot be in the past unless return date is as well'; 
  END IF;
END;
//

DELIMITER //
CREATE TRIGGER CustomerCantRegisterForNewCardWhenHasOneNotExpired
BEFORE INSERT ON LibraryCard
FOR EACH ROW
BEGIN
  IF NEW.ExpiryDate >= CURDATE() AND NEW.CustomerID IN 
    (SELECT CustomerID FROM LibraryCard WHERE ExpiryDate >= CURDATE()) 
  THEN
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Customer already has an active library card';
  END IF;
END;
//

DELIMITER //
CREATE TRIGGER LibraryCardNotExpired
BEFORE INSERT ON Borrow
FOR EACH ROW
BEGIN
  IF NOT EXISTS 
    (SELECT CardNo FROM LibraryCard 
      WHERE CardNo = NEW.LibraryCardNo 
      AND ExpiryDate >= NEW.BorrowDate) 
  THEN
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Library card is expired';
  END IF;
END;
//

DELIMITER //
CREATE TRIGGER CustomerCanOnlyRateMediaTheyBorrowed
BEFORE INSERT ON CustomerRate
FOR EACH ROW
BEGIN
  IF New.CustomerID NOT IN 
    (SELECT lc.CustomerID 
     FROM Borrow AS b, MediaInstance AS i, Media AS m, LibraryCard AS lc 
     WHERE b.InstanceID = i.InstanceID
     AND b.LibraryCardNo = lc.CardNo
     AND i.MediaID = m.MediaID 
     AND m.MediaID = New.MediaID) 
  THEN
    SIGNAL SQLSTATE '45000' 
    SET MESSAGE_TEXT = 'Customer can only rate media they borrowed';
  END IF;
END;
//

DELIMITER //
CREATE PROCEDURE 
  borrowedHistory( customerID INT )
BEGIN  
  SELECT mi.MediaID, me.Name, b.BorrowDate, b.ReturnDate, b.IsReturned, l.Name AS Librarian
  FROM MediaInstance AS mi, Media AS me, Borrow AS b, Person AS l, LibraryCard AS lc
  WHERE mi.InstanceID = b.InstanceID 
  AND mi.MediaID = me.MediaID 
  AND b.LibrarianID = l.PersonID
  AND b.LibraryCardNo = lc.CardNo
  AND lc.CustomerID = customerID;
END;
//

DELIMITER // 
CREATE PROCEDURE 
  searchMedia (searchQuery VARCHAR(255))
  BEGIN 
    SELECT q.MediaID, q.DeweyNumber, q.Name, q.MediaType, q.CreatedYear, 
           q.Creators, q.Tags, q.Genres, q.Copies, q.AvailableCopies 
    FROM (
      SELECT me.*,
        GROUP_CONCAT(DISTINCT(c.name)) AS Creators, 
        GROUP_CONCAT(DISTINCT(t.name)) AS Tags, 
        GROUP_CONCAT(DISTINCT(g.name)) AS Genres, 
        COUNT(DISTINCT(mi.InstanceID)) AS Copies, 
        COUNT(DISTINCT(mi.InstanceID)) - COUNT(DISTINCT(b.InstanceID)) AS AvailableCopies,
        MAX(MATCH (me.Name, me.MediaType, me.DeweyNumber) AGAINST (searchQuery)) AS NameScore,
        MAX(MATCH (c.Name) AGAINST (searchQuery)) AS CreatorScore,
        MAX(MATCH (t.Name) AGAINST (searchQuery)) AS TagScore,
        MAX(MATCH (g.Name) AGAINST (searchQuery)) AS GenreScore
      FROM Media AS me 
      LEFT JOIN MediaInstance AS mi ON me.MediaID = mi.MediaID
      LEFT JOIN MediaGenre AS mg ON me.MediaID = mg.MediaID
        LEFT JOIN Genre AS g ON mg.GenreID = g.GenreID
      LEFT JOIN MediaTag AS mt ON me.MediaID = mt.MediaID
        LEFT JOIN Tag AS t ON mt.TagID = t.TagID
      LEFT JOIN MediaCreator AS mc ON me.MediaID = mc.MediaID
        LEFT JOIN Creator AS c ON mc.CreatorID = c.CreatorID
      LEFT JOIN Borrow AS b ON b.InstanceID = mi.InstanceID AND b.IsReturned
      GROUP BY me.MediaID) AS q
    WHERE NameScore > 0 OR CreatorScore > 0 OR TagScore > 0 OR GenreScore > 0 
    ORDER BY NameScore DESC, CreatorScore DESC, TagScore DESC, GenreScore DESC, AvailableCopies DESC;
  END;
//

DELIMITER //
CREATE PROCEDURE borrow( libraryCardNo INT, mediaID INT, librarianID INT, returnDate DATE, OUT instanceID INT )
BEGIN
  DECLARE instanceID INT;

  SELECT mi.InstanceID INTO instanceID
  FROM MediaInstance AS mi
  LEFT JOIN Borrow AS b ON b.InstanceID = mi.InstanceID AND b.IsReturned
  WHERE mi.MediaID = mediaID AND b.InstanceID IS NULL
  LIMIT 1;

  INSERT INTO Borrow (LibraryCardNo, InstanceID, LibrarianID, BorrowDate, ReturnDate, IsReturned)
  VALUES (libraryCardNo, instanceID, librarianID, CURDATE(), returnDate, FALSE);
END;
//