package com.sun.electric.tool.drc;

import com.sun.electric.database.geometry.EPoint;
import com.sun.electric.database.geometry.PolyBase;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.user.ErrorLogger;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Reads RDB error database, typically used for reporting antenna rule
 * violations. A read of a new file accumulates to the same error report,
 * as typically multiple RDB databases are generated for a single
 * antenna rule drc run.
 */
public class CalibreRdbErrors {
    private int scale;
    private String topCellName;
    private Cell topCell;
    private ErrorLogger logger;
    private BufferedReader in;
    private int lineno;
//    private Map<Cell,String> mangledNames;
    private int errorCount;

    private static final String spaces = "[\\s\\t ]+";

    /**
     * Create a new error object to read errors in from file
     * This creates a new error logger instance to add errors to,
     * but you must call {@link CalibreRdbErrors#termLogging(boolean)}
     * to display the errors in the error log.
     */
    public CalibreRdbErrors() {
        logger = ErrorLogger.newInst("Calibre Antenna DRC Errors");
        errorCount = 0;
    }

    /**
     * Import errors from an RDB database to the current logger
     * @param filename the filename to import errors from
     * @param mangledNames mangled GDS cell names
     */
    public void importErrors(String filename, Map<Cell,String> mangledNames) {
        lineno = 1;
//        this.mangledNames = mangledNames;
        try {
            System.out.println("Reading RDB file "+filename);
            FileReader reader = new FileReader(filename);
            in = new BufferedReader(reader);

            // read header
            String line = in.readLine();
            if (line == null) return;
            String [] parts = line.trim().split(spaces);
            if (parts.length != 2) {
                System.out.println("Error: Invalid header in RDB file at line "+lineno+": "+line);
                return;
            }
            topCellName = parts[0];
            topCell = CalibreDrcErrors.getCell(topCellName, mangledNames);
            if (topCell == null) {
                System.out.println("Error: Cell '"+topCellName+"' specified in file does not exist in current hierarchy: "+filename);
                return;
            }
            try {
                scale = Integer.valueOf(parts[1]).intValue();
            } catch (NumberFormatException e) {
                System.out.println("Warning: Invalid scale value "+parts[1]+" at line "+lineno);
                scale = 1000;
            }
            lineno++;
            boolean more = true;
            while (more) {
                more = readRuleViolation();
            }
            in.close();
        } catch (IOException e) {
            System.out.println("Error importing RDB database: "+e.getMessage());
            return;
        }
    }

    /**
     * Finish reading errors and display error logger
     * @param explain true to pop up display to user
     */
    public void termLogging(boolean explain) {
        logger.termLogging(explain);
        Job.getUserInterface().showInformationMessage("Calibre Antenna DRC Imported "+errorCount+" errors", "Calibre Antenna DRC");
    }

    /**
     * Read a violation from the current opened database file
     * @return false if nothing left to read
     * @throws IOException on read exception
     */
    private boolean readRuleViolation() throws IOException {
        if (in == null) return false;
        double lambdaScale = topCell.getTechnology().getScale() / 1000;

        // first line is rule_name::some value
        String line = in.readLine();
        if (line == null) return false;
        int ruleEnd = line.indexOf("::");
        if (ruleEnd < 0) {
            System.out.println("Error: expected :: in rule name line at "+lineno+": "+line);
            return false;
        }
        String rule = line.substring(0, ruleEnd);
        lineno++;

        // second line is date, ignore
        line = in.readLine();
        if (line == null) {
            System.out.println("Error: premature end of file at line "+lineno);
            return false;
        }
        lineno++;

        // third line is rule check code
        line = in.readLine();
        if (line == null) {
            System.out.println("Error: premature end of file at line "+lineno);
            return false;
        }
        String ruleText = line;
        boolean ruleTextAnnotatedWithAR = false;
        lineno++;

        // from now on, line is either a list of name=value pairs,
        // or it is a polygon (p) or edge (e) geometry list
        List<EPoint> lineList = new ArrayList<EPoint>();
        List<PolyBase> polyList = new ArrayList<PolyBase>();
        List<Property> properties = new ArrayList<Property>();
        StringBuffer propLines = new StringBuffer();
        Cell cell = null;

        while (true) {
            in.mark(200);
            line = in.readLine();
            if (line == null) break;
            if (line.startsWith(rule)) {
                in.reset();
                break; // next error found, report this error and return true
            }
            if (line.contains("=")) {
                String parts [] = line.trim().split(spaces);
                String commonprefix = "";
                int i=0;
                if (!parts[0].contains("=")) {
                    // this is a common prefix to all names in the subsequent name=value pairs
                    commonprefix = parts[0]+" ";
                    i++;
                }
                for (; i<parts.length; i++) {
                    String [] namevalue = parts[i].split("=");
                    if (namevalue.length != 2) continue;
                    properties.add(new Property(commonprefix+namevalue[0], namevalue[1]));
/*
                    if (namevalue[0].equalsIgnoreCase("cell")) {
                        cell = CalibreDrcErrors.getCell(namevalue[1], mangledNames);
                        if (cell == null) continue;
                        lambdaScale = cell.getTechnology().getScale() / 1000;
                    }
*/
                }
                propLines.append(line);
                propLines.append("\n");
                lineno++;
            }
            else if (line.startsWith("p")) {
                String [] parts = line.trim().split(spaces);
                if (parts.length != 3) {
                    System.out.println("Error parsing polygon list at line"+lineno+": "+line);
                    return false;
                }
                int count = 0;
                try {
                    count = Integer.parseInt(parts[2]);
                } catch (NumberFormatException e) {
                    System.out.println("Error: expected number as third argument in polygon at line "+lineno+": "+parts[2]);
                    return false;
                }
                PolyBase.Point[] points = new PolyBase.Point[count];
                for (int i=0; i<count; i++) {
                    line = in.readLine();
                    if (line == null) {
                        System.out.println("Error: premature end of file at line "+lineno);
                        return false;
                    }
                    if (line.startsWith("AR")) {
                        i=-1; // reset, AR line seems to be new thing for newer version of Calibre.
                        if (!ruleTextAnnotatedWithAR) {
                            ruleText = line+"; "+ruleText;
                            ruleTextAnnotatedWithAR = true;
                        }
                        continue;
                    }
                    lineno++;
                    String [] coords = line.trim().split(spaces);
                    if (coords.length != 2) {
                        System.out.println("Error: expected two fields for poly coord at "+lineno+": "+line);
                        return false;
                    }
                    double x=0, y=0;
                    try {
                        x = Double.parseDouble(coords[0])/scale/lambdaScale;
                        y = Double.parseDouble(coords[1])/scale/lambdaScale;
                    } catch (NumberFormatException e) {
                        System.out.println("Error: polygon coordinates should be numbers at line "+lineno+": "+line);
                        return false;
                    }
                    points[i] = PolyBase.fromLambda(x, y);
                }
                polyList.add(new PolyBase(points));
            }
            else if (line.startsWith("e")) {
                System.out.println("Error: edge spec not supported yet, didn't have example. Please tell JKG. Thanks");
                return false;
            }
        }

        // Log error
        StringBuffer props = new StringBuffer();
        for (Property prop : properties) {
            props.append(prop.toString());
        }
        if (cell == null) cell = topCell;
        logger.logMessageWithLines(ruleText+"\n"+propLines.toString(), polyList, lineList, cell, 0, true);
        errorCount++;
        return true;
    }

    private static class Property {
        private final String name;
        private final String value;
        private Property(String name, String value) {
            this.name = name;
            this.value = value;
        }
        public String toString() {
            return name+"="+value;
        }
    }
}
