package it.enea.cruise.controller;

import java.awt.Color;
import java.awt.Font;
import java.awt.Paint;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.imageio.ImageIO;
import javax.ws.rs.POST;

import org.apache.commons.io.IOUtils;
import org.apache.hc.core5.annotation.Obsolete;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.chart.renderer.category.CategoryItemRenderer;
import org.jfree.data.category.DefaultCategoryDataset;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Transactional;

import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.databind.ObjectMapper;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiParam;
import it.enea.cruise.data.ElectricMeterReading;
import it.enea.cruise.data.ElectricMeterReading.Line;
import it.enea.cruise.data.ElectricMeterReading.Period;
import it.enea.cruise.data.ElectricMeterReading.Property;
import it.enea.cruise.data.ElectricMeterReading.UrbanDataset;
import it.enea.cruise.data.ElectricMeterReading.Values;
import it.enea.cruise.data.InputParameters;
import it.enea.cruise.model.data.DataConsumption;
import it.enea.cruise.model.parametricQuery.LocalQueryParameter;
import it.enea.cruise.model.parametricQuery.ParametricQuery;
import it.enea.cruise.model.user.User;
import it.enea.cruise.service.data.DataInterface;
import it.enea.cruise.service.data.IDataService;
import it.enea.cruise.service.idp.IdpProvider;
import it.enea.cruise.service.parametricQuery.LocalQueryParameterInterface;
import it.enea.cruise.service.parametricQuery.ParametricQueryInterface;
import it.enea.cruise.service.parametricQuery.ParametricQueryManager;
import it.enea.cruise.service.parametricQuery.ParametricQueryManager.ResultWithMetadata;
import it.enea.cruise.service.structure.TableMetadataInterface;
import it.enea.cruise.repositories.DTOManager;
import it.enea.cruise.repositories.UserRepository;
import lombok.extern.log4j.Log4j2;

@RestController
@Api(value = "DataController")
@EnableTransactionManagement
@Transactional("dataTransactionManagerIDP")
@Log4j2
public class DataController {

    @Autowired
    UserRepository userInterface;

    @Autowired
    TableMetadataInterface tMetadataInterface;

    @Autowired
    LocalQueryParameterInterface lqpInterface;

    @Autowired
    DataInterface dataInterface;

    @Autowired
    ParametricQueryInterface pqueryInterface;

    @Autowired
    TableMetadataInterface tmInterface;

    @Autowired
    ParametricQueryManager pqm;

    @Autowired
    IdpProvider idpClientManager;

    @Autowired
    CacheManager cacheManager;


    //@Autowired
    //IEncryptionManager encryptionManager;

    @Autowired
    DTOManager dtoManager;

    @Autowired
    IDataService dataService;

    @Value("${spring.jackson.time-zone}")
    private String timeZone;

    private static final String COMMA_DELIMITER = ";";
    private ObjectMapper om = new ObjectMapper();

    private ElectricMeterReading emr; // = new ElectricMeterReading(timeZone);
    private UrbanDataset uds; // = emr.generateEmptyUDS();
    private Values vals;// = emr.new Values();
    private Line line;// = emr.new Line();
    private List<Property> props;// = new ArrayList<Property>();
    private List<Line> lines;// = new ArrayList<Line>();
    private Property prop;// = emr.new Property();
    private Period period;// = emr.new Period();


    @POST
    @PostMapping("/data/clearListOfFunctions")
    @CacheEvict(value = "ListofFunctions", allEntries = true)
    public ResponseEntity<Boolean> clearListOfFunction() {
        cacheManager.getCache("ListofFunctions").clear();
        return new ResponseEntity<Boolean>(true, HttpStatus.OK);
    }


