/*
 *   Copyright (C) 2014  Alfons Wirtz
 *   website www.freerouting.net
 *
 *   Copyright (C) 2017 Michael Hoffer <info@michaelhoffer.de>
 *   Website www.freerouting.mihosoft.eu
*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License at <http://www.gnu.org/licenses/> 
 *   for more details.
 *
 * SessionFile.java
 *
 * Created on 29. Oktober 2004, 08:01
 */

package eu.mihosoft.freerouting.designforms.specctra;

import java.util.Collection;
import java.util.Iterator;

import eu.mihosoft.freerouting.geometry.planar.FloatPoint;
import eu.mihosoft.freerouting.geometry.planar.Point;

import eu.mihosoft.freerouting.board.BasicBoard;
import eu.mihosoft.freerouting.board.PolylineTrace;
import eu.mihosoft.freerouting.board.Via;
import eu.mihosoft.freerouting.board.ConductionArea;
import eu.mihosoft.freerouting.datastructures.IndentFileWriter;
import eu.mihosoft.freerouting.datastructures.IdentifierType;
import eu.mihosoft.freerouting.logger.FRLogger;

/**
 * Methods to handle a Specctra session file.
 *
 * @author Alfons Wirtz
 */
public class SessionFile
{
    /**
     * Creates a Specctra session file to update the host system from the RoutingBooard
     */
    public static boolean write( BasicBoard p_board, java.io.OutputStream p_output_stream,  String p_design_name)
    {
        if (p_output_stream == null)
        {
            return false;
        }
        IndentFileWriter output_file = null;
        try
        {
            output_file = new IndentFileWriter(p_output_stream);
        }
        catch (Exception e)
        {
            FRLogger.error("unable to create session file", e);
            return false;
        }
        String session_name = p_design_name.replace(".dsn", ".ses");
        try
        {
            String [] reserved_chars = {"(", ")", " ", "-"};
            IdentifierType identifier_type = new IdentifierType(reserved_chars, p_board.communication.specctra_parser_info.string_quote);
            write_session_scope(p_board, identifier_type, output_file, session_name, p_design_name);
        }
        catch (java.io.IOException e)
        {
            FRLogger.error("unable to write session file", e);
            return false;
        }
        try
        {
            output_file.close();
        }
        catch (java.io.IOException e)
        {
            FRLogger.error("unable to close session file", e);
            return false;
        }
        return true;
    }
    
    private static void write_session_scope(BasicBoard p_board, IdentifierType p_identifier_type,
            IndentFileWriter p_file, String p_session_name, String p_design_name) throws java.io.IOException
    {
        double scale_factor =  p_board.communication.coordinate_transform.dsn_to_board(1)/p_board.communication.resolution;
        CoordinateTransform coordinate_transform = new CoordinateTransform(scale_factor, 0, 0);
        p_file.start_scope();
        p_file.write("session ");
        p_identifier_type.write(p_session_name, p_file);
        p_file.new_line();
        p_file.write("(base_design ");
        p_identifier_type.write(p_design_name, p_file);
        p_file.write(")");
        write_placement(p_board, p_identifier_type, coordinate_transform, p_file);
        write_was_is(p_board, p_identifier_type, p_file);
        write_routes(p_board, p_identifier_type, coordinate_transform, p_file);
        p_file.end_scope();
    }
    
    public static void write_placement(BasicBoard p_board, IdentifierType p_identifier_type,
            CoordinateTransform p_coordinate_transform, IndentFileWriter p_file) throws java.io.IOException
    {
        p_file.start_scope();
        p_file.write("placement");
        Resolution.write_scope(p_file, p_board.communication);
        
        for (int i = 1; i <= p_board.library.packages.count(); ++i)
        {
            write_components(p_board, p_identifier_type, p_coordinate_transform, p_file, p_board.library.packages.get(i));
        }
        p_file.end_scope();
    }
    
