package meerkat.voting.gui.configuration;

import com.google.protobuf.ByteString;
import meerkat.protobuf.BallotQuestionUiElement;
import org.factcenter.qilin.util.Pair;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;



/**
 * Created by Vladimir Eliezer Tokarev on 10/4/2016.
 * This class creates QuestionBallotUIElement from given configuration file
 */
public class VotingBoothConfigurationCreator {

    private final Logger logger = LoggerFactory.getLogger(VotingBoothConfigurationCreator.class);

    private int CURRENT_NUMBER_OF_QUESTIONS_REPRESENTED_TO_VOTER = 2 ;
    public static String WRONG_QUESTIONS_AMOUNT_ERROR_MESSAGE = "The configuration file had too many ballot question objects.";
    public static String UNSUPPORTED_VALUE_TYPE_ERROR_MESSAGE = "The given question type doesnt supported in current version";
    private String IMAGES_TYPE = "png";

    /**
     * Creates VotingBoothConfiguarion object form file that given as paramter
     * @param configPath the path to the json configuration
     * @return configuraiton object
     */
    public VotingBoothConfiguration CreateConfiguration(String configPath) throws IOException, JSONException {
        VotingBoothConfiguration config = new VotingBoothConfiguration();
        JSONArray array = this.getJsonFromPath(configPath);

        if (array.length() != CURRENT_NUMBER_OF_QUESTIONS_REPRESENTED_TO_VOTER) {
            throw new IOException(WRONG_QUESTIONS_AMOUNT_ERROR_MESSAGE);
        } else {
            try {
                BallotQuestionUiElement.BallotQuestionUIElement ballotQuestionUIElement = this.convertJSONObjToBallotQuestion(array.getJSONObject(0));
                config.NameSelectionQuestion = ballotQuestionUIElement;

                ballotQuestionUIElement = this.convertJSONObjToBallotQuestion(array.getJSONObject(1));
                config.NameSelectionByPictureQuestion = ballotQuestionUIElement;
            }
            catch (JSONException e) {
                e.printStackTrace();
            }
        }

        this.logger.debug("Created VotingBoothConfiguration object from given path: " + configPath);
        return config;
    }

    /**
     * Gets array of object that contains the data of the BallotAQuestions
     * @param path the path to the configuration file
     * @return JSONArray which represnets array of ballot Questions
     * @throws FileNotFoundException will be thrown in the config file not exists
     * @throws JSONException will be thrown if the parsing will fail
     */
    private JSONArray getJsonFromPath(String path) throws FileNotFoundException, JSONException {
        String content = new Scanner(new File(path)).useDelimiter("\\Z").next();
        JSONObject jsonObject = new JSONObject(content);
        this.logger.debug("Created json object from from configuration path.");
        return (JSONArray)jsonObject.get("questions");
    }

    /**
     * converts the given JSONObject into BallotQuestionUIElement
     * @param object the object to convert
     * @return BallotQuestion parsed fron JSONObject
     */
    private BallotQuestionUiElement.BallotQuestionUIElement convertJSONObjToBallotQuestion(JSONObject object) throws JSONException, IOException {
        boolean randomize = object.getBoolean("randomize");

        // Question part
        JSONObject question = object.getJSONObject("question");
        int questionType = question.getInt("type");
        ByteString questionValue = ByteString.copyFromUtf8(question.getString("questionValue"));

        // Convert and add json objects which have field answer and description into list of UIAnswers
        JSONObject answers = object.getJSONObject("answers");
        int answersType = answers.getInt("type");
        JSONArray answersValues = answers.getJSONArray("answersValue");
        List<Pair<ByteString, String>> answersPairs= new ArrayList<>();
        for (int i  = 0 ; i < answersValues.length(); i ++){
            ByteString answer = this.convertToByteStringBasedOnType(answersType, answersValues.getJSONObject(i));
            String description = answersValues.getJSONObject(i).getString("description");
            answersPairs.add(new Pair<>(answer, description));
        }

        // Create the UIQuestion and set the values parsed from jason
        BallotQuestionUiElement.UIQuestion uiQuestion = BallotQuestionUiElement.UIQuestion.newBuilder()
                .setQuestionType(questionType)
                .setQuestion(BallotQuestionUiElement.Question.newBuilder()
                .setQuestion(questionValue))
                .build();

        // Create UIAnswers and set the values parsed from jason
        BallotQuestionUiElement.UIAnswers.Builder uiAnswersBuilder = BallotQuestionUiElement.UIAnswers.newBuilder()
                .setAnswersType(answersType);
        BallotQuestionUiElement.ListOfAnswers.Builder listOfAnswers = BallotQuestionUiElement.ListOfAnswers.newBuilder();
        for(Pair<ByteString, String> pair: answersPairs){
            listOfAnswers.addAnswers(ConvertPairIntoUIAnswer(pair));
        }
        uiAnswersBuilder.setAnswers(listOfAnswers);

        this.logger.debug("Converted the json object into ballot question object.\n" + object);
        return BallotQuestionUiElement.BallotQuestionUIElement.newBuilder()
                .setQuestion(uiQuestion)
                .setAnswers(uiAnswersBuilder.build())
                .setRandomizeListOrder(randomize)
                .build();
    }

    /**
     * Converts given pair<ByteString, String> into UIAnswer
     * @param pair A pair of byteString which is the answer and string which is the description of the answer
     * @return UIAnswer
     */
    private BallotQuestionUiElement.UIAnswer ConvertPairIntoUIAnswer(Pair<ByteString, String> pair) {
        return BallotQuestionUiElement.UIAnswer.newBuilder()
                .setAnswer(pair.a)
                .setDescription(pair.b)
                .build();
    }

    /**
     * Tries to get convert the given question to ByteString else throws excetion
     * @param questionType the type of the question (can be 0 - text, 1 - audio, 2 - image)
     * @param value jsonObject which contains the value and its description
     * @return ByteString which is or the value or the file in given path
     * @throws IOException will be thrown if the given type is unsupported
     */
    private ByteString convertToByteStringBasedOnType(int questionType, JSONObject value) throws IOException, JSONException {
        if (questionType == BallotQuestionUiElement.ValueType.TEXT_TYPE.getNumber()) {
            return ByteString.copyFromUtf8(value.getString("answer"));
        }
        if (questionType == BallotQuestionUiElement.ValueType.IMAGE_TYPE.getNumber()) {
            return ByteString.copyFrom(getImageBytes(value.getString("answer")));
        }
        else {
            throw new IOException(UNSUPPORTED_VALUE_TYPE_ERROR_MESSAGE);
        }
    }

    /**
     * Converts the image  to bytes array
     * @param fileOrResourcePath the path to the wanted picture. Tries to read as a file path, and if not successful as a resource path.
     * @return byte array of the image
     * @throws IOException will be thrown if the given file doesn't exist and there is no resource of that path either.
     */
    private byte[] getImageBytes(String fileOrResourcePath) throws IOException {
        byte[] imageInByte;
        File imageFile = new File(fileOrResourcePath);
        BufferedImage originalImage;
        if (imageFile.exists()) {
            originalImage = ImageIO.read(new File(fileOrResourcePath));
        } else {
            originalImage = ImageIO.read(getClass().getResourceAsStream(fileOrResourcePath));
        }

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(originalImage, IMAGES_TYPE, baos);
        baos.flush();
        imageInByte = baos.toByteArray();
        baos.close();
        return imageInByte;
    }
}


