/*
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License (the License). You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
 * or http://www.netbeans.org/cddl.txt.
 * 
 * When distributing Covered Code, include this CDDL Header Notice in each file
 * and include the License file at http://www.netbeans.org/cddl.txt.
 * If applicable, add the following below the CDDL Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.core.perftool;

import java.io.*;
import java.util.*;
import java.util.regex.*;
import java.text.DateFormat;

/** Parses ide.log, searches for sessions, and acquires
 * data about the specific session
 *
 */
final class Parser {

    private static final String nline = System.getProperty("line.separator");
    private static final Pattern SESSION_START_PATTERN = Pattern.compile("^([\\-]+)" + nline + "^(>Log Session: .*)", Pattern.MULTILINE); // NOI18N
    private static Pattern SESSION_END_PATTERN = Pattern.compile("^([\\-]+)" + nline, Pattern.MULTILINE); // NOI18N
    private static Pattern ENTRY_PATTERN = Pattern.compile("^\\Q" + PerformanceMeterImpl.LOG_PREFIX + "\\E([^0-9]+)([0-9]+)\\.([0-9]+) s", Pattern.MULTILINE); // NOI18N
    
    /** Constructs new Parser */
    private Parser() {
    }
    
    public static SessionInfo[] getData(File f) throws IOException {
        CharSequence iter;
        Reader r = new FileReader(f);
        try {
            StringBuffer b = new StringBuffer((int)f.length());
            int read;
            char[] buf = new char[4096];
            while ((read = r.read(buf)) != -1) {
                b.append(buf, 0, read);
            }
            iter = b;
        } finally {
            r.close();
        }
        List sessions = new ArrayList(20);
        
        int idx = 0; // index of last processed char
        for (;;) {
            Matcher m = SESSION_START_PATTERN.matcher(iter.subSequence(idx, iter.length()));
            if (m.find()) {
                int start = m.start();
                parseEntries(iter, idx, start, sessions);
                Matcher m2 = SESSION_END_PATTERN.matcher(iter.subSequence(m.end(), iter.length()));
                if (m2.find()) {
                    int end = m2.end();
                    SessionInfo info = new SessionInfo(trimString(iter.subSequence(start, end).toString()));
                    sessions.add(info);
                    idx = end;
                }
            } else {
                break;
            }
        }
    
        return (SessionInfo[]) sessions.toArray(new SessionInfo[sessions.size()]);
    }
    
    private static void parseEntries(CharSequence iter, int startpos, int endpos, List sessions) throws IOException, PatternSyntaxException {
        int size = sessions.size();
        if (size == 0) {
            return;
        }
        
        SessionInfo session = (SessionInfo) sessions.get(size - 1);

        Matcher m;;
        while ((endpos > startpos) && (m = ENTRY_PATTERN.matcher(iter.subSequence(startpos, iter.length()))).find()) {
            if (m.start() > endpos) {
                startpos = m.start();
                continue;
            }
            
            String entry = m.group();
            double timeVal = strings2Double(m.group(2), m.group(3));
            Field f = new Field(session, m.group(1), timeVal);
            session.addField(f);
            startpos = m.end();
        }
        
        if (session.size() == 0) {
            sessions.remove(size - 1);
        }
    }
    
    private static double strings2Double(String sx, String sy) {
        try {
            double ret = Double.parseDouble(sx);
            ret += Double.parseDouble(sy) / Util.tenpower(sy.length());
            return ret;
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
        
        return -1d;
    }

    private static String trimString(String s) {
        int idx = 0;
        char c;
        final int slen = s.length();

        if (slen == 0) {
            return s;
        }

        do {
            c = s.charAt(idx++);
        } while ((c == '\n' || c == '\r') &&
                 (idx < slen));

        s = s.substring(--idx);
        idx = s.length() - 1;

        if (idx < 0) {
            return s;
        }

        do {
            c = s.charAt(idx--);
        } while ((c == '\n' || c == '\r') &&
                 (idx >= 0));

        return s.substring(0, idx + 2);
    }
    
    /** Basic Info about the session
     * whole desc
     * build number (-1 if a bug)
     * Date (null if a problem)
     * Fields related to the session
     */
    public static class SessionInfo {
        private String desc;
        private Map fields;
        private int build;
        private Date date;
        
        SessionInfo(String desc) {
            this.desc = desc;
            this.fields = new HashMap(80);
            this.build = getBuildNumber(desc);
            Locale loc = getLocale(desc);
            this.date = getDate(desc, loc);
        }
        
        public String getDescription() {
            return desc;
        }
        
        void addField(Field f) {
            fields.put(f.getName(), f);
        }
        
        public Field getField(String name) {
            return (Field) fields.get(name);
        }
        
        public int getBuildNumber() {
            return build;
        }
        
        public Date getDate() {
            return date;
        }
        
        int size() {
            return fields.size();
        }
        
        private static int getBuildNumber(String desc) {
            try {
                int idx = desc.indexOf("(Build "); // NOI18N
                int paren = desc.indexOf(')', idx);
                String build = desc.substring(idx + 7, paren);
                if (build.equals("@BUILD_NUMBER_SUBST@")) { // NOI18N
                    return -1;
                }
                return Integer.parseInt(build);
            } catch (NumberFormatException e) {
                e.printStackTrace();
                return -1;
            }
        }
        
        private static Locale getLocale(String desc) {
            int idx = desc.indexOf("System Locale"); // NOI18N
            idx = desc.indexOf("= ", idx); // NOI18N
            idx += 2;
            String[] locale = new String[] { null, "", "" }; // NOI18N
            int ptr = 0;
            StringBuffer sb = new StringBuffer(3);
            char c = desc.charAt(idx);
            while (Character.isLetter(c)) {
                if (c == ':') {
                    locale[ptr++] = sb.toString();
                    sb.setLength(0);
                    continue;
                }
                sb.append(c);
                c = desc.charAt(++idx);
            }
            locale[ptr] = sb.toString();
            return new Locale(locale[0], locale[1], locale[2]);
        }
        
        private static Date getDate(String desc, Locale locale) {
            int idx = desc.indexOf(">Log Session: "); // NOI18N
            int end = desc.indexOf('>', idx + 14);
            String date = trimString(desc.substring(idx + 14, end));
            DateFormat df = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL, locale);
            Date dt = df.parse(date, new java.text.ParsePosition(0));
            return dt;
        }
    }
    
    /** Represents one line of Performance journal in the log */
    public static class Field {
        private String name;
        private double time;
        private SessionInfo session;
        
        Field(SessionInfo session, String name, double time) {
            this.name = name;
            this.time = time;
            this.session = session;
        }
        
        public String getName() {
            return name;
        }
        
        public double getTime() {
            return time;
        }
        
        public SessionInfo getSessionInfo() {
            return session;
        }
    }
}
