/*
 * Copyright (c) 2022 MiLaboratories 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 com.milaboratory.core.alignment;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.milaboratory.core.sequence.AminoAcidSequence;
import com.milaboratory.util.IntArrayList;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;

import static com.milaboratory.core.sequence.AminoAcidAlphabet.INCOMPLETE_CODON;
import static com.milaboratory.core.sequence.AminoAcidAlphabet.STOP;

final class ScoringMatrixIO {
    public static final class Deserializer extends JsonDeserializer<SubstitutionMatrix> {
        @Override
        public SubstitutionMatrix deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
            String strValue = jp.readValueAs(String.class);
            strValue = strValue.replaceAll("\\s", "").toLowerCase();

            if (strValue.startsWith("raw(")) {
                if (!strValue.endsWith(")"))
                    throw new IOException("Not balanced brackets in : " + strValue);

                strValue = strValue.substring(4, strValue.length() - 1);
                String split[] = strValue.split(",");

                int[] values = new int[split.length];
                for (int i = 0; i < split.length; ++i)
                    values[i] = Integer.parseInt(split[i], 10);

                return new SubstitutionMatrix(values);
            }

            if (strValue.startsWith("simple(")) {
                if (!strValue.endsWith(")"))
                    throw new IOException("Not balanced brackets in : " + strValue);

                strValue = strValue.substring(7, strValue.length() - 1);

                String split[] = strValue.split(",");

                int match = Integer.MIN_VALUE, mismatch = Integer.MIN_VALUE;

                for (int i = 0; i < split.length; ++i) {
                    if (split[i].startsWith("match="))
                        match = Integer.parseInt(split[i].substring(6), 10);
                    if (split[i].startsWith("mismatch="))
                        mismatch = Integer.parseInt(split[i].substring(9), 10);
                }

                if (match == Integer.MIN_VALUE)
                    throw new IOException("Match value not set in : " + strValue);

                if (mismatch == Integer.MIN_VALUE)
                    throw new IOException("Mismatch value not set in : " + strValue);

                return new SubstitutionMatrix(match, mismatch);
            }

            throw new IOException("Can't parse: " + strValue);
        }
    }

    public static final class Serializer extends JsonSerializer<SubstitutionMatrix> {
        @Override
        public void serialize(SubstitutionMatrix value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
            if (value.data.length == 2)
                jgen.writeString("simple(match = " + value.data[0] + ", mismatch = " + value.data[1] + ")");
            else {
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < value.data.length; ++i) {
                    sb.append(value.data[i]);
                    sb.append(", ");
                }
                sb.delete(sb.length() - 2, sb.length());
                jgen.writeString("raw(" + sb.toString() + ")");
            }
        }
    }

    //public static final class Deserializer extends JsonDeserializer<int[]> {
    //    @Override
    //    public int[] deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
    //        String strValue = jp.readValueAs(String.class);
    //        strValue = strValue.replaceAll("\\s", "").toLowerCase();
    //
    //        if (strValue.startsWith("raw(")) {
    //
    //            if (!strValue.endsWith(")"))
    //                throw new IOException("Not balanced brackets in : " + strValue);
    //
    //            strValue = strValue.substring(4, strValue.length() - 1);
    //            String split[] = strValue.split(",");
    //
    //            int[] values = new int[split.length];
    //            for (int i = 0; i < split.length; ++i)
    //                values[i] = Integer.parseInt(split[i], 10);
    //
    //            return values;
    //        }
    //
    //        if (strValue.startsWith("simple(")) {
    //            if (!strValue.endsWith(")"))
    //                throw new IOException("Not balanced brackets in : " + strValue);
    //
    //            strValue = strValue.substring(7, strValue.length() - 1);
    //
    //            String split[] = strValue.split(",");
    //
    //            int match = Integer.MIN_VALUE, mismatch = Integer.MIN_VALUE;
    //
    //            for (int i = 0; i < split.length; ++i) {
    //                if (split[i].startsWith("match="))
    //                    match = Integer.parseInt(split[i].substring(6), 10);
    //                if (split[i].startsWith("mismatch="))
    //                    mismatch = Integer.parseInt(split[i].substring(9), 10);
    //            }
    //
    //            if (match == Integer.MIN_VALUE)
    //                throw new IOException("Match value not set in : " + strValue);
    //
    //            if (mismatch == Integer.MIN_VALUE)
    //                throw new IOException("Mismatch value not set in : " + strValue);
    //
    //            return new int[]{match, mismatch};
    //        }
    //
    //        throw new IOException("Can't parse: " + strValue);
    //    }
    //}
    //
    //public static final class Serializer extends JsonSerializer<int[]> {
    //    @Override
    //    public void serialize(int[] value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
    //        int size = (int) (Math.sqrt(value.length));
    //
    //        if (value.length != size * size)
    //            throw new IOException("Wrong matrix size.");
    //
    //        int diagonalValue = value[0];
    //        int otherValue = value[1];
    //
    //        boolean isSymmetric = true;
    //
    //        for (int i = 0; i < size; ++i)
    //            for (int j = 0; j < size; ++j) {
    //
    //                if (i == j)
    //                    isSymmetric &= (value[size * i + j] == diagonalValue);
    //                else
    //                    isSymmetric &= (value[size * i + j] == otherValue);
    //
    //                if (!isSymmetric)
    //                    break;
    //            }
    //
    //        if (isSymmetric)
    //            jgen.writeString("simple(match = " + diagonalValue + ", mismatch = " + otherValue + ")");
    //        else {
    //            StringBuilder sb = new StringBuilder();
    //            for (int i = 0; i < value.length; ++i) {
    //                sb.append(value[i]);
    //                sb.append(", ");
    //            }
    //            sb.delete(sb.length() - 2, sb.length());
    //            jgen.writeString("raw(" + sb.toString() + ")");
    //        }
    //    }
    //}

    /**
     * Reads BLAST AminoAcid substitution matrix from InputStream
     *
     * @param stream InputStream
     * @return BLAST AminoAcid substitution matrix
     * @throws java.io.IOException
     */
    public static int[] readAABlastMatrix(InputStream stream) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(stream));
        String line;

        //Creating stopValues array
        int[] stopValues = new int[]{STOP, INCOMPLETE_CODON};

        //~AminoAcidAlphabet.
        IntArrayList mappings = new IntArrayList(30);

        int alSize = AminoAcidSequence.ALPHABET.size();
        int[] matrix = new int[alSize * alSize];
        Arrays.fill(matrix, Integer.MIN_VALUE);

        String[] cells;
        while ((line = br.readLine()) != null) {
            //Processing comment
            if (line.startsWith("#"))
                continue;

            //Processing header
            if (line.startsWith(" ")) {
                String[] letters = line.trim().split("\\s+");
                for (int i = 0; i < letters.length; ++i)
                    mappings.add(getAACode(letters[i]));
                continue;
            }

            //Processing line with values
            cells = line.trim().split("\\s+");

            //Parsing letter in the first column
            for (int from : getVals(getAACode(cells[0]), stopValues)) {
                for (int i = 1; i < cells.length; ++i)
                    for (int to : getVals(mappings.get(i - 1), stopValues))
                        matrix[from * alSize + to] = Integer.parseInt(cells[i]);
            }
        }

        // Filling gaps in matrix
        // ScoringUtils.fillWildcardScores(matrix, AminoAcidSequence.ALPHABET, X, INCOMPLETE_CODON);

        //Checking for matrix fullness
        for (int val : matrix)
            if (val == Integer.MIN_VALUE)
                throw new IllegalArgumentException("Some letters are missing in matrix.");

        return matrix;
    }

    private static int[] getVals(int ll, int[] stopValues) {
        if (ll == -1)
            return new int[0];
        if (ll == -2)
            return stopValues;
        return new int[]{ll};
    }

    /**
     * Returns AminoAcid code
     *
     * @param letter letter
     * @return code
     */
    private static byte getAACode(String letter) {
        if (letter.length() != 1)
            throw new IllegalArgumentException();
        char l = letter.charAt(0);
        if (l == '*' || l == '*')
            return -2;
        return AminoAcidSequence.ALPHABET.symbolToCode(l);
    }
}
