/*
 * 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.sequence;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
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 gnu.trove.impl.Constants;
import gnu.trove.map.hash.TIntObjectHashMap;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * Registry of all alphabets.
 */
public final class Alphabets {
    private final static Map<String, Alphabet> alphabetsByName = new HashMap<>();
    private final static TIntObjectHashMap<Alphabet> alphabetsById = new TIntObjectHashMap<>(Constants.DEFAULT_CAPACITY,
            Constants.DEFAULT_LOAD_FACTOR, Byte.MIN_VALUE);

    private Alphabets() {
    }

    /**
     * Register new alphabet
     *
     * @param alphabet alphabet
     */
    public static void register(Alphabet alphabet) {
        if (alphabetsByName.put(alphabet.getAlphabetName(), alphabet) != null)
            throw new IllegalStateException("Alphabet with this name is already registered.");

        if (alphabetsById.put(alphabet.getId(), alphabet) != null)
            throw new IllegalStateException("Alphabet with this id is already registered.");
    }

    static {
        register(NucleotideAlphabet.INSTANCE);
        register(AminoAcidAlphabet.INSTANCE);
    }

    /**
     * Returns instance of {@code Alphabet} from its string name.
     *
     * @param name string name of alphabet
     * @return instance of {@code Alphabet} from its string name
     */
    public static Alphabet getByName(String name) {
        return alphabetsByName.get(name);
    }

    /**
     * Searches for instance of {@code Alphabet} using first letter of it's name
     *
     * @param letter first letter of alphabet's name
     * @return instance of {@code Alphabet}
     */
    public static Alphabet getByFirstLetterOfName(char letter) {
        letter = Character.toLowerCase(letter);
        for (Alphabet alphabet : alphabetsByName.values())
            if (alphabet.getAlphabetName().charAt(0) == letter)
                return alphabet;
        throw new IllegalArgumentException("No alphabet starting with letter: " + letter);
    }

    /**
     * Returns instance of {@code Alphabet} from its byte id.
     *
     * @param id byte id of alphabet
     * @return instance of {@code Alphabet} from its byte id
     */
    public static Alphabet getById(byte id) {
        return alphabetsById.get(id);
    }

    /**
     * Returns unmodifiable collection of all registered alphabets.
     *
     * @return unmodifiable collection of all registered alphabets
     */
    public static Collection<Alphabet> getAll() {
        return Collections.unmodifiableCollection(alphabetsByName.values());
    }

    /* JSON serializers / deserializers */

    public static final class Deserializer extends JsonDeserializer<Alphabet> {
        @Override
        public Alphabet deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
            return Alphabets.getByName(jp.readValueAs(String.class));
        }

        @Override
        public Alphabet getEmptyValue(DeserializationContext context) {
            return NucleotideAlphabet.INSTANCE;
        }

        @Override
        public Alphabet getNullValue(DeserializationContext context) {
            return NucleotideAlphabet.INSTANCE;
        }
    }

    public static final class Serializer extends JsonSerializer<Alphabet> {
        @Override
        public void serialize(Alphabet value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
            jgen.writeString(value.getAlphabetName());
        }
    }
}