    /**
     * Writes all components with the package p_package to the session file.
     */
    public static void write_components(BasicBoard p_board, IdentifierType p_identifier_type, CoordinateTransform p_coordinate_transform,
            IndentFileWriter p_file, eu.mihosoft.freerouting.library.Package p_package) throws java.io.IOException
    {
        Collection<eu.mihosoft.freerouting.board.Item> board_items =  p_board.get_items();
        boolean component_found = false;
        for (int i = 1; i <= p_board.components.count(); ++i)
        {
            eu.mihosoft.freerouting.board.Component curr_component =  p_board.components.get(i);
            if (curr_component.get_package() == p_package)
            {
                // check, if not all items of the component are deleted
                boolean undeleted_item_found = false;
                Iterator<eu.mihosoft.freerouting.board.Item> it = board_items.iterator();
                while (it.hasNext())
                {
                    eu.mihosoft.freerouting.board.Item curr_item = it.next();
                    if (curr_item.get_component_no() == curr_component.no)
                    {
                        undeleted_item_found = true;
                        break;
                    }
                }
                if (undeleted_item_found)
                {
                    if (!component_found)
                    {
                        // write the scope header
                        p_file.start_scope();
                        p_file.write("component ");
                        p_identifier_type.write(p_package.name, p_file);
                        component_found = true;
                    }
                    write_component(p_board, p_identifier_type, p_coordinate_transform, p_file, curr_component);
                }
            }
        }
        if (component_found)
        {
            p_file.end_scope();
        }
    }
    
    public static void write_component(BasicBoard p_board, IdentifierType p_identifier_type, CoordinateTransform p_coordinate_transform,
            IndentFileWriter p_file, eu.mihosoft.freerouting.board.Component p_component) throws java.io.IOException
    {
        p_file.new_line();
        p_file.write("(place ");
        p_identifier_type.write(p_component.name, p_file);
        double[] location = p_coordinate_transform.board_to_dsn(p_component.get_location().to_float());
        Integer x_coor = (int) Math.round(location[0]);
        Integer y_coor = (int) Math.round(location[1]);
        p_file.write(" ");
        p_file.write(x_coor.toString());
        p_file.write(" ");
        p_file.write(y_coor.toString());
        if (p_component.placed_on_front())
        {
            p_file.write(" front ");
        }
        else
        {
            p_file.write(" back ");
        }
        int rotation = (int) Math.round(p_component.get_rotation_in_degree());
        p_file.write((Integer.valueOf(rotation).toString()));
        if (p_component.position_fixed)
        {
            p_file.new_line();
            p_file.write(" (lock_type position)");
        }
        p_file.write(")");
    }
    
    public static void write_was_is(BasicBoard p_board, IdentifierType p_identifier_type,
            IndentFileWriter p_file) throws java.io.IOException
    {
        p_file.start_scope();
        p_file.write("was_is");
        Collection<eu.mihosoft.freerouting.board.Pin> board_pins = p_board.get_pins();
        for (eu.mihosoft.freerouting.board.Pin curr_pin : board_pins)
        {
            eu.mihosoft.freerouting.board.Pin swapped_with = curr_pin.get_changed_to();
            if (curr_pin.get_changed_to() != curr_pin)
            {
                p_file.new_line();
                p_file.write("(pins ");
                eu.mihosoft.freerouting.board.Component curr_cmp = p_board.components.get(curr_pin.get_component_no());
                if (curr_cmp != null)
                {
                    p_identifier_type.write(curr_cmp.name, p_file);
                    p_file.write("-");
                    eu.mihosoft.freerouting.library.Package.Pin package_pin = curr_cmp.get_package().get_pin(curr_pin.get_index_in_package());
                    p_identifier_type.write(package_pin.name, p_file);
                }
                else
                {
                    FRLogger.warn("SessionFile.write_was_is: component not found");
                }
                p_file.write(" ");
                eu.mihosoft.freerouting.board.Component swap_cmp = p_board.components.get(swapped_with.get_component_no());
                if (swap_cmp != null)
                {
                    p_identifier_type.write(swap_cmp.name, p_file);
                    p_file.write("-");
                    eu.mihosoft.freerouting.library.Package.Pin package_pin = swap_cmp.get_package().get_pin(swapped_with.get_index_in_package());
                    p_identifier_type.write(package_pin.name, p_file);
                }
                else
                {
                    FRLogger.warn("SessionFile.write_was_is: component not found");
                }
                p_file.write(")");
            }
        }
        p_file.end_scope();
    }
    
