public class CoversTable {

    private Map<String,CoverTableItem> items;

    public CoversTable() {
        this.items = new HashMap<>();
    }

    public Map<String,CoverTableItem> getItems() { return items; }

    public void setItems(Map<String,CoverTableItem> items) { this.items = items; }

    public void addCoverItem(CoverItem ci, float price) {
        CoverTableItem cti = new CoverTableItem();
        if(this.items.containsKey(ci.getCoverType().toString())) {
            cti = this.items.get(ci.getCoverType().toString());
            if(ci.getFormat().equals(Item.Format.A4)) {
                cti.setPriceA4(String.valueOf(price));
            }
            else if(ci.getFormat().equals(Item.Format.A3)) {
                cti.setPriceA3(String.valueOf(price));
            }
            this.items.put(ci.getCoverType().toString(),cti);
        }
        else {
            cti.setCoverType(ci.getCoverType().toString());
            if(ci.getFormat().equals(Item.Format.A4)) {
                cti.setPriceA4(String.valueOf(price));
            }
            else if(ci.getFormat().equals(Item.Format.A3)) {
                cti.setPriceA3(String.valueOf(price));
            }
            this.items.put(ci.getCoverType().toString(),cti);
        }
    }
} public class PapersTable {
    private Map<String,Map<String,PaperTableItem>> items = new HashMap<>();

    public PapersTable() {
        this.items = new HashMap<>();
    }

    public Map<String, Map<String,PaperTableItem>> getItems() { return items; }

    public void setItems(Map<String, Map<String,PaperTableItem>> items) { this.items = items; }

    /**
     * Insert a (Range)PaperItem in a pricetable.
     * @param pi, the PaperItem to be inserted.
     * @param pshop, the current print shop instance.
     * @return the updated price table.
     */
    public void addRangePaperItem(RangePaperItem pi, PrintShop pshop) {
        if (!items.containsKey(pi.getColors().toString())) { // The color is new
            // Create new PaperTableItem
            PaperTableItem pti = new PaperTableItem(pi.getInfLim(), pi.getSupLim());
            pti.addPriceToPaperTableItem(pi, pshop.getPrice(pi));
            pti.setColors(pi.getColors().toString());

            // Add new range and associated PaperTableItem instance
            Map<String,PaperTableItem> map = new HashMap<>();
            map.put(pti.genKey(), pti);

            // Add to table
            items.put(pi.getColors().toString(), map);

        } else { // The color already exists
            String ptiKey = pi.getInfLim() + ";" + pi.getSupLim();
            Map<String, PaperTableItem> aux = items.get(pi.getColors().toString());
            PaperTableItem pti = aux.get(ptiKey);

            if (pti != null) {
                // PriceTableItem instance already exists add price
                pti.addPriceToPaperTableItem(pi, pshop.getPrice(pi));
                pti.setColors(pi.getColors().toString());
                items.get(pi.getColors().toString()).put(pti.genKey(), pti);
            } else {
                // Create new PaperTableItem
                pti = new PaperTableItem(pi.getInfLim(), pi.getSupLim());
                pti.setColors(pi.getColors().toString());
                pti.addPriceToPaperTableItem(pi, pshop.getPrice(pi));

                // Add new range and associated PaperTableItem instance
                Map<String,PaperTableItem> map = items.get(pi.getColors().toString());
                map.put(pti.genKey(), pti);

                // Add to table
                items.put(pi.getColors().toString(), map);
            }
        }
    }

    public Map<String, Set<PaperTableItem>> getFinalTable() {
        Map<String, Set<PaperTableItem>> finalTable = new HashMap<>();

        // Covert Map<String,PaperTableItem> into Set<PaperTableItem>
        for(String color : items.keySet()) {
            Map<String,PaperTableItem> map = items.get(color);
            Set<PaperTableItem> set = new TreeSet<>();
            for(PaperTableItem pti : map.values()) {
                set.add(pti);
            }
            finalTable.put(color,set);
        }

        return finalTable;
    }
} public class RingsTable {
    public static String DEFAULT = "-";

    private Map<String,Set<RingTableItem>> items;
    private float staplingPrice;

    public RingsTable() {
        this.items = new HashMap<>();
        this.staplingPrice = 0;
    }

    public Map<String, Set<RingTableItem>> getItems() {
        return items;
    }

    public void setItems(Map<String, Set<RingTableItem>> items) {
        this.items = items;
    }

    public float getStaplingPrice() { return staplingPrice; }

    public void setStaplingPrice(float staplingPrice) { this.staplingPrice = staplingPrice; }

    public void addBindingItem(BindingItem bi, float price) {
        if(bi.getRingsType().equals(BindingItem.RingType.STAPLING)) {
            this.staplingPrice = price;
        }
        else {
            RingTableItem rti = new RingTableItem(RingTableItem.getPresentationStringForRings(bi.getRingsType()), bi.getRingThicknessInfLim(), bi.getRingThicknessSupLim(), String.valueOf(price));
            if (items.containsKey(bi.getRingsType().toString())) {
                this.items.get(bi.getRingsType().toString()).add(rti);
            } else {
                Set<RingTableItem> newRingSet = new TreeSet<>();
                newRingSet.add(rti);
                this.items.put(bi.getRingsType().toString(), newRingSet);
            }
        }
    }

} @Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@Table(name = "admin")
public class Admin extends User {

