/*******************************************************************************
 * Copyright (c) 2009, 2018 Red Hat, Inc.
 * 
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Elliott Baron <ebaron@redhat.com> - initial API and implementation
 *******************************************************************************/
package org.eclipse.linuxtools.internal.valgrind.cachegrind;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

import org.eclipse.linuxtools.internal.valgrind.cachegrind.model.CachegrindDescription;
import org.eclipse.linuxtools.internal.valgrind.cachegrind.model.CachegrindFile;
import org.eclipse.linuxtools.internal.valgrind.cachegrind.model.CachegrindFunction;
import org.eclipse.linuxtools.internal.valgrind.cachegrind.model.CachegrindLine;
import org.eclipse.linuxtools.internal.valgrind.cachegrind.model.CachegrindOutput;
import org.eclipse.linuxtools.valgrind.core.ValgrindParserUtils;

public final class CachegrindParser {
    private static final String COLON = ":"; //$NON-NLS-1$
    private static final String SPACE = " "; //$NON-NLS-1$
    private static final String EQUALS = "="; //$NON-NLS-1$

    private static final String CMD = "cmd"; //$NON-NLS-1$
    private static final String DESC = "desc"; //$NON-NLS-1$
    private static final String FL = "fl"; //$NON-NLS-1$
    private static final String FN = "fn"; //$NON-NLS-1$
    private static final String EVENTS = "events"; //$NON-NLS-1$
    private static final String SUMMARY = "summary"; //$NON-NLS-1$

    private static final String COMMA = ","; //$NON-NLS-1$

    protected static CachegrindParser instance;

    private CachegrindParser() {
    }

    public static CachegrindParser getParser() {
        if (instance == null) {
            instance = new CachegrindParser();
        }
        return instance;
    }

    public void parse(CachegrindOutput output, File cgOut) throws IOException {
        try (BufferedReader br = new BufferedReader(new FileReader(cgOut))) {
            output.setPid(ValgrindParserUtils.parsePID(cgOut.getName(), CachegrindLaunchDelegate.OUT_PREFIX));

            String line;
            CachegrindFile curFl = null;
            CachegrindFunction curFn = null;
            while ((line = br.readLine()) != null) {
                if (line.startsWith(EVENTS + COLON)) {
                    output.setEvents(ValgrindParserUtils.parseStrValue(line, COLON + SPACE).split(SPACE));
                } else if (line.startsWith(CMD + COLON)) {
                    //continue
                } else if (line.startsWith(DESC + COLON)) {
                    CachegrindDescription description = parseDescription(line);
                    output.addDescription(description);
                } else if (line.startsWith(FL + EQUALS)) {
                    curFl = new CachegrindFile(output, ValgrindParserUtils.parseStrValue(line, EQUALS));
                    output.addFile(curFl);
                } else if (line.startsWith(FN + EQUALS)) {
                    if (curFl != null) {
                        curFn = new CachegrindFunction(curFl, ValgrindParserUtils.parseStrValue(line, EQUALS));
                        curFl.addFunction(curFn);
                    } else {
                        ValgrindParserUtils.fail(line);
                    }
                } else if (line.startsWith(SUMMARY + COLON)) {
                    long[] summary = parseData(line, ValgrindParserUtils.parseStrValue(line, COLON + SPACE).split(SPACE));
                    output.setSummary(summary);
                }
                else { // line data
                    String[] tokens = line.split(SPACE, 2);
                    if (ValgrindParserUtils.isNumber(tokens[0])) {
                        int lineNo = Integer.parseInt(tokens[0]);

                        long[] data = parseData(line, tokens[1].split(SPACE));
                        if (curFn != null) {
                            curFn.addLine(new CachegrindLine(curFn, lineNo, data));
                        } else {
                            ValgrindParserUtils.fail(line);
                        }
                    } else {
                        ValgrindParserUtils.fail(line);
                    }
                }
            }
        }
    }

    private long[] parseData(String line, String[] data) throws IOException {
        long[] result = new long[data.length];
        for (int i = 0; i < data.length; i++) {
            if (!ValgrindParserUtils.isNumber(data[i])) {
                ValgrindParserUtils.fail(line);
            }
            result[i] = Long.parseLong(data[i]);
        }
        return result;
    }

    private CachegrindDescription parseDescription(String line) throws IOException {
        CachegrindDescription desc = null;
        String[] tokens = line.split(COLON + "\\s+"); //$NON-NLS-1$
        if (tokens.length == 3) {
            String name = tokens[1];
            tokens = tokens[2].split(COMMA + SPACE);
            if (tokens.length == 3) {
                desc = new CachegrindDescription(name);
            } else {
                ValgrindParserUtils.fail(line);
            }
        } else {
            ValgrindParserUtils.fail(line);
        }
        return desc;
    }
}