    private static void write_routes(BasicBoard p_board, IdentifierType p_identifier_type, CoordinateTransform p_coordinate_transform,
            IndentFileWriter p_file) throws java.io.IOException
    {
        p_file.start_scope();
        p_file.write("routes ");
        Resolution.write_scope(p_file, p_board.communication);
        Parser.write_scope(p_file,p_board.communication.specctra_parser_info, p_identifier_type, true);
        write_library(p_board, p_identifier_type, p_coordinate_transform, p_file);
        write_network(p_board, p_identifier_type, p_coordinate_transform, p_file);
        p_file.end_scope();
    }
    
    private static void write_library(BasicBoard p_board, IdentifierType p_identifier_type, CoordinateTransform p_coordinate_transform,
            IndentFileWriter p_file) throws java.io.IOException
    {
        p_file.start_scope();
        p_file.write("library_out ");
        for (int i = 0; i < p_board.library.via_padstack_count(); ++i)
        {
            write_padstack(p_board.library.get_via_padstack(i), p_board, p_identifier_type, p_coordinate_transform, p_file);
        }
        p_file.end_scope();
    }
    
    private static void write_padstack(eu.mihosoft.freerouting.library.Padstack p_padstack, BasicBoard p_board, IdentifierType p_identifier_type,
                                       CoordinateTransform p_coordinate_transform, IndentFileWriter p_file)
            throws java.io.IOException
    {
        // search the layer range of the padstack
        int first_layer_no = 0;
        while (first_layer_no < p_board.get_layer_count())
        {
            if (p_padstack.get_shape(first_layer_no) != null)
            {
                break;
            }
            ++first_layer_no;
        }
        int last_layer_no = p_board.get_layer_count() - 1;
        while (last_layer_no >= 0 )
        {
            if (p_padstack.get_shape(last_layer_no) != null)
            {
                break;
            }
            --last_layer_no;
        }
        if (first_layer_no >= p_board.get_layer_count() || last_layer_no < 0)
        {
            FRLogger.warn("SessionFile.write_padstack: padstack shape not found");
            return;
        }
        
        p_file.start_scope();
        p_file.write("padstack ");
        p_identifier_type.write(p_padstack.name, p_file);
        for (int i = first_layer_no; i <= last_layer_no; ++i)
        {
            eu.mihosoft.freerouting.geometry.planar.Shape curr_board_shape = p_padstack.get_shape(i);
            if (curr_board_shape == null)
            {
                continue;
            }
            eu.mihosoft.freerouting.board.Layer board_layer = p_board.layer_structure.arr[i];
            Layer curr_layer = new Layer(board_layer.name, i, board_layer.is_signal);
            Shape curr_shape = p_coordinate_transform.board_to_dsn_rel(curr_board_shape, curr_layer);
            p_file.start_scope();
            p_file.write("shape");
            curr_shape.write_scope_int(p_file, p_identifier_type);
            p_file.end_scope();
        }
        if (!p_padstack.attach_allowed)
        {
            p_file.new_line();
            p_file.write("(attach off)");
        }
        p_file.end_scope();
    }
    
    
    
    private static void write_network(BasicBoard p_board, IdentifierType p_identifier_type, CoordinateTransform p_coordinate_transform,
            IndentFileWriter p_file) throws java.io.IOException
    {
        p_file.start_scope();
        p_file.write("network_out ");
        for (int i = 1; i <= p_board.rules.nets.max_net_no(); ++i)
        {
            write_net(i, p_board, p_identifier_type, p_coordinate_transform, p_file);
        }
        p_file.end_scope();
    }
    
