/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.csu.sdmx.web.controllers;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import cz.csu.sdmx.Utils;
import cz.csu.sdmx.constants.JSP;
import cz.csu.sdmx.constants.SESSION;
import cz.csu.sdmx.dao.LogicConditionsDao;
import cz.csu.sdmx.validation.model.dsds.Dsd;
import cz.csu.sdmx.validation.model.LogicCondition;
import cz.csu.sdmx.constants.URL;
import cz.csu.sdmx.web.forms.ImportConditionsForm;
import cz.csu.sdmx.web.model.ConditionImportResult;
import cz.csu.sdmx.web.security.User;
import cz.csu.sdmx.web.validators.LogicConditionValidator;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

/**
 *
 * @author themi
 */
@Controller
public class ConditionController extends BaseController{
   
    @Autowired
    private LogicConditionsDao logicConditionsDao;
    
    @RequestMapping(value = URL.CONDITIONS)
    public String showConditions(Model model) {
        List<String> dsds = logicConditionsDao.getDsds();
        
        if(dsds.size() > 0){
            String firstDsd = dsds.get(0);
            model.addAttribute("conditions", logicConditionsDao.getByDsdUrn(firstDsd));
            model.addAttribute("dsd", firstDsd);
        }
        model.addAttribute("dsds", dsds);

        return JSP.CONDITION_LIST;
    }
    
    @RequestMapping(value = URL.CONDITIONS_FILTER)
    public String showFilteredConditions(Model model, @RequestParam String dsd) {
        
        model.addAttribute("conditions", logicConditionsDao.getByDsdUrn(dsd));
        model.addAttribute("dsds", logicConditionsDao.getDsds());
        model.addAttribute("dsd", dsd);

        return JSP.CONDITION_LIST;
    }
    
    @RequestMapping(value = URL.CONDITION_ADD)
    public String addCondition(Model model) {
        if(session.getAttribute(SESSION.DSD) == null)
            return URL.redirectTo(URL.DSD_DETECT);
            
        model.addAttribute(new LogicCondition());
        return JSP.CONDITION_ADD;
    }
    
    @RequestMapping(value = URL.CONDITION_ADD_SUBMIT)
    public String addConditionSubmit(@ModelAttribute LogicCondition condition, BindingResult result, 
            RedirectAttributes redirectAttributes) throws Exception {
        
        LogicConditionValidator validator = new LogicConditionValidator((Dsd)session.getAttribute(SESSION.DSD));
        validator.validate(condition, result);
        if(result.hasErrors()){
            return JSP.CONDITION_ADD;
        }
        
        logicConditionsDao.insert(condition);
        
        redirectAttributes.addAttribute("dsd", condition.getDsdName());
        return URL.redirectTo(URL.CONDITIONS_FILTER);
    }
    
    @RequestMapping(value = URL.CONDITION_DELETE, method = RequestMethod.POST)
    public String deleteCondition(@RequestParam("id") int id, @RequestParam("dsdUri") String dsdUrn, Model model,
        RedirectAttributes redirectAttributes) {
        logicConditionsDao.delete(id);
        
        redirectAttributes.addAttribute("dsd",dsdUrn);
        return URL.redirectTo(URL.CONDITIONS_FILTER);
    }
    

    @RequestMapping(value = URL.CONDITIONS_EXPORT, method = RequestMethod.GET)
    public void exportConditions(HttpServletResponse response) throws IOException {
        List<LogicCondition> lcList = logicConditionsDao.list();
        sendFile(response, lcList, "AllConditions");
    }
    
    @RequestMapping(value = URL.CONDITIONS_EXPORT_ALLOWED, method = RequestMethod.GET)
    public void exportAllowedConditions(HttpServletResponse response) throws IOException {
        List<LogicCondition> lcList = logicConditionsDao.list();
        
        User currentUser = getCurrentUser();
        Iterator<LogicCondition> lcIterator = lcList.iterator();
        //remove conditions for which current user has no permission
        while(lcIterator.hasNext()){
            if(!currentUser.hasUrn(lcIterator.next().getDsdName()))
                lcIterator.remove();
        }
        
        sendFile(response, lcList, "AllowedConditions");
    }
       
    @RequestMapping(value = URL.CONDITIONS_EXPORT+"/{DsdUrn:.+}", method = RequestMethod.GET)
    public void exportDsdConditions(HttpServletResponse response, @PathVariable(value = "DsdUrn") String DsdUrn) throws IOException {
        List<LogicCondition> lcList = logicConditionsDao.getByDsdUrn(DsdUrn);
        sendFile(response, lcList, DsdUrn);
    }
    