    @Column(name = "email", nullable = true)
    private String email;

    @Column(unique = true)
    private Money balance;

    public Admin() {
        super.addRole(User.Roles.ROLE_ADMIN.name());
        this.balance = new Money();
    }

    public Admin(String username, String password, String email) {
        super(username, password);
        super.addRole(User.Roles.ROLE_ADMIN.name());
        this.email = email;
        this.balance = new Money();
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "Admin{" + super.toString()
                + "email='" + email + '\''
                + '}';
    }

    public Money getBalance() { return balance; }

    public void setBalance(Money balance) { this.balance = balance; }
} @Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@Table(name = "consumers")
public class Consumer extends User {

    @Column(name = "name", nullable = false)
    private String name;
    @Column(name = "email", nullable = true) // all true because null constraint block adition of employees
    private String email;
    @Column(name = "latitude", nullable = true)
    private String latitude;
    @Column(name = "longitude", nullable = true)
    private String longitude;

    @JoinColumn(name = "consumer_id")
    @OneToMany(cascade = CascadeType.ALL)
    private Set<PrintingSchema> printingSchemas;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "consumer")
    @Exclude
    private Set<PrintRequest> printrequests;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "consumer")
    @Exclude
    private List<Notification> notifications;

    @Column(unique = true)
    private Money balance;

    public Consumer() {
        this.printingSchemas = new HashSet<>();
        this.notifications = new ArrayList<>();
        super.addRole(User.Roles.ROLE_USER.name());
        this.balance = new Money();
    }

    public Consumer(String name, String username, String password, String email, String latitude, String longitude) {
        super(username, password);
        super.addRole(User.Roles.ROLE_USER.toString());
        this.name = name;
        this.email = email;
        this.latitude = latitude;
        this.longitude = longitude;
        this.printingSchemas = new HashSet<>();
        this.notifications = new ArrayList<>();
        this.balance = new Money();
    }

    public Consumer(String username, String password, String name, String email, String latitude, String longitude, Set<PrintingSchema> printingSchemas) {
        super(username, password);
        this.name = name;
        this.email = email;
        this.latitude = latitude;
        this.longitude = longitude;
        this.printingSchemas = printingSchemas;
        this.balance = new Money();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getLatitude() {
        return latitude;
    }

    public void setLatitude(String latitude) {
        this.latitude = latitude;
    }

    public String getLongitude() {
        return longitude;
    }

    public void setLongitude(String longitude) {
        this.longitude = longitude;
    }

    public Set<PrintingSchema> getPrintingSchemas() {
        return printingSchemas;
    }

    public void setPrintingSchemas(Set<PrintingSchema> printingSchemas) {
        this.printingSchemas = printingSchemas;
    }

    public boolean addPrintingSchema(PrintingSchema ps) {
        return this.printingSchemas.add(ps);
    }

    public boolean deletePrintingSchema(long psID) {
        Iterator it = this.printingSchemas.iterator();
        while (it.hasNext()) {
            PrintingSchema ps = (PrintingSchema) it.next();
            if (ps.getId() == psID) {
                it.remove();
                return true;
            }
        }
        return false;
    }

    public Set<PrintRequest> getPrintRequests() {
        return printrequests;
    }

    public void addPrintRequest(PrintRequest printrequest) {
        this.printrequests.add(printrequest);
    }

    public void addNotifications(Notification notification) {
        this.notifications.add(notification);
    }

    public List<Notification> getNotifications() {
        return new ArrayList<>(notifications);
    }
    
    public void removeAllNotifications () {
        this.notifications.clear();
    }
    
    public void readAllNotifications() {
       for (Notification n : notifications)
           n.setReadStatus(true);
    }

    public Money getBalance() { return balance; }

    public void setBalance(Money balance) { this.balance = balance; }

    @Override
    public String toString() {
        return "Consumer{" + super.toString()
                + "name='" + name + '\''
                + ", email='" + email + '\''
                + ", latitude='" + latitude + '\''
                + ", longitude='" + longitude + '\''
                + '}';
    }

    
} @Entity
@Table(name = "documents")
public class Document implements Serializable {
    public static String DIRECTORY_PATH;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(name = "file_name", nullable = false)
    private String name;