    private static void write_net(int p_net_no, BasicBoard p_board, IdentifierType p_identifier_type, CoordinateTransform p_coordinate_transform,
            IndentFileWriter p_file) throws java.io.IOException
    {
        Collection<eu.mihosoft.freerouting.board.Item> net_items = p_board.get_connectable_items(p_net_no);
        boolean header_written = false;
        Iterator<eu.mihosoft.freerouting.board.Item> it = net_items.iterator();
        while (it.hasNext())
        {
            eu.mihosoft.freerouting.board.Item curr_item = it.next();
            if (curr_item.get_fixed_state() == eu.mihosoft.freerouting.board.FixedState.SYSTEM_FIXED)
            {
                continue;
            }
            boolean is_wire = curr_item instanceof PolylineTrace;
            boolean is_via = curr_item instanceof Via;
            boolean is_conduction_area =  curr_item instanceof ConductionArea
                    && p_board.layer_structure.arr [curr_item.first_layer()].is_signal;
            if (!header_written && (is_wire || is_via || is_conduction_area))
            {
                p_file.start_scope();
                p_file.write("net ");
                eu.mihosoft.freerouting.rules.Net curr_net = p_board.rules.nets.get(p_net_no);
                if (curr_net == null)
                {
                    FRLogger.warn("SessionFile.write_net: net not found");
                }
                else
                {
                    p_identifier_type.write(curr_net.name, p_file);
                }
                header_written = true;
            }
            if (is_wire)
            {
                write_wire((PolylineTrace)curr_item, p_board, p_identifier_type, p_coordinate_transform, p_file);
            }
            else if (is_via)
            {
                write_via((Via) curr_item, p_board, p_identifier_type, p_coordinate_transform, p_file);
            }
            else if (is_conduction_area)
            {
                write_conduction_area( (ConductionArea) curr_item, p_board, p_identifier_type,
                        p_coordinate_transform,  p_file);
            }
        }
        if (header_written)
        {
            p_file.end_scope();
        }
    }
    
    private static void write_wire(PolylineTrace p_wire, BasicBoard p_board, IdentifierType p_identifier_type,
            CoordinateTransform p_coordinate_transform, IndentFileWriter p_file) throws java.io.IOException
    {
        int layer_no = p_wire.get_layer();
        eu.mihosoft.freerouting.board.Layer board_layer = p_board.layer_structure.arr[layer_no];
        int wire_width = (int) Math.round(p_coordinate_transform.board_to_dsn(2 * p_wire.get_half_width()));
        p_file.start_scope();
        p_file.write("wire");
        Point[] corner_arr = p_wire.polyline().corner_arr();
        int [] coors = new int [2 * corner_arr.length];
        int corner_index = 0;
        int [] prev_coors = null;
        for (int i = 0; i < corner_arr.length; ++i)
        {
            double[] curr_float_coors = p_coordinate_transform.board_to_dsn(corner_arr[i].to_float());
            int [] curr_coors = new int[2];
            curr_coors[0] = (int) Math.round(curr_float_coors[0]);
            curr_coors[1] = (int) Math.round(curr_float_coors[1]);
            if (i == 0  || ( curr_coors[0] != prev_coors[0] || curr_coors[1] != prev_coors[1]))
            {
                coors[corner_index] = curr_coors[0];
                ++corner_index;
                coors[corner_index] = curr_coors[1];
                ++corner_index;
                prev_coors = curr_coors;
                
            }
        }
        if (corner_index < coors.length)
        {
            int [] adjusted_coors = new int[corner_index];
            for (int i = 0; i < adjusted_coors.length; ++i)
            {
                adjusted_coors[i] = coors[i];
            }
            coors = adjusted_coors;
        }
        write_path(board_layer.name, wire_width, coors, p_identifier_type, p_file);
        write_fixed_state(p_file, p_wire.get_fixed_state());
        p_file.end_scope();
    }
    
