public class AddOperation extends ConfigurationOperation {
    private static AddOperation instance = null;

    public static AddOperation getInstance(){
        if(instance == null) {
            instance = new AddOperation();
        }

        return instance;
    }

    public double applyOperation(double accumulator, double value) {
        return accumulator + value;
    }
} public class LogOperation extends ConfigurationOperation {
    private static LogOperation instance = null;

    public static LogOperation getInstance(){
        if(instance == null) {
            instance = new LogOperation();
        }

        return instance;
    }

    public double applyOperation(double accumulator, double value) {
        return accumulator + Math.log(2 + value);
    }
} public class SumSquaredOperation extends ConfigurationOperation {
    private static SumSquaredOperation instance = null;

    public static SumSquaredOperation getInstance(){
        if(instance == null) {
            instance = new SumSquaredOperation();
        }

        return instance;
    }

    public double applyOperation(double accumulator, double value) {
        return accumulator + value * value;
    }
} public abstract class SynsetRelatedness {
    // TODO read stopwords from file
    public static final String[] stopWords = { "a", "about", "above", "across", "after", "afterwards",
            "again", "against", "all", "almost", "alone", "along", "already", "also", "although", "always",
            "am", "among", "amongst",  "an", "and", "another", "any", "anyhow", "anyone", "anything",
            "anyway", "anywhere", "are", "aren't", "around", "as", "at", "be", "because", "been", "before",
            "behind", "below", "beside", "besides", "between", "beyond", "both", "but", "by", "can't",
            "cannot", "could" ,"couldn't", "did", "didn't", "do", "does", "doesn't",  "don't", "during",
            "each", "either", "else", "elsewhere", "enough", "etc", "ever", "every", "everyone",
            "everything", "everywhere", "few", "for", "from", "further", "had", "hadn't", "has", "hasn't",
            "have", "haven't", "having", "he", "hence", "her", "here", "hereafter", "hereby", "hers",
            "herself", "him", "himself", "his", "how", "i", "if", "in", "into", "is", "isn't", "it",
            "it's", "its", "itself", "let's", "like", "many", "me", "meanwhile", "more", "most", "mostly",
            "much", "mustn't", "my", "myself", "near", "neither", "never", "no", "none", "nor", "not",
            "nothing", "now", "of", "off", "often", "on", "once", "only", "onto", "or", "other", "others",
            "otherwise", "ought", "our", "ours", "ourselves", "over", "own", "past", "per", "please",
            "rather", "same", "seem", "seems", "shall", "shan't", "she", "should", "shouldn't", "since",
            "so", "some", "somehow", "someone", "something", "sometime", "sometimes", "somewhere", "such",
            "than", "that", "the", "their", "theirs", "them", "themselves", "then", "there", "thereafter",
            "thereby", "therefore", "these", "they", "this", "those", "through", "throughout", "thus",
            "to", "together", "too", "toward", "towards", "under", "unless", "until", "up", "upon", "us",
            "very", "via", "was", "wasn't", "we", "were", "weren't", "what", "when", "whenever", "where",
            "whether", "which", "while", "who", "whoever", "whom", "whose", "why", "with", "within",
            "without", "won't", "would", "wouldn't", "yes", "yet", "you", "your", "yours", "yourself",
            "yourselves" };

    public abstract Object[] computeSynsetRepresentations(Synset[] windowWordsSynsets, String[] windowWords, int[] synset2WordIndex);
    public abstract double computeSimilarity(Object[] synsetRepresentations, String[] windowWords, int[] synset2WordIndex, int i, int j);
    public abstract double computeSimilarity(Synset synset1, String word1, Synset synset2, String word2);

    // This method eliminates stop words and removes apostrophe terminations of remaning words.
    public static String[] eliminateStopWordsFromWordSet(String[] contextWords) {
        boolean[] isStopWord = new boolean[contextWords.length];
        int i,j,newContextSize = contextWords.length;

        for (i = 0; i < contextWords.length; i++) {
            isStopWord[i] = false;

            for (j = 0; j < stopWords.length; j++)
                if (contextWords[i].toLowerCase().equals(stopWords[j]))
                    isStopWord[i] = true;

            if (isStopWord[i]) {
                newContextSize--;
            } else if(contextWords[i].indexOf("\'") > 0){
                contextWords[i] = contextWords[i].substring(0,contextWords[i].indexOf("\'"));
                for (j = 0; j < stopWords.length; j++)
                    if (contextWords[i].toLowerCase().equals(stopWords[j]))
                        isStopWord[i] = true;

                if (isStopWord[i])
                    newContextSize--;
            }

        }
        String[] newContextWords = new String[newContextSize];
        for (i = 0, j = 0; i < contextWords.length; i++)
            if (!isStopWord[i]){
                newContextWords[j++] = contextWords[i];
            }
        return newContextWords;
    }
} public class WordEmbeddingRelatedness extends SynsetRelatedness {
    private static WordVectors wordVectors = null;
    static WordEmbeddingRelatedness instance = null;
    static SenseComputation senseComputation;

    protected WordEmbeddingRelatedness(){}
    public static WordEmbeddingRelatedness getInstance(String wePath, String weType, SenseComputation senseComputation) {
        if(instance == null) {
            instance = new WordEmbeddingRelatedness();
        }

        if(wordVectors == null) {
            WordEmbeddingRelatedness.loadWordEmbeddings(wePath, weType);
        }

        WordEmbeddingRelatedness.senseComputation = senseComputation;

        return instance;
    }

    public double computeSimilarity(Object[] synsetRepresentation, String[] windowWords, int[] synset2WordIndex, int k, int j){
        INDArray[] windowWordsSenseEmbeddings = (INDArray[])synsetRepresentation;

        return Transforms.cosineSim(windowWordsSenseEmbeddings[k], windowWordsSenseEmbeddings[j]);
    }

    public double computeSimilarity(Synset synset1, String word1, Synset synset2, String word2){
        INDArray[] senseEmbeddings = new INDArray[2];
        senseEmbeddings[0] = Nd4j.create(SenseEmbedding.getSenseEmbedding(wordVectors, synset1, word1, senseComputation));
        senseEmbeddings[1] = Nd4j.create(SenseEmbedding.getSenseEmbedding(wordVectors, synset2, word2, senseComputation));

        return computeSimilarity(senseEmbeddings, null, null, 0, 1);
    }

    /**
     * Generates the sense embedding for each synset of every word from the context window
     */
    public Object[] computeSynsetRepresentations(Synset[] windowWordsSynsets, String[] windowWords, int[] synset2WordIndex) {
        Object[] windowWordsSenseEmbeddings = new INDArray[windowWordsSynsets.length];
        for (int k = 0; k < windowWordsSynsets.length; k++) {
            windowWordsSenseEmbeddings[k] = Nd4j.create(SenseEmbedding.getSenseEmbedding(wordVectors, windowWordsSynsets[k], windowWords[synset2WordIndex[k]], senseComputation));
        }

        return windowWordsSenseEmbeddings;
    }

    public static void loadWordEmbeddings(String wePath, String weType) {
        try {
            switch (weType) {
                case "Google":
                    WordEmbeddingRelatedness.wordVectors = WordVectorSerializer.loadGoogleModel(new File(wePath), true);
                    break;
                case "Glove":
                    WordEmbeddingRelatedness.wordVectors = WordVectorSerializer.loadTxtVectors(new File(wePath));
                    break;
                default:
                    System.out.println("Word Embeddings type is invalid! " + weType + " is not a valid type. Please use Google or Glove model.");
                    System.exit(0);
            }
        } catch (IOException e) {
            System.out.println("Could not find Word Embeddings file in " + wePath);
        }
    }
} public class AverageComputation extends SenseComputation {
    static AverageComputation instance = null;

    public static AverageComputation getInstance() {
        if(instance == null){
            instance = new AverageComputation();
        }

        return instance;
    }

    public double[] compute(ArrayList<Double[]> senseEmbeddings) {
        double[] senseEmbedding = new double[300];

        for (int i = 0; i < 300; i++) {
            senseEmbedding[i] = 0;
        }

        for (Double[] tmpSenseEmbedding : senseEmbeddings) {
            for (int i = 0; i < tmpSenseEmbedding.length; i++) {
                senseEmbedding[i] += tmpSenseEmbedding[i];
            }
        }

        int size = senseEmbeddings.size();
        for (int i = 0; i < senseEmbedding.length; i++) {
            senseEmbedding[i] /= size;
        }

        return senseEmbedding;
    }
} public class GeometricMedianComputation extends SenseComputation {
    static GeometricMedianComputation instance = null;

    public static GeometricMedianComputation getInstance() {
        if(instance == null){
            instance = new GeometricMedianComputation();
        }

        return instance;
    }

    /**
     * Computes the Geometric Median of a given array of word embeddings
     * @param senseEmbeddings Array of Word Embeddings
     * @return The vector that represents the geometric median of the given array
     */
    public double[] compute(ArrayList<Double[]> senseEmbeddings) {
        Double[] senseEmbedding = new Double[300];
        Double[] sum1;
        double sum2;

        int iterations = 10;
        double norm;

        for (int i = 0; i < 300; i++) {
            senseEmbedding[i] = 0d;
        }

        for (int i = 0; i < iterations; i++) {
            sum1 = new Double[300]; sum2 = 0;

            for (int j = 0; j < 300; j++) {
                sum1[j] = 0d;
            }

            for(Double[] tmpSenseEmbedding : senseEmbeddings) {
                // this is x
                norm = MathUtil.computeNorm(tmpSenseEmbedding, senseEmbedding);

                sum1 = MathUtil.sumVectors(sum1, MathUtil.divideVectors(tmpSenseEmbedding, norm));
                sum2 += 1 / norm;
            }

            senseEmbedding = MathUtil.divideVectors(sum1, sum2);
        }

        return ArrayUtils.toPrimitive(senseEmbedding);
    }
} public abstract class SenseComputation {
    protected SenseComputation() {
        // Exists only to defeat instantiation.
    }

    /**
     * Computes the Geometric Median of a given array of word embeddings
     * @param senseEmbeddings Array of Word Embeddings
     * @return The vector that represents the geometric median of the given array
     */
    public abstract double[] compute(ArrayList<Double[]> senseEmbeddings);
} public class LeskRelatedness extends SynsetRelatedness {
    static LeskRelatedness instance = null;

    protected LeskRelatedness(){}
    public static LeskRelatedness getInstance() {
        if(instance == null) {
            instance = new LeskRelatedness();
        }

        return instance;
    }
    public Object[] computeSynsetRepresentations(Synset[] windowWordsSynsets, String[] windowWords, int[] synset2WordIndex){
        return windowWordsSynsets;
    }

    public double computeSimilarity(Object[] synsetRepresentations, String[] windowWords, int[] synset2WordIndex, int i, int j){
        Synset[] synsets = (Synset[]) synsetRepresentations;

        return computeSimilarity(synsets[i], windowWords[synset2WordIndex[i]], synsets[j], windowWords[synset2WordIndex[j]]);
    }

    public double computeSimilarity(Synset synset1, String word1, Synset synset2, String word2){
        SynsetType pos1 = null, pos2 = null;

        try {
            pos1 = synset1.getType();
            pos2 = synset2.getType();
        } catch(Exception e){
            System.out.printf("ok");
        }

        if(pos1 == SynsetType.NOUN && pos2 == SynsetType.NOUN) {
            return NounSimilarity.similarity(synset1, synset2);
        } else if(pos1 == SynsetType.VERB && pos2 == SynsetType.VERB) {
            return VerbSimilarity.similarity(synset1, synset2);
        } else if(AdjectiveSimilarity.synsetTypeAdjective(pos1, pos2)) {
            return AdjectiveSimilarity.similarity(synset1, word1, synset2, word2);
        } else if(pos1 == SynsetType.ADVERB && pos2 == SynsetType.ADVERB) {
            return AdverbSimilarity.similarity(synset1, word1, synset2, word2);
        } else {
            return SynsetSimilarity.similarity(synset1, synset2);
        }
    }
} public class SynsetUtils {
    public static ConfigurationOperation configurationOperation;
    public static SynsetRelatedness synsetRelatedness;

    public static HashMap<String, Double> cacheSynsetRelatedness;

    public static double computeConfigurationScore(int[] synsets, double[][] synsetPairScores) {
        double senseScore = configurationOperation.getInitialScore();

        for (int i = 0; i < synsets.length - 1; i++) {
            for (int j = i + 1; j < synsets.length; j++) {
                senseScore = configurationOperation.applyOperation(senseScore, synsetPairScores[synsets[i]][synsets[j]]);
                senseScore = configurationOperation.applyOperation(senseScore, synsetPairScores[synsets[j]][synsets[i]]);
            }
        }

        return senseScore;
    }

    public static Synset[] getSynsets(int[] synsetsIndex, Synset[] windowWordsSynsets){
        Synset[] returnSynsets = new Synset[synsetsIndex.length];

        for (int i = 0; i < synsetsIndex.length; i++) {
            returnSynsets[i] = windowWordsSynsets[synsetsIndex[i]];
        }

        return returnSynsets;
    }

    public static double computeConfigurationScore(Synset[] synsets, String[] words, String[] POSTags, String[] globalSynsets){

        Synset targetSynset;
        int synsetsSize = synsets.length;
        int targetPOS;

        double senseScore = configurationOperation.getInitialScore();
        String targetWord;
        String key1, key2;
        String targetGlobalSense;
        double score;


        for (int i = 0; i < synsetsSize - 1; i++) {
            targetSynset = synsets[i];
            targetWord = words[i];
            targetGlobalSense = globalSynsets[i];

            for (int j = i + 1; j < synsetsSize; j++) {
                key1 = targetGlobalSense + "||" +  globalSynsets[j];
                key2 = globalSynsets[j] + "||" + targetGlobalSense;

                if(SynsetUtils.cacheSynsetRelatedness.containsKey(key1)){
                    score = SynsetUtils.cacheSynsetRelatedness.get(key1);
                } else if(SynsetUtils.cacheSynsetRelatedness.containsKey(key2)){
                    score = SynsetUtils.cacheSynsetRelatedness.get(key2);
                } else {
                    score = synsetRelatedness.computeSimilarity(targetSynset, targetWord, synsets[j], words[j]);
                    SynsetUtils.cacheSynsetRelatedness.put(key1, score);
                }

                senseScore = configurationOperation.applyOperation(senseScore, score);
                senseScore = configurationOperation.applyOperation(senseScore, score);
            }
        }

        return senseScore;
    }

    // TODO check if we need to remove this
    public static long numberOfSynsetCombination(WordNetDatabase wnDatabase, String[] documentWindow, String[] documentPOS){
        long combinations = 1;
        int length;

        for (int i = 0; i < documentWindow.length; i++) {
            length = WordUtils.extractSynsets(wnDatabase, documentWindow[i], POSUtils.asSynsetType(documentPOS[i])).length;

            combinations *= length == 0 ? 1 : length;
        }

        return combinations;
    }

    public static String getGloss(Synset synset) {
        String[] examples;
        String gloss;

        gloss = synset.getDefinition();
        examples = synset.getUsageExamples();
        for (String example : examples) gloss += " " + example;

        return gloss;
    }

    public static String getDefinition(Synset synset){
        return synset.getDefinition();
    }

    public static String getRelationGloss(Synset[] synsets) {
        String gloss = "";

        for(Synset synset : synsets)
            gloss += " " + getGloss(synset);

        return gloss;
    }

    public static String getRelationGloss(WordSense[] senses) {
        String gloss = "";

        for(WordSense sense : senses)
            gloss += " " + getGloss(sense.getSynset());

        return gloss;
    }

    public static String getSenseKey(Synset synset, String lemma){
        String[] allSenseKeys = synset.getSenseKeys();
        String senseKey = extractSenseKey(allSenseKeys, lemma);

        if(senseKey.isEmpty())
            return allSenseKeys[0];

        if(synset.getType() == SynsetType.ADJECTIVE_SATELLITE) {
            AdjectiveSatelliteSynset adjSynset = (AdjectiveSatelliteSynset) synset;
            String headSenseKey = adjSynset.getHeadSynset().getSenseKeys()[0];

            String headLemma = headSenseKey.split("%")[0];
            String headID = headSenseKey.split(":")[2];

            if(senseKey.length() > 0)
                senseKey = senseKey.substring(0, senseKey.length() - 1) + headLemma + ":" + headID;
        }

        return senseKey;
    }

    public static String extractSenseKey(String[] senseKeys, String lemma) {
        String senseKey = "";

        if(senseKeys.length == 0)
            return senseKey;

        for(String tmpSenseKey : senseKeys) {
            if(tmpSenseKey.contains(lemma.toLowerCase())) {
                senseKey = tmpSenseKey;
                break;
            }
        }

        return senseKey;
    }
} public class DatabaseWriter extends DocumentWriter {
    String outputPath;

    public DatabaseWriter(String outputPath){
        this.outputPath = outputPath;
    }

    public void write(String documentName, Synset[] synsets, String[] lemmas, String[] wordsID){
        BufferedWriter writer = null;
        String senseKey;
        try {

            File theDir = new File(outputPath);
            if (!theDir.exists()) {
                theDir.mkdir();
            }

            FileWriter fileWriter = new FileWriter(outputPath + "\\" + documentName + ".txt");
            writer = new BufferedWriter(fileWriter);

            for (int i = 0; i < synsets.length; i++) {

                if(synsets[i] == null){
                    senseKey = "";
                } else {
                    senseKey = SynsetUtils.getSenseKey(synsets[i], lemmas[i]);
                }

                writer.write(documentName + " " + wordsID[i] + " " + senseKey + "\n");
            }

            writer.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
} public abstract class DocumentWriter {
    public abstract void write(String documentName, Synset[] synsets, String[] lemmas, String[] wordsID);
} public class FileOutputWriter extends DocumentWriter {
    String outputPath;

    public FileOutputWriter(String outputPath) {
        this.outputPath = outputPath;
    }

    public void write(String documentName, Synset[] synsets, String[] lemmas, String[] wordsID){
        BufferedWriter writer = null;
        String gloss;
        try {

            File theDir = new File(outputPath);
            if (!theDir.exists()) {
                theDir.mkdir();
            }

            FileWriter fileWriter = new FileWriter(outputPath + "\\" + documentName + ".txt");
            writer = new BufferedWriter(fileWriter);

            for (int i = 0; i < synsets.length; i++) {

                if(synsets[i] == null){
                    gloss = "";
                } else {
                    gloss = SynsetUtils.getDefinition(synsets[i]);
                }

                writer.write("Word Index: " + wordsID[i] + " Lemma: \"" + lemmas[i] + "\"\n Definition: " + gloss + "\n\n");
            }

            writer.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import configuration.operations.AddOperation;
import configuration.operations.LogOperation;
import configuration.operations.SumSquaredOperation;
import edu.smu.tspell.wordnet.Synset;
import parsers.*;
import relatedness.SynsetRelatedness;
import relatedness.embeddings.WordEmbeddingRelatedness;
import relatedness.embeddings.sense.computations.AverageComputation;
import relatedness.embeddings.sense.computations.GeometricMedianComputation;
import relatedness.embeddings.sense.computations.SenseComputation;
import relatedness.lesk.LeskRelatedness;
import utils.SynsetUtils;
import writers.DatabaseWriter;
import writers.DocumentWriter;
import writers.FileOutputWriter;
import java.util.ArrayList;



class ShotgunWSD {
    @Parameter(names = "-n", description = "Length of the context windows", required = true)
    private Integer n;

    @Parameter(names = "-k", description = "Number of sense configurations considered for the voting scheme", required = true)
    private Integer k;

    @Parameter(names = "-c", description = "How many sense configurations are kept per context window")
    private Integer c = 15;

    @Parameter(names = "-minSynsetCollisions", description = "Minimum number of synset collisions used to merge context windows.")
    private Integer minSynsetCollisions = 1;

    @Parameter(names = "-maxSynsetCollisions", description = "Maximum number of synset collisions used to merge context windows.")
    private Integer maxSynsetCollisions = 4;

    @Parameter(names = {"-wn", "-wnDirectory"}, description = "Path to WordNet dictionary", required = true)
    private String wnDirectory;

    @Parameter(names = {"-we", "-wePath"}, description = "Path to Word Embeddings file path", required = true)
    private String wePath;

    @Parameter(names = "-weType", description = "Model of the Word Embeddings (Glove / Google)", required = true)
    private String weType;

    @Parameter(names = "-inputType", description = "Type of the input file ( dataset / text )", required = true)
    private String inputType;

    @Parameter(names = "-outputType", description = "Type of the input file ( dataset / text )", required = true)
    private String outputType;

    @Parameter(names = "-input", description = "Input file path", required = true)
    private String input;

    @Parameter(names = "-output", description = "Output file path", required = true)
    private String output;

    @Parameter(names = "-configurationOperationName", description = "Configuration Operation Name", required = true)
    private String configurationOperationName;

    @Parameter(names = "-senseComputationMethod", description = "Sense Computation Method", required = true)
    private String senseComputationMethod;

    @Parameter(names = "--help", help = true)
    private boolean help;

    public static void main(String[] args) {
        ShotgunWSD shotgunWSD = new ShotgunWSD();

        try {
            new JCommander(shotgunWSD, args);
            shotgunWSD.run();
        } catch(ParameterException e) {
            String[] helpArgs = {"--help"};
            JCommander jCommander = new JCommander(shotgunWSD, helpArgs);
            jCommander.setProgramName("ShotgunWSD");
            jCommander.usage();
        }
    }


    // Make as program attributes: synsetRelatedness, configurationOperation and senseComputation
    public void run() {
        ShotgunWSDRunner.loadWordNet(wnDirectory);
        DocumentParser fileParser = null;
        DocumentWriter fileWriter = null;

        switch (inputType) {
            case "dataset-semeval2007":
                fileParser = new SemEval2007Parser(input);
                break;
            case "dataset-semcor":
                fileParser = new SemCorParser(input);
                break;
            case "text":
                fileParser = new FileParser(input);
                FileParser.wnDatabase = ShotgunWSDRunner.wnDatabase;
                break;
            default:
                System.out.println("Input type is invalid! " + inputType + " is not a valid type.");
                System.exit(0);
        }

        switch (outputType) {
            case "dataset":
                fileWriter = new DatabaseWriter(output);
                break;
            case "text":
                fileWriter = new FileOutputWriter(output);
                break;
            default:
                System.out.println("Input type is invalid! " + inputType + " is not a valid type.");
                System.exit(0);
        }



        ArrayList<ParsedDocument> documents = fileParser.parse();

        SenseComputation senseComputation = null;
        if(senseComputationMethod.equals("geo"))
            senseComputation = GeometricMedianComputation.getInstance();
        else if(senseComputationMethod.equals("avg"))
            senseComputation = AverageComputation.getInstance();

        // SynsetRelatedness synsetRelatedness = WordEmbeddingRelatedness.getInstance(wePath, weType, senseComputation);
        SynsetRelatedness synsetRelatedness = LeskRelatedness.getInstance();

        if(configurationOperationName.equals("add2"))
            SynsetUtils.configurationOperation = SumSquaredOperation.getInstance();
        else if(configurationOperationName.equals("log"))
            SynsetUtils.configurationOperation = LogOperation.getInstance();
        else if(configurationOperationName.equals("add"))
            SynsetUtils.configurationOperation = AddOperation.getInstance();