    @Column(name = "total_pages", nullable = false)
    private int totalPages;

    @OneToMany(cascade = CascadeType.REMOVE)
    @JoinColumn(name = "document_id")
    private Set<DocumentSpec> specs;

    @ManyToOne
    @Exclude
    private PrintRequest printRequest;

    public Document() {
        specs = new HashSet<>();
    }

    public Document(String name, int totalPages) {
        this.name = name;
        this.totalPages = totalPages;
        specs = new HashSet<>();
    }

    public Document(String name, int totalPages, PrintRequest printRequest) {
        this.name = name;
        this.totalPages = totalPages;
        this.specs = new HashSet<>();
        this.printRequest = printRequest;
    }

    public long getId() { return id; }

    public void setId(long id) { this.id = id; }

    public String getName() { return name; }

    public void setName(String name) { this.name = name; }

    public int getTotalPages() { return totalPages; }

    public void setTotalPages(int totalPages) { this.totalPages = totalPages; }

    public Set<DocumentSpec> getSpecs() { return specs; }

    public void setSpecs(Set<DocumentSpec> specs) { this.specs = specs; }

    public void addSpecification(DocumentSpec ds) { this.specs.add(ds); }

    public PrintRequest getPrintRequest() { return printRequest; }

    public void setPrintRequest(PrintRequest printRequest) { this.printRequest = printRequest; }

    @Override
    public String toString() {
        return "Document{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", totalPages=" + totalPages +
                '}';
    }

    public File getFile(){
        return new File(this.id+".pdf");
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Document)) return false;

        Document document = (Document) o;

        if (getId() != document.getId()) return false;
        if (getTotalPages() != document.getTotalPages()) return false;
        if (getName() != null ? !getName().equals(document.getName()) : document.getName() != null)
            return false;
        return getSpecs() != null ? getSpecs().equals(document.getSpecs()) : document.getSpecs() == null;

    }

    @Override
    public int hashCode() {
        int result = (int) (getId() ^ (getId() >>> 32));
        result = 31 * result + (getName() != null ? getName().hashCode() : 0);
        return result;
    }

    public String getPresentationString() {
        StringBuilder sb = new StringBuilder();

        for(DocumentSpec docSpecs : this.specs) {
            sb.append(docSpecs.getPresentationString()).append("\n");
        }

        return sb.toString();
    }

} @Entity
@Table(name = "documents_specs")
public class DocumentSpec {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(name = "first_page", nullable = false)
    private int firstPage;

    @Column(name = "last_page", nullable = false)
    private int lastPage;