    private static void write_via(Via p_via, BasicBoard p_board, IdentifierType p_identifier_type,
            CoordinateTransform p_coordinate_transform, IndentFileWriter p_file) throws java.io.IOException
    {
        eu.mihosoft.freerouting.library.Padstack via_padstack = p_via.get_padstack();
        FloatPoint via_location = p_via.get_center().to_float();
        p_file.start_scope();
        p_file.write("via ");
        p_identifier_type.write(via_padstack.name, p_file);
        p_file.write(" ");
        double[] location = p_coordinate_transform.board_to_dsn(via_location);
        Integer x_coor = (int) Math.round(location[0]);
        p_file.write(x_coor.toString());
        p_file.write(" ");
        Integer y_coor = (int) Math.round(location[1]);
        p_file.write(y_coor.toString());
        write_fixed_state(p_file, p_via.get_fixed_state());
        p_file.end_scope();
    }
    
    static private void write_fixed_state(IndentFileWriter p_file, eu.mihosoft.freerouting.board.FixedState p_fixed_state) throws java.io.IOException
    {
        if (p_fixed_state.ordinal() <= eu.mihosoft.freerouting.board.FixedState.SHOVE_FIXED.ordinal())
        {
            return;
        }
        p_file.new_line();
        p_file.write("(type ");
        if (p_fixed_state == eu.mihosoft.freerouting.board.FixedState.SYSTEM_FIXED)
        {
            p_file.write("fix)");
        }
        else
        {
            p_file.write("protect)");
        }
    }
    
    private static void write_path(String p_layer_name, int p_width, int[] p_coors, IdentifierType p_identifier_type,
            IndentFileWriter p_file)
            throws java.io.IOException
    {
        p_file.start_scope();
        p_file.write("path ");
        p_identifier_type.write(p_layer_name, p_file);
        p_file.write(" ");
        p_file.write((Integer.valueOf(p_width)).toString());
        int corner_count = p_coors.length/ 2;
        for (int i = 0; i < corner_count; ++i)
        {
            p_file.new_line();
            p_file.write(Integer.valueOf(p_coors[2 * i]).toString());
            p_file.write(" ");
            p_file.write(Integer.valueOf(p_coors[2 * i + 1]).toString());
        }
        p_file.end_scope();
    }
    
    private static void write_conduction_area( ConductionArea p_conduction_area, BasicBoard p_board,
            IdentifierType p_identifier_type, CoordinateTransform p_coordinate_transform,
            IndentFileWriter p_file) throws java.io.IOException
    {
        int net_count = p_conduction_area.net_count();
        if (net_count <= 0 || net_count > 1)
        {
            FRLogger.warn("SessionFile.write_conduction_area: unexpected net count");
            return;
        }
        eu.mihosoft.freerouting.geometry.planar.Area curr_area = p_conduction_area.get_area();
        int layer_no = p_conduction_area.get_layer();
        eu.mihosoft.freerouting.board.Layer board_layer = p_board.layer_structure.arr[ layer_no];
        Layer conduction_layer = new Layer(board_layer.name, layer_no, board_layer.is_signal);
        eu.mihosoft.freerouting.geometry.planar.Shape boundary_shape;
        eu.mihosoft.freerouting.geometry.planar.Shape [] holes;
        if (curr_area instanceof eu.mihosoft.freerouting.geometry.planar.Shape)
        {
            boundary_shape = (eu.mihosoft.freerouting.geometry.planar.Shape) curr_area;
            holes = new eu.mihosoft.freerouting.geometry.planar.Shape [0];
        }
        else
        {
            boundary_shape = curr_area.get_border();
            holes = curr_area.get_holes();
        }
        p_file.start_scope();
        p_file.write("wire ");
        Shape dsn_shape = p_coordinate_transform.board_to_dsn(boundary_shape, conduction_layer);
        if (dsn_shape != null)
        {
            dsn_shape.write_scope_int(p_file, p_identifier_type);
        }
        for (int i = 0; i < holes.length; ++i)
        {
            Shape dsn_hole = p_coordinate_transform.board_to_dsn(holes[i], conduction_layer);
            dsn_hole.write_hole_scope(p_file, p_identifier_type);
        }
        p_file.end_scope();
    }
}