    /**
     * Returns a list of Functions, formatted, used to create the widget that shows data extracted
     * from the local database.
     *
     * @param Authorization the token of the user allowed to extract the list
     * @param language      the language used by the interface
     * @return A list of @ParametricQuery used to extract data
     * @see ParametricQuery
     */
    @POST
    @PostMapping("/data/getListOfFunctions")
    @Cacheable("ListofFunctions")
    public ResponseEntity<List<ParametricQuery>> listOfFuncionts(
            @RequestHeader(name = "Authorization", required = false) String token,
            @RequestHeader(name = "Content-Language", required = false, defaultValue = "it_IT") String language) {

        List<ParametricQuery> pqList = pqueryInterface.findAll();
        List<LocalQueryParameter> tmdata;
        List<HashMap<String, String>> parameterMetadata = new ArrayList<HashMap<String, String>>();
        List<String> list = null;


        for (ParametricQuery pq : pqList) {
            parameterMetadata = new ArrayList<HashMap<String, String>>();
            Map<String, List<String>> resultList = pqm.getResultList(pq);

            for (String key : resultList.keySet()) {
                list = resultList.get(key);
                for (String columnName : list) {
                    HashMap<String, String> metadata = new HashMap<String, String>();
                    tmdata = lqpInterface.findAllByName(columnName);
                    for (LocalQueryParameter lqpTemp : tmdata) {
                        if (lqpTemp.getName() == null || "".equals(lqpTemp.getName().trim())) continue;
                        metadata.put("name", lqpTemp.getName());
                        metadata.put("description", lqpTemp.getDescription(language));
                        metadata.put("type", lqpTemp.getType());
                        metadata.put("example", lqpTemp.getExample());
                        metadata.put("unitOfMeasure", lqpTemp.getUnitofmeasure());
                    }

                    parameterMetadata.add(metadata);
                }

            }
            pq.setParameterMetadata(parameterMetadata);

        }
        ResponseEntity<List<ParametricQuery>> rs = new ResponseEntity<List<ParametricQuery>>(pqList, HttpStatus.OK);
        return rs; //new ResponseEntity<List<ParametricQuery>>(data, HttpStatus.OK);
    }

    @POST
    @PostMapping(value = "/data/getData", consumes = {"application/x-www-form-urlencoded", "application/json"})
    public ResponseEntity<?> getData(
            @RequestBody Map<String, String> inputData,
            @RequestHeader(name = "Authorization", required = true) String token,
            @RequestHeader(name = "Content-Language", required = false, defaultValue = "it_IT") String language) {
        log.info("Inputdata:" + inputData);
        ResultWithMetadata data = dataService.getData(inputData, token, language);
        return new ResponseEntity<>(data, HttpStatus.OK);
    }

    @POST
    @PostMapping("/data/getData_old")
    public ResponseEntity<?> getDataold(
            @RequestBody InputParameters inputData,
            @RequestHeader(name = "Authorization", required = true) String token,
            @RequestHeader(name = "Content-Language", required = false, defaultValue = "it_IT") String language) {
        log.info(inputData.toString());
        ResultWithMetadata data = null; //dataService.getData(inputData, token, language);

        return new ResponseEntity<>(data, HttpStatus.OK);
    }

    @POST
    @PostMapping("/data/getInstantConsumption")
    @Obsolete
    public ResponseEntity<ElectricMeterReading> getInstantConsumptionPower(String token) {
        User user = new User();
        log.info("Token: " + token);
        if (idpClientManager.isValid(token)) {
            User userdto = idpClientManager.getUserByToken(token);
            String subject = userdto.getUuid();
            user = userInterface.findBySubject(subject);

        }
        emr = new ElectricMeterReading(timeZone);
        uds = emr.generateEmptyUDS();
        vals = emr.new Values();
        line = emr.new Line();

        InputParameters inputData = new InputParameters();

        inputData.setCommunityid(2);
        inputData.setFlowid(18);
        inputData.setUserid(user.getUserid().intValue());
        ParametricQuery pq = pqueryInterface.findByQueryid(new Long(6));
        ResultWithMetadata data = pqm.executeQuery(pq, inputData.getParameters(), "it_IT");


        props = new ArrayList<Property>();
        lines = new ArrayList<Line>();
        prop = emr.new Property();
        prop.name = "ActivePower";
        String value = data.getResult() != null ? data.getResult().get(0).get("powactive").toString() : "0";
        prop.val = value.contains(".") ? value.substring(0, value.length() - 2) : value;
        props.add(prop);

        //Questo valore dovrebbe arrivare dalle proprieta' dell'utente o del suo device attuale.
        prop = emr.new Property();
        prop.name = "MaxActivePower";
        prop.val = "3300";
        props.add(prop);

        line.property = props;
        lines.add(line);
        vals.line = lines;
        uds.values = vals;
        emr.urbanDataset = uds;
        Period period = emr.new Period();
        period.start_ts = Calendar.getInstance().getTime();
        period.end_ts = Calendar.getInstance().getTime();
        line.period = period;

        log.info(data);

        return new ResponseEntity<ElectricMeterReading>(emr, HttpStatus.OK);

    }