    @OneToOne(cascade = CascadeType.DETACH)
    @JoinColumn(name = "printing_schema")
    private PrintingSchema printingSchema;

    @Transient private String specsToString;

    public DocumentSpec() {}

    public DocumentSpec(int firstPage, int lastPage, PrintingSchema printingSchema) {
        this.firstPage = firstPage;
        this.lastPage = lastPage;
        this.printingSchema = printingSchema;
    }

    public long getId() { return id; }

    public void setId(long id) { this.id = id; }

    public int getFirstPage() { return firstPage; }

    public void setFirstPage(int firstPage) { this.firstPage = firstPage; }

    public int getLastPage() { return lastPage; }

    public void setLastPage(int lastPage) { this.lastPage = lastPage; }

    public PrintingSchema getPrintingSchema() { return printingSchema; }

    public void setPrintingSchema(PrintingSchema printingSchema) { this.printingSchema = printingSchema; }

    public void setSpecsToString() {
        String range;
        if(this.firstPage==0 && this.lastPage==0) {
            range = "Documento completo";
        } else {
            range = "Páginas " + this.firstPage + " - " + this.getLastPage();
        }
        this.specsToString = printingSchema.getPresentationString(range);
    }

    @Override
    public String toString() {
        return "DocumentSpec{" +
                "id=" + id +
                ", firstPage=" + firstPage +
                ", lastPage=" + lastPage +
                ", printingSchema=" + printingSchema.toString() +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof DocumentSpec)) return false;

        DocumentSpec that = (DocumentSpec) o;

        if (getId() != that.getId()) return false;
        if (getFirstPage() != that.getFirstPage()) return false;
        if (getLastPage() != that.getLastPage()) return false;
        return getPrintingSchema() != null ? getPrintingSchema().equals(that.getPrintingSchema()) : that.getPrintingSchema() == null;
    }

    @Override
    public int hashCode() {
        int result = (int) (getId() ^ (getId() >>> 32));
        result = 31 * result + getFirstPage();
        result = 31 * result + getLastPage();
        result = 31 * result + (getPrintingSchema() != null ? getPrintingSchema().hashCode() : 0);
        return result;
    }

    public String getPresentationString() {
        StringBuilder sb = new StringBuilder();
        String range;
        if(this.firstPage==0 && this.lastPage==0) {
            range = "Documento completo ";
        } else {
            range = "Páginas "+this.firstPage+"-"+this.getLastPage();
        }
        sb.append(range);
        sb.append(" "+this.printingSchema.getPresentationString(range));

        return sb.toString();
    }
} @Entity
@Table(name = "notification")
public class Notification implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(name = "email", nullable = true)
    private String message;

    @Column(name = "timestamp", nullable = true)
    @Temporal(javax.persistence.TemporalType.TIMESTAMP)
    private Date timestamp;
    
    @Column(name = "read" , nullable=true)
    private boolean read;

    public Notification() {
    }

    public Notification(String message) {
        this.message = message;
        this.timestamp = Date.from(Instant.now());
        this.read = false;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Date getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(Date timestamp) {
        this.timestamp = timestamp;
    }
    
    public boolean getReadStatus() {
        return this.read;
    }
    
    public void setReadStatus(boolean status) {
        this.read = status;
    }

} @Entity
@Table(name = "employees")
public class Employee extends User {

    @Column(name = "name", nullable = false)
    private String name;
    @OneToOne(cascade = CascadeType.DETACH)
    @JoinColumn(name = "printshop_id")
    private PrintShop printShop;

    public Employee() {
        super.addRole(User.Roles.ROLE_EMPLOYEE.name());
    }

    public Employee(String username, String password, String name, PrintShop pshop) {
        super(username, password);
        super.addRole(User.Roles.ROLE_EMPLOYEE.name());
        this.name = name;
        this.printShop = pshop;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public PrintShop getPrintShop() { return printShop; }

    public void setPrintShop(PrintShop printShop) { this.printShop = printShop; }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("Employee{");
        sb.append("name='" + name + '\'');
        sb.append("username='" + username + '\'');
        sb.append("password='" + password + '\'');
        if(printShop!=null) {
            sb.append(", printShop=" + printShop.getName());
        }

        return sb.toString();
    }
} @Entity
@Table(name = "managers")
public class Manager extends User {

