package info.bukova.isspst.services.signeddocs;

import info.bukova.isspst.DateTimeUtils;
import info.bukova.isspst.Module;
import info.bukova.isspst.ModuleUtils;
import info.bukova.isspst.data.DataModel;
import info.bukova.isspst.data.Member;
import info.bukova.isspst.data.SignedDocument;
import info.bukova.isspst.data.SignedDocumentItem;
import info.bukova.isspst.services.AbstractOwnedService;
import info.bukova.isspst.services.LazyLoader;
import info.bukova.isspst.storage.ReportFileStorage;

import java.util.*;

import javax.servlet.ServletContext;

import info.bukova.isspst.ui.signeddocs.SignedDocsTreeNode;
import org.hibernate.Hibernate;
import org.hibernate.Query;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.zkoss.zul.DefaultTreeModel;
import org.zkoss.zul.DefaultTreeNode;
import org.zkoss.zul.TreeModel;
import org.zkoss.zul.TreeNode;

public class SignedDocumentServiceImpl extends AbstractOwnedService<SignedDocument> implements SignedDocumentService {

    @Autowired
    private ServletContext servletContext;
    @Autowired
    private ReportFileStorage signedDocStorage;

    @LazyLoader("grid")
    @Transactional
    public void lazyLoadItems(SignedDocument signedDocument) {
        if (signedDocument == null) {
            return;
        }

        SignedDocument sd = dao.getById(signedDocument.getId());
        Hibernate.initialize(sd.getItems());
        signedDocument.setItems(sd.getItems());
    }

    @Override
    @Transactional
    public SignedDocument getForEntity(DataModel entity) {
        Module module = ModuleUtils.getModule(entity, servletContext);

        if (module == null) {
            return null;
        }

        Query q = dao.getQuery("from SignedDocument where moduleName = :module and recordId = :record");
        q.setParameter("module", module.getId());
        q.setParameter("record", entity.getId());

        return (SignedDocument) q.uniqueResult();
    }

    @Override
    @Transactional
    public SignedDocumentItem getItem(DataModel entity, long reportId) {
        SignedDocument doc = getForEntity(entity);

        if (doc == null) {
            return null;
        }

        for (SignedDocumentItem item : doc.getItems()) {
            if (item.getReportId() == reportId) {
                return item;
            }
        }

        return null;
    }

    @Override
    public void addFromApprove(SignedDocument document) {
        super.add(document);
    }

    @Override
    public void delFromApprove(SignedDocument document) {
        if (document == null) {
            return;
        }

        super.delete(document);

        for (SignedDocumentItem item : document.getItems()) {
            signedDocStorage.removeFile(item);
        }
    }

    @Override
    @Transactional
    public void deleteForEntity(DataModel entity) {
        SignedDocument doc = getForEntity(entity);
        if (doc != null) {
            delFromApprove(doc);
        }
    }

    private Date getTresholdDate() {
        Date date = DateTimeUtils.getDateDelta(DateTimeUtils.getCurrDate(), Calendar.MONTH, -6);
        return date;
    }

    @SuppressWarnings("unchecked")
    @Override
    @Transactional
    @PreAuthorize("hasPermission(this, 'PERM_READ')")
    public List<SignedDocument> getActualList() {
        Query q = dao.getQuery("from SignedDocument where signDate >= :refDate");
        q.setParameter("refDate", this.getTresholdDate());
        return q.list();
    }

    @SuppressWarnings("unchecked")
    @Override
    @Transactional
    @PreAuthorize("hasPermission(this, 'PERM_READ')")
    public List<SignedDocument> getArchiveList() {
        Query q = dao.getQuery("from SignedDocument where signDate < :refDate");
        q.setParameter("refDate", this.getTresholdDate());
        return q.list();
    }