    @POST
    @PostMapping("/data/todayConsumption")
    public ResponseEntity<ElectricMeterReading> getTodayConsumptionData(String token) {

        User user = new User();
        DateFormat formatter = new SimpleDateFormat("HH:mm:ss");
        Date date = new Date();
        log.info("Token: " + token);
        if (idpClientManager.isValid(token)) {
            User userdto = idpClientManager.getUserByToken(token);
            String subject = userdto.getUuid();
            user = userInterface.findBySubject(subject);

        }
        InputParameters inputData = new InputParameters();

        inputData.setCommunityid(2);
        inputData.setFlowid(18);
        inputData.setUserid(user.getUserid().intValue());
        ParametricQuery pq = pqueryInterface.findByQueryid(new Long(9));
        ResultWithMetadata data = pqm.executeQuery(pq, inputData.getParameters(), "it_IT");

        emr = new ElectricMeterReading(timeZone);
        uds = emr.generateEmptyUDS();
        vals = emr.new Values();
        line = emr.new Line();
        props = new ArrayList<Property>();
        lines = new ArrayList<Line>();
        prop = emr.new Property();
        int i = 0;

        for (Map<String, Object> res : data.getResult()) {
            props = new ArrayList<Property>();
            period = emr.new Period();

            try {
                date = formatter.parse(res.get("timestamp").toString());
            } catch (ParseException e) {
                // TODO Auto-generated catch block
                log.error(e.getLocalizedMessage());
            }
            period.start_ts = date;
            period.end_ts = date;

            props.add(emr.new Property("ActivePower",
                    res.get("powactive").toString().contains(".") ?
                            res.get("powactive").toString().substring(0, res.get("powactive").toString().length() - 2)
                            : res.get("powactive").toString()
            ));
            props.add(emr.new Property("ActiveEnergy",
                    res.get("energy").toString().contains(".") ?
                            res.get("energy").toString().substring(0, res.get("energy").toString().length() - 2)
                            : res.get("energy").toString()
            ));

            lines.add(emr.new Line(i, period, props));
        }


        vals.line = lines;
        uds.values = vals;
        emr.urbanDataset = uds;

        return new ResponseEntity<ElectricMeterReading>(emr, HttpStatus.OK);

    }

    @POST
    @PostMapping("/data/getConsumptionInAPeriod")
    public ResponseEntity<ElectricMeterReading> getConsumptionInAPeriod(
            @ApiParam(name = "token", type = "String", value = "The user's token", example = "", required = true) @RequestParam String token,
            @ApiParam(name = "startDate", type = "String", value = "Initial date of a period", example = "2021-03-01 00:00:00", required = true) @RequestParam String startDate,
            @ApiParam(name = "endDate", type = "String", value = "Final date of a period", example = "2021-03-06 23:59:59", required = true) @RequestParam String endDate)
            throws NumberFormatException, ParseException {
        /*
         * Questa funzione dovrebbe restituire i dati compresi tra un'ora di inizio e
         * una di fine. Se la data di fine (non e' determinata, prendo i dati entro una
         * settimana a partire da oggi).
         */
        Long userid = new Long(137); // to be modified when data from token will be available;
        Integer communityid = 4; // to be modified
        Date start = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(startDate);
        Date end = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(endDate);

        List<DataConsumption> dataConsumption = dataInterface.findAllByUserIdAndStartDateAndEndDate(userid, start, end,
                communityid);

        emr = new ElectricMeterReading(timeZone);
        uds = emr.generateEmptyUDS();
        vals = emr.new Values();
        line = emr.new Line();
        props = new ArrayList<Property>();
        lines = new ArrayList<Line>();
        prop = emr.new Property();
        int i = 0;

        for (DataConsumption dc : dataConsumption) {
            props = new ArrayList<Property>();
            period = emr.new Period();

            period.start_ts = dc.getTimestamp();
            period.end_ts = dc.getTimestamp();

            props.add(emr.new Property("ActivePower", dc.getPowactive().toString()));
            props.add(emr.new Property("ActiveEnergy", dc.getEnergy().toString()));

            lines.add(emr.new Line(i, period, props));
        }

        vals.line = lines;
        uds.values = vals;
        emr.urbanDataset = uds;

        return new ResponseEntity<ElectricMeterReading>(emr, HttpStatus.OK);

    }