    @Column(name = "name", nullable = false)
    private String name;
    @Column(name = "email", nullable = false)
    private String email;
    @OneToOne(cascade = CascadeType.REMOVE)
    @JoinColumn(name = "printshop_id")
    private PrintShop printShop;

    public Manager() {
        super.addRole(User.Roles.ROLE_MANAGER.name());
    }

    public Manager(String username, String password, String name, String email) {
        super(username, password);
        super.addRole(User.Roles.ROLE_MANAGER.name());
        this.name = name;
        this.email = email;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public PrintShop getPrintShop() {
        return printShop;
    }

    public void setPrintShop(PrintShop printShop) {
        this.printShop = printShop;
    }

    @Override
    public String toString() {
        return "Manager{" + super.toString()
                + "name='" + name + '\''
                + ", email='" + email + '\''
                + ", printShopID='" + printShop.toString() + '\''
                + '}';
    }
} public class BindingItem extends Item {
    public static String KEY_BASE = "BINDING";

    public static int B_6_10_THICKNESS_FLOOR = 1;
    public static int B_6_10_THICKNESS_CEIL = 20;
    public static int B_12_20_THICKNESS_FLOOR = 21;
    public static int B_12_20_THICKNESS_CEIL = 80;
    public static int B_22_28_THICKNESS_FLOOR = 81;
    public static int B_22_28_THICKNESS_CEIL = 119;
    public static int B_32_38_THICKNESS_FLOOR = 120;

    private RingType ringsType;
    private int ringThicknessInfLim; // in millimeters
    private int ringThicknessSupLim; // in millimeters

    public BindingItem() {
        this.ringsType = RingType.PLASTIC;
        this.ringThicknessInfLim = 0;
        this.ringThicknessInfLim = 0;
    }

    public BindingItem(RingType ringsType, int ringThicknessInfLim, int ringThicknessSupLim) {
        this.ringsType = ringsType;
        this.ringThicknessInfLim = ringThicknessInfLim;
        this.ringThicknessSupLim = ringThicknessSupLim;
    }

    public RingType getRingsType() { return ringsType; }

    public void setRingsType(RingType ringsType) { this.ringsType = ringsType; }

    public int getRingThicknessInfLim() { return ringThicknessInfLim; }

    public void setRingThicknessInfLim(int ringThicknessInfLim) { this.ringThicknessInfLim = ringThicknessInfLim; }

    public int getRingThicknessSupLim() { return ringThicknessSupLim; }

    public void setRingThicknessSupLim(int ringThicknessSupLim) { this.ringThicknessSupLim = ringThicknessSupLim; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof BindingItem)) return false;

        BindingItem that = (BindingItem) o;