    @SuppressWarnings("unchecked")
    @Override
    @Transactional
    @PreAuthorize("hasPermission(this, 'PERM_READ')")
    public TreeModel<TreeNode<SignedDocumentItem>> getTree() {
        Query q = queryDao.getQuery("select item "
                        + "from SignedDocumentItem as item left join item.signedDocument as rec "
                        + "order by rec.moduleName, rec.signDate desc, item.reportName, item.fileName "
        );
        List<SignedDocumentItem> list = q.list();

        SignedDocumentItem lastItem = null;
        SignedDocument lastRec = null;
        List<SignedDocsTreeNode> agendaNameList = new LinkedList<SignedDocsTreeNode>();
        List<SignedDocsTreeNode> signYearList = new LinkedList<SignedDocsTreeNode>();
        List<SignedDocsTreeNode> signDateList = new LinkedList<SignedDocsTreeNode>();
        List<SignedDocsTreeNode> filesList = null;

        int documentsCount = list.size() - 1;

        for (int i = 0; i <= documentsCount; i++) {
            SignedDocumentItem item = list.get(i);
            SignedDocument rec = item.getSignedDocument();
            SignedDocsTreeNode itemNode = new SignedDocsTreeNode(item);

            boolean isChangedAgendaName = ((i == 0)
                    || (i == documentsCount)
                    || !lastRec.getAgendaName().equals(rec.getAgendaName())
            );
            boolean isChangedSignYear = ((i == 0)
                    || (i == documentsCount)
                    || isChangedAgendaName
                    || !DateTimeUtils.isEqualByYear(lastRec.getSignDate(), rec.getSignDate())
            );
            boolean isChangedSignDate = ((i == 0)
                    || (i == documentsCount)
                    || isChangedSignYear
                    || !DateTimeUtils.isEqualByDate(lastRec.getSignDate(), rec.getSignDate())
            );

            if (isChangedAgendaName || isChangedSignYear || isChangedSignDate) {

                if (isChangedSignDate) {
                    if (i > 0) {
                        SignedDocsTreeNode signDateNode = new SignedDocsTreeNode((i == documentsCount ? item : lastItem), filesList);
                        signDateList.add(signDateNode);
                    }

                    filesList = new LinkedList<SignedDocsTreeNode>();
                    filesList.add(itemNode);
                }

                if (isChangedSignYear) {
                    if (i > 0) {
                        SignedDocsTreeNode signYearNode = new SignedDocsTreeNode((i == documentsCount ? item : lastItem), signDateList);
                        signYearList.add(signYearNode);
                    }

                    signDateList = new LinkedList<SignedDocsTreeNode>();
                }

                if (isChangedAgendaName) {
                    if (i > 0) {
                        SignedDocsTreeNode agendaNode = new SignedDocsTreeNode((i == documentsCount ? item : lastItem), signYearList);
                        agendaNameList.add(agendaNode);
                    }

                    signYearList = new LinkedList<SignedDocsTreeNode>();
                }
            } else {
                filesList.add(itemNode);
            }

            lastItem = item;
            lastRec = lastItem.getSignedDocument();
        }

        Collections.sort(agendaNameList, new Comparator<SignedDocsTreeNode>() {
                    @Override
                    public int compare(SignedDocsTreeNode nodeL, SignedDocsTreeNode nodeR) {
                        if ((nodeL != null) && (nodeR != null)) {
                            SignedDocumentItem itemL = nodeL.getData();
                            SignedDocumentItem itemR = nodeR.getData();

                            if ((itemL != null) && (itemR != null)) {
                                SignedDocument recL = itemL.getSignedDocument();
                                SignedDocument recR = itemR.getSignedDocument();

                                if ((recL != null) && (recR != null)) {
                                    String nameL = recL.getAgendaName();
                                    String nameR = recR.getAgendaName();

                                    if ((nameL != null) && (nameR != null)) {
                                        return nameL.compareTo(nameR);
                                    }
                                }
                            }
                        }

                        return 0;
                    }
                }
        );

        SignedDocsTreeNode root = new SignedDocsTreeNode(null, agendaNameList);
        TreeModel<TreeNode<SignedDocumentItem>> tree = new DefaultTreeModel<SignedDocumentItem>(root);
        return tree;
    }
}