    @POST
    @PostMapping("/data/todayConsumptionAsChart")
    public ResponseEntity<byte[]> getTodayConsumptionDataAsChart(String token) {
        emr = new ElectricMeterReading(timeZone);
        uds = emr.generateEmptyUDS();
        vals = emr.new Values();
        line = emr.new Line();
        props = new ArrayList<Property>();
        lines = new ArrayList<Line>();
        prop = emr.new Property();
        HashMap<Date, String[]> valuesMap = getValues();

        DefaultCategoryDataset dataset = new DefaultCategoryDataset();
        SortedSet<Date> keys = new TreeSet<>(valuesMap.keySet());
        Paint[] colors = new Paint[valuesMap.size()];

        int i = 0;

        for (Date key : keys) {

            String[] values = valuesMap.get(key);
            String localDate = Long.toString(key.getHours()) + ":" + Long.toString(key.getMinutes()) + ":"
                    + Long.toString(key.getSeconds());

            LocalTime lt = LocalTime.of(8, 0);
            LocalTime rt = LocalTime.of((key.getHours()), key.getMinutes());

            if (rt.isBefore(LocalTime.of(7, 0))) {
                dataset.addValue(new Long(values[8]), "low", localDate);
                colors[i] = Color.green;
            } else if (rt.isBefore(LocalTime.of(8, 0))) {
                dataset.addValue(new Long(values[8]), "middle", localDate);
                colors[i] = Color.yellow;
            } else if (rt.isBefore(LocalTime.of(19, 0))) {
                dataset.addValue(new Long(values[8]), "high", localDate);
                colors[i] = Color.red;
            } else if (rt.isBefore(LocalTime.of(23, 00))) {
                dataset.addValue(new Long(values[8]), "middle", localDate);
                colors[i] = Color.yellow;
            } else if (rt.isBefore(LocalTime.of(23, 59))) {
                dataset.addValue(new Long(values[8]), "low", localDate);
                colors[i] = Color.green;
            }
            i++;
        }

        JFreeChart barChart = ChartFactory.createBarChart("Energy Consumption", "Time", "Energy (W)", dataset,
                PlotOrientation.VERTICAL, false, true, false);
        CategoryPlot plot = (CategoryPlot) barChart.getPlot();
        plot.getDomainAxis().setCategoryLabelPositions(CategoryLabelPositions.UP_45);
        plot.getDomainAxis().setCategoryLabelPositionOffset(6);

        Font font = new Font("Arial", Font.PLAIN, 10);
        plot.getDomainAxis().setTickLabelFont(font);
        plot.getRangeAxis().setTickLabelFont(font);
        plot.setDomainGridlinesVisible(true);
        plot.setRangeGridlinesVisible(false);

        final CategoryItemRenderer renderer = new CustomRenderer(colors);
        plot.setRenderer(renderer);
        final BufferedImage bufferedImage;
        byte[] media = null;
        try {
            bufferedImage = barChart.createBufferedImage(1000, 600);
            // ChartUtils.saveChartAsPNG(new File("time_chart.png"), chart, 1000, 800);

            ByteArrayOutputStream os = new ByteArrayOutputStream();
            ImageIO.write(bufferedImage, "png", os); // Passing: ​(RenderedImage im, String formatName, OutputStream
            // output)
            InputStream is = new ByteArrayInputStream(os.toByteArray());
            media = IOUtils.toByteArray(is);

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        HttpHeaders headers = new HttpHeaders();
        headers.setCacheControl(CacheControl.noCache().getHeaderValue());
        headers.setContentType(MediaType.IMAGE_PNG);
        return new ResponseEntity<byte[]>(media, headers, HttpStatus.OK);

    }

    public void createLine(Date date, String[] data, Integer i) {

        props = new ArrayList<Property>();
        period = emr.new Period();

        period.start_ts = date;
        period.end_ts = date;

        props.add(emr.new Property("ActivePower", data[6]));
        props.add(emr.new Property("ActiveEnergy", data[8]));

        lines.add(emr.new Line(i, period, props));

    }

    private HashMap<Date, String[]> getValues() {
        File file;
        HashMap<Date, String[]> valuesMap = new HashMap<Date, String[]>();
        try {
            file = ResourceUtils.getFile("classpath:average_DU20080002_2021-06-07_1.csv");
            InputStream in = new FileInputStream(file);

            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            String recordLine;
            int i = 0;

            while ((recordLine = br.readLine()) != null) {
                String[] values = recordLine.split(COMMA_DELIMITER);
                if (i > 0) {

                    DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

                    Date date = formatter.parse(values[1] + " " + values[2]);

                    valuesMap.put(date, values);
                }
                i++;
            }
            br.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return valuesMap;

    }

    class CustomRenderer extends BarRenderer {

        /**
         *
         */
        private static final long serialVersionUID = 1L;
        /**
         * The colors.
         */
        private Paint[] colors;

        /**
         * Creates a new renderer.
         *
         * @param colors the colors.
         */
        public CustomRenderer(final Paint[] colors) {
            this.colors = colors;
        }

        /**
         * Returns the paint for an item. Overrides the default behaviour inherited from
         * AbstractSeriesRenderer.
         *
         * @param row    the series.
         * @param column the category.
         * @return The item color.
         */
        public Paint getItemPaint(final int row, final int column) {
            return this.colors[column % this.colors.length];
        }
    }
}