        if (getRingThicknessInfLim() != that.getRingThicknessInfLim()) return false;
        if (getRingThicknessSupLim() != that.getRingThicknessSupLim()) return false;
        return getRingsType() != null ? getRingsType().equals(that.getRingsType()) : that.getRingsType() == null;

    }

    @Override
    public int hashCode() {
        int result = getRingsType() != null ? getRingsType().hashCode() : 0;
        result = 31 * result + getRingThicknessInfLim();
        result = 31 * result + getRingThicknessSupLim();
        return result;
    }

    @Override
    public String genKey() {
        return String.format("%s,%s,%d,%d",KEY_BASE, this.ringsType.toString(), this.ringThicknessInfLim, this.ringThicknessSupLim);
    }
} public class CoverItem extends Item {
    public static String KEY_BASE = "COVER";

    private CoverType coverType;
    private PaperItem.Format format;

    public CoverItem() {
        this.coverType = CoverType.CRISTAL_ACETATE;
        this.format = PaperItem.Format.A4;
    }

    public CoverItem(CoverType coverType, PaperItem.Format format) {
        this.coverType = coverType;
        this.format = format;
    }

    public CoverType getCoverType() { return coverType; }

    public void setCoverType(CoverType coverType) { this.coverType = coverType; }

    public PaperItem.Format getFormat() { return format; }

    public void setFormat(PaperItem.Format format) { this.format = format; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof CoverItem)) return false;

        CoverItem coverItem = (CoverItem) o;

        if (getCoverType() != coverItem.getCoverType()) return false;
        return getFormat() == coverItem.getFormat();

    }

    @Override
    public int hashCode() {
        int result = getCoverType() != null ? getCoverType().hashCode() : 0;
        result = 31 * result + (getFormat() != null ? getFormat().hashCode() : 0);
        return result;
    }

    @Override
    public String genKey() {
        return String.format("%s,%s,%s",KEY_BASE, this.coverType.toString(), this.format.toString());
    }
} public abstract class Item {

    public static String PAPER = "PAPER";
    public static String COVER = "COVER";

    public static String checkItemType(String item) {
        String[] chunks = item.split(",");
        if (chunks[0] != null && (chunks[0].equals(PAPER) || chunks[0].equals(COVER) || chunks[0].equals(Item.RingType.BINDING.toString()))) {
            return chunks[0];
        }
        return "";
    }

    // Paper
    public static enum Format {
        A4, A3
    }

    public static enum Sides {
        SIMPLEX, DUPLEX
    }

    public static enum Colors {
        BW, GREY_TONES, COLOR
    }

    // Cover
    public static enum CoverType {
        CRISTAL_ACETATE, PVC_TRANSPARENT, PVC_OPAQUE
    }

    // Binding
    public static enum RingType {
        PLASTIC, SPIRAL, WIRE, STEELMAT, STAPLING, BINDING
    }

    public Item() {
    }

    /**
     * Generates a key which identifies (and describes) a certain item.
     *
     * @return the key which identifies the item by the
     * concatenation of its characteristics.
     */
    public abstract String genKey();

} public class NotificationManager {
  
    private final Logger logger = LoggerFactory.getLogger(WebAppConfig.class);
    
    @Autowired
    private ConsumerDAO consumers;
    @Autowired
    private NotificationDAO notifications;

    private final Multimap<String, SseEmitter> subscriptions;

    public NotificationManager() {
        this.subscriptions = HashMultimap.create();
    }

    public SseEmitter subscribe(String username) {
        SseEmitter sseEmitter = new SseEmitter(0l);
        sseEmitter.onCompletion(new DeleteTask(this, username, sseEmitter));
        this.subscriptions.put(username, sseEmitter);
        return sseEmitter;
    }

    public void unsubscribe(String username, SseEmitter emitter) {
        this.subscriptions.remove(username, emitter);
    }

    public void sendNotification(String username, Notification notification){
        Consumer consumer = this.consumers.findByUsername(username);
        notification = notifications.save(notification);
        consumer.addNotifications(notification);
        consumers.save(consumer);

        if (this.subscriptions.containsKey(username)) {
            for (SseEmitter sse : this.subscriptions.get(username)) {
                try {
                    sse.send(notification, MediaType.APPLICATION_JSON);
                } catch (IOException ex) {
                    this.logger.warn("Broken SSE Emitter..discarding it...");
                }
            }
        }
    }

    public void removeNotification(long notificationId) {
        notifications.delete(notificationId);
    }

    public void readNotification(long notificationId) {
        Notification n = notifications.findById(notificationId);
        n.setReadStatus(true);
        notifications.save(n);
    }
} public class PayPalWrapper {

    public static String PAYPAL_COMPLETED_PAYMENT = "Completed";

    @Autowired
    private Gson GSON;

    // Generate access token in this construct
    public PayPalWrapper() {
    }

    public String generatePayPalAccessToken() throws PayPalRESTException {
        JsonObject response = new JsonObject();

        // Load configurations
        String[] tmp = this.getClass().getResource("/paypal.properties").toString().split(":");
	LoggingManager.info(this.getClass(), (String)tmp[1]);
        OAuthTokenCredential tokenCredential = Payment.initConfig(new File(tmp[1]));

        // Create access token
        String accessToken = tokenCredential.getAccessToken();

        return accessToken;
    }

    /**
     * Pay to a printshop its share of a print request.
     *
     * @param prequest the print request.
     * @param manager  the manager of the printshop.
     * @param pshop    the prinshop where the prequest was sent.
     * @return true/false pending on paypal operation success/insuccess.
     * @throws PayPalRESTException
     */
    public boolean payShareToPrintShop(PrintRequest prequest, Manager manager, PrintShop pshop) throws PayPalRESTException {
        if (prequest != null && manager != null && pshop != null) {
            Payout payout = new Payout();
            PayoutBatch batch = null;

            // Get an access token
            String accessToken = generatePayPalAccessToken();
            PayoutSenderBatchHeader senderBatchHeader = new PayoutSenderBatchHeader();

            // Batch
            Random random = new Random();
            String batchId = prequest.getArrivalTimestamp().toString();
            String emailSubject = "ProxyPrint - Pagamento relativo ao pedido ";
            // UNMARK getFinishedTimestamp() on full integration
            emailSubject += prequest.getArrivalTimestamp().toString() + "::" /*+ prequest.getFinishedTimestamp()*/ + " .";
            senderBatchHeader.setSenderBatchId(batchId).setEmailSubject(emailSubject);

            // Currency (90% of the print request value)
            double amountValue = prequest.getCost() * PrintShop.PRINTSHOPS_PERCENTILS_REVENUE;
            Currency amount = new Currency();
            amount.setValue(String.format("%.2f", amountValue)).setCurrency("EUR");

            // Sender Item
            String senderItemID = prequest.getFinishedTimestamp().toString();
            PayoutItem senderItem = new PayoutItem();
            senderItem.setRecipientType("Email")
                    .setNote("Obrigado por usar o ProxyPrint!")
                    .setReceiver(manager.getEmail())
                    .setSenderItemId(senderItemID).setAmount(amount);

            List<PayoutItem> items = new ArrayList<PayoutItem>();
            items.add(senderItem);

            payout.setSenderBatchHeader(senderBatchHeader).setItems(items);

            APIContext apiContext = new APIContext(accessToken);
            batch = payout.createSynchronous(apiContext);

            LoggingManager.info(this.getClass(), "Payout Batch Processed with ID: " + batch.getBatchHeader().getPayoutBatchId());

            if (batch != null) {
                return true;
            }
        }
        return false;
    }

    /**
     * Not working yet.
     * @param c the consumer which cancelled a print request.
     * @param pr the printrequest that the above consumer cancelled.
     * @return true/false uppon success or failure
     * @throws PayPalRESTException
     */
    public boolean refundConsumerCancelledPrintRequest(Consumer c, PrintRequest pr) throws PayPalRESTException {
        if (c != null & pr != null) {
            String accessToken = generatePayPalAccessToken();
            Sale sale = Sale.get(accessToken, /*pr.getPayPalSaleID()*/"1KR725617U497772K");

            Amount amount = new Amount();
            amount.setTotal("2.21");
            amount.setCurrency("EUR");

            Refund refund = new Refund();
            refund.setAmount(amount);

            Refund newRefund = sale.refund(accessToken, refund);

            if(newRefund!=null) {
                LoggingManager.info(this.getClass(), "Refund successfull "+c.getEmail()+"::"+newRefund.toString());
                return true;
            }
        }

        return false;
    }
}
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import com.paypal.base.rest.PayPalRESTException;
import io.github.proxyprint.kitchen.controllers.printshops.pricetable.CoversTable;
import io.github.proxyprint.kitchen.controllers.printshops.pricetable.PapersTable;
import io.github.proxyprint.kitchen.controllers.printshops.pricetable.RingsTable;
import io.github.proxyprint.kitchen.models.Admin;
import io.github.proxyprint.kitchen.models.consumer.Consumer;
import io.github.proxyprint.kitchen.models.consumer.printrequest.Document;
import io.github.proxyprint.kitchen.models.consumer.printrequest.DocumentSpec;
import io.github.proxyprint.kitchen.models.consumer.printrequest.PrintRequest;
import io.github.proxyprint.kitchen.models.consumer.printrequest.PrintRequest.Status;
import io.github.proxyprint.kitchen.models.notifications.Notification;
import io.github.proxyprint.kitchen.models.printshops.Employee;
import io.github.proxyprint.kitchen.models.printshops.Manager;
import io.github.proxyprint.kitchen.models.printshops.PrintShop;
import io.github.proxyprint.kitchen.models.printshops.pricetable.BindingItem;
import io.github.proxyprint.kitchen.models.printshops.pricetable.CoverItem;
import io.github.proxyprint.kitchen.models.printshops.pricetable.Item;
import io.github.proxyprint.kitchen.models.printshops.pricetable.RangePaperItem;
import io.github.proxyprint.kitchen.models.repositories.*;
import io.github.proxyprint.kitchen.utils.DistanceCalculator;
import io.github.proxyprint.kitchen.utils.NotificationManager;
import io.github.proxyprint.kitchen.utils.PayPalWrapper;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.WebRequest;
import java.io.IOException;
import java.lang.reflect.Type;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TreeMap;
import org.springframework.transaction.annotation.Transactional;