    @RequestMapping(value = URL.CONDITION_EXPORT+"/{conditionId}", method = RequestMethod.GET)
    public void exportSingleCondition(HttpServletResponse response, @PathVariable(value = "conditionId") Integer conditionId) throws IOException {
        LogicCondition lc = logicConditionsDao.find(conditionId);
        sendFile(response, Arrays.asList(lc), String.valueOf(lc.getId()));
    }
    
    //sends LogicConditions converted to JSON to user
    private void sendFile(HttpServletResponse response, List<LogicCondition> lcList, String filename) throws IOException{
        Gson gson = new GsonBuilder()
                .excludeFieldsWithoutExposeAnnotation()
                .disableHtmlEscaping()
                .setPrettyPrinting()
                .create();
        
        InputStream myStream = Utils.toInputStream(gson.toJson(lcList));

	// Set the content type and attachment header.
	response.addHeader("Content-disposition", "attachment;filename="+filename+".json");
	response.setContentType("txt/plain");
        
        Utils.copyInputStreamToOutputStream(myStream, response.getOutputStream());
	response.flushBuffer();
    }
    
    @RequestMapping(value = URL.CONDITIONS_IMPORT)
    public String importConditions(Model model) {
        model.addAttribute("importConditionsForm", new ImportConditionsForm());
        
        return JSP.CONDITIONS_IMPORT;
    }
    
    @RequestMapping(value = URL.CONDITIONS_IMPORT_SUBMIT, method = RequestMethod.POST)
    public String importConditionsSubmit(@ModelAttribute("importConditionsForm") ImportConditionsForm conditionsForm, BindingResult result, Model model) throws IOException {
        MultipartFile conFile = conditionsForm.getConditionsFile();
        if(conFile == null || conFile.isEmpty()){
            result.addError(new ObjectError("ConditionsFile", "File required.. "));
            return JSP.CONDITIONS_IMPORT;
        }
        
        Gson gson = new GsonBuilder()
                .disableHtmlEscaping()
                .setPrettyPrinting()
                .excludeFieldsWithoutExposeAnnotation()
                .create();
        List<LogicCondition> lcList;
        //try to convert supplied file to list of LogicCondition
        try{
            String conditionsString = Utils.readLinesString(conFile);
            Type listType = new TypeToken<ArrayList<LogicCondition>>(){}.getType();
            lcList = gson.fromJson(conditionsString, listType);
        }catch(JsonSyntaxException ex){
            //if convertion not successfull, return error
            result.addError(new ObjectError("ConditionsFile", ex.getLocalizedMessage()));
            return JSP.CONDITIONS_IMPORT;
        }
        
        List<ConditionImportResult> importResults = new ArrayList<>();
        LogicConditionValidator validator = new LogicConditionValidator();
        
        User user = getCurrentUser();
        Iterator<LogicCondition> lcIterator = lcList.iterator();
        
        while(lcIterator.hasNext()){
            LogicCondition lc = lcIterator.next();
            //check if user has permission to add condition to such DSD
            ConditionImportResult importResult = new ConditionImportResult();
            importResult.setExpression(lc.getExpression());
            if(!user.hasUrn(lc.getDsdName())){
                importResult.addError("You have no right to add conditions to dsd - " + lc.getDsdName());
            }
            
            //check if condition is valid
            List<String> errors = validator.validateCondition(lc.getExpression());
            if(!errors.isEmpty()){
                importResult.setErrors(errors);
            }
            
            //if errors present 
            if(importResult.hasErrors()){
                //remove condition from list
                lcIterator.remove();
                //add JSON structure of LogicCondition to importResult    
                importResult.setJson(gson.toJson(lc));
                //add importResult to list of results
                importResults.add(importResult);
            }
        }
        
        //add all valid conditions to DB and create import results
        for(LogicCondition lc : lcList){
            boolean added = logicConditionsDao.insert(lc);
            ConditionImportResult importResult = new ConditionImportResult();
            importResult.setExpression(lc.getExpression());
            importResult.setJson(gson.toJson(lc));
            if(!added)
                importResult.addError("Database error");
            
            importResults.add(importResult);
        }
        
        model.addAttribute("importResults", importResults);
        return JSP.CONDITIONS_IMPORT;
    }
 
}
