package sklep.rest;

import java.math.BigDecimal;
import java.net.URI;
import java.util.List;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

import sklep.db.DBConnection;
import sklep.db.DBException;
import sklep.db.ProductDAO;
import sklep.db.RecordNotFound;
import sklep.model.Product;
import sklep.photo.PhotoUtil;

// To jest "klasa zasobu" (resource class), czyli klasa odpowiedzialna za obsługę rekordów określonego typu.
// Adres /products oznacza "katalog produktów", a adres /products/3 wskazuje konkretny produkt nr 3.
// Zapytania typu GET oznaczają odczyt danych, PUT zapis, DELETE usunięcie.
// POST może oznaczać różne rzeczy, ale najczęściej jest uzywany do dodawania nowych rekordów.

@Path("/products")
public class RProducts {
	// Gdy w adnotacji @Produces umieszczona jest lista content-typów,
	// to serwer jest w stanie wygenerować odpowiedź w każdym z tych formatów.
	// Jeśli w zapytaniu znajduje się nagłówek Accept, to znaczy, że klient prosi o podany format (lub kilka)
	// i serwer stara się dostosować do tych preferencji.
	// Jeśli nie ma nagłówka Accept, to standard JAX-RS nie mówi co ma się stać,
	// ale implementacja REST Easy (WildFly/JBoss) zastosuje pierwszy z formatów podanych w adnmotacji.
	// Glassfish i WebLogic tak nie robią - wybierają "losowo".
	@GET
	@Produces({"application/json", "application/xml", "text/plain", "application/pdf"})
	public List<Product> readAll() throws DBException {
		try(DBConnection db = DBConnection.open()) {
			ProductDAO productDAO = db.productDAO();
			return productDAO.readAll();
		}
	}
	
	// W ramach JAX-RS można utworzyć wiele metod, które działają pod tym samym adresem,
	// ale różnią się obsługiwanymi formatami danych (Produces/Consumes).
	// W zależności od preferecji klienta zostanie wywołana jedna lub druga metoda.
	@GET
	@Produces("text/html")
	public String readAllHTML() throws DBException {
		List<Product> products = readAll();
		StringBuilder txt = new StringBuilder("<html><body>");
		txt.append("<h1>Lista produktów:</h1>");
		for(Product product : products) {
			txt.append(product.toHtml());
		}
		txt.append("</body></html>");
		return txt.toString();
	}
	
	@GET
	@Produces({"application/json", "application/xml", "text/plain", "application/pdf"})
	@Path("/{id}")
	public Product readOne(@PathParam("id") int productId) throws DBException, RecordNotFound {
		try(DBConnection db = DBConnection.open()) {
			ProductDAO productDAO = db.productDAO();
			return productDAO.findById(productId);
		}
	}
	
	@GET
	@Produces("text/html")
	@Path("/{id}")
	public String readOneHTML(@PathParam("id") int productId) throws DBException, RecordNotFound {
		Product product = readOne(productId);
		StringBuilder txt = new StringBuilder("<html><body>");
		txt.append("<h1>Odczytany produkt:</h1>");
		txt.append(product.toHtml());
		txt.append("</body></html>");
		return txt.toString();
	}
	
	// To twórca usługi decyduje o tym, jakie dokładnie adresy będą dostepne, nie ma obowiązku obsługiwania wszystkich,
	// ale gdybyśmy chcieli dać bezpośredni doste do wybranego pola w rekordzie, to tak jak w tym przykładzie.
	// Struktura adresów URL powjnna odpowiadać logicznej strukturze danych.
	@GET
	@Produces({"application/json", "text/plain"})
	@Path("/{id}/price")
	public BigDecimal getPrice(@PathParam("id") int productId) throws DBException, RecordNotFound {
		try(DBConnection db = DBConnection.open()) {
			ProductDAO productDAO = db.productDAO();
			Product product = productDAO.findById(productId);
			return product.getPrice();
		}
	}
	
	// Operacja PUT oznacza "zapisz dane pod podanym adresem".
	// Może być uzyta zarówno do zapisnaia nowych danych, jak i do aktualiazcji, ale najważniejsze jest, że musimy podać adres, pod którym zapisujemy.
	// W zapytaniu PUT jest przysyłana jakaś treść (content/body/entity)
	// - format, w jakim te dane są przysyłane, opisuje adnotacja @Consumes
	// - metoda może mieć jeden parametr bez żadnej adnotacji i to właśnie poprzez ten prametr jest przekazywna przysłana wartość
	
	// Gdy adnotacja @Consumes zawiera kilka formatów, to klient może przysłać dane w każdym z nich.
	@PUT
	@Consumes({"text/plain", "application/json"})
	@Path("/{id}/price")
	public void setPrice(@PathParam("id") int productId, BigDecimal newPrice) throws DBException, RecordNotFound {
		try(DBConnection db = DBConnection.open()) {
			ProductDAO productDAO = db.productDAO();
			Product product = productDAO.findById(productId);
			product.setPrice(newPrice);
			productDAO.update(product);
			db.commit();
		}
	}
	
	// Powszechnie przyjętą praktyką w REST jest to, że
	// zapytanie typu POST przysłane pod adres katalogu (np. /products)
	// jest traktowane jako polecenie dodania nowego rekordu.
	// W tej wersji zwracamy kod "201 Created" ze wskazaną lokalizacją nowoutworzonego rekordu.
	// Jest to przykład sesnownego użycia klasy Response.
	@POST
	@Consumes({"application/json", "application/xml"})
	public Response zapisz(Product product,
			@Context UriInfo uriInfo) throws DBException {
		try(DBConnection db = DBConnection.open()) {
			ProductDAO productDAO = db.productDAO();
			productDAO.save(product);
			db.commit();
			URI location = uriInfo.getBaseUriBuilder() // bazowy adres aplikacji
					.path(RProducts.class) // krok obejmujący katalog produktów /products
					.path("/{id}") // krok obejmujcy id produktu, np. /2
					.build(product.getProductId()); // podstawienie ID tego konkretnego produktu w miejsce {}
			return Response.created(location).build();
		}
	}

	@DELETE
	@Path("/{id}")
	public void removeProduct(@PathParam("id") int productId) throws DBException, RecordNotFound {
		try(DBConnection db = DBConnection.open()) {
			ProductDAO productDAO = db.productDAO();
			if(productDAO.delete(productId)) {
				db.commit();
			} else {
				throw new RecordNotFound("Nie ma produktu nr " + productId);
			}
		}
	}
	
	@GET
	@Path("/{id}/photo")
	@Produces("image/jpeg")
	public byte[] getPhoto(@PathParam("id") int productId) throws DBException, RecordNotFound {
		return PhotoUtil.readBytes(productId);
	}

}