/*
 * Copyright 2016 Pivotal Software, Inc..
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.github.proxyprint.kitchen.controllers.printshops;



/**
 * @author josesousa
 */
@RestController 
@Transactional
public class PrintShopController {

    @Autowired
    private PrintShopDAO printshops;
    @Autowired
    private PrintRequestDAO printrequests;
    @Autowired
    private EmployeeDAO employees;
    @Autowired
    private ConsumerDAO consumers;
    @Autowired
    private ManagerDAO managers;
    @Autowired
    private AdminDAO admin;
    @Autowired
    private NotificationManager notificationManager;


    @Autowired
    private UserDAO users;
    @Autowired
    private Gson GSON;

    @ApiOperation(value = "Returns a list of printshops.", notes = "This method returns a list of the nearest printshops.")
    @RequestMapping(value = "/printshops/nearest", method = RequestMethod.GET)
    public String getNearestPrintShops(WebRequest request) {
        String lat = request.getParameter("latitude");
        String lon = request.getParameter("longitude");

        JsonObject response = new JsonObject();

        if (lat != null && lon != null) {
            Double latitude = Double.parseDouble(lat);
            Double longitude = Double.parseDouble(lon);
            System.out.format("Latitude: %s Longitude: %s\n", latitude, longitude);

            TreeMap<Double, PrintShop> pshops = new TreeMap<>();

            for (PrintShop p : printshops.findAll()) {
                double distance = DistanceCalculator.distance(latitude, longitude, p.getLatitude(), p.getLongitude());
                pshops.put(distance, p);
            }
            response.addProperty("success", true);
            response.add("printshops", GSON.toJsonTree((pshops)));
        } else {
            response.addProperty("success", false);
        }
        return GSON.toJson(response);
    }

    @ApiOperation(value = "Returns a pricetable", notes = "This method returns a pricetable of a specific printshop.")
    @Secured({"ROLE_MANAGER", "ROLE_USER"})
    @RequestMapping(value = "/printshops/{id}/pricetable", method = RequestMethod.GET)
    public String getPrintShopPriceTable(@PathVariable(value = "id") long id) {
        PrintShop pshop = printshops.findOne(id);
        JsonObject response = new JsonObject();

        PapersTable papersTable = new PapersTable();
        RingsTable ringsTable = new RingsTable();
        CoversTable coversTable = new CoversTable();

        if (pshop == null) {
            response.addProperty("success", false);
        } else {
            for (String key : pshop.getPriceTable().keySet()) {
                String type = Item.checkItemType(key);
                if (type.equals(Item.PAPER)) {
                    RangePaperItem rpi = (RangePaperItem) pshop.loadPriceItem(key);
                    papersTable.addRangePaperItem(rpi, pshop);
                } else if (type.equals(Item.RingType.BINDING.toString())) {

                    BindingItem bi = (BindingItem) pshop.loadPriceItem(key);
