
/*
 * This file is part of pcb2gcode.
 * 
 * Copyright (C) 2009, 2010 Patrick Birnzain <pbirnzain@users.sourceforge.net>
 * 
 * pcb2gcode 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.
 * 
 * pcb2gcode 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 for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with pcb2gcode.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "board.hpp"

typedef pair<string, shared_ptr<Layer> > layer_t;

Board::Board( int _dpi, bool _fill_outline, double _outline_width)
{
        margin = 0.0;
	dpi = _dpi;
	fill_outline = _fill_outline;
	outline_width = _outline_width;
}

double
Board::get_width()
{
	return layers.begin()->second->surface->get_width_in();
}

double
Board::get_height()
{
	return layers.begin()->second->surface->get_height_in();
}

uint
Board::get_dpi()
{
	return dpi;
}

void
Board::prepareLayer( string layername, shared_ptr<LayerImporter> importer, shared_ptr<RoutingMill> manufacturer, bool mirror, bool mirror_absolute )
{
        // see comment for prep_t in board.hpp
        prepared_layers.insert( std::make_pair( layername, make_tuple(importer, manufacturer, mirror, mirror_absolute) ) );
}

void
Board::createLayers()
{
	if( !prepared_layers.size() )
		throw std::logic_error("No layers prepared.");

        // start calculating the minimal board size

	min_x = INFINITY;
	max_x = -INFINITY;
	min_y = INFINITY;
	max_y = -INFINITY;

        // calculate room needed by the PCB traces
        for( map< string, prep_t >::iterator it = prepared_layers.begin(); it != prepared_layers.end(); it++ ) {
                shared_ptr<LayerImporter> importer = it->second.get<0>();
                float t;
                t = importer->get_min_x();
                if(min_x > t) min_x = t;
                t = importer->get_max_x();
                if(max_x < t) max_x = t;
                t = importer->get_min_y();
                if(min_y > t) min_y = t;
                t = importer->get_max_y();
                if(max_y < t) max_y = t;
        }

        // if there's no pcb outline, add the specified margins
        try {
		shared_ptr<RoutingMill> outline_mill = prepared_layers.at("outline").get<1>();
		ivalue_t radius = outline_mill->tool_diameter / 2;
		min_x -= radius;
		max_x += radius;
		min_y -= radius;
		max_y += radius;
        }
	catch( std::logic_error& e ) {
                min_x -= margin;
                max_x += margin;
                min_y -= margin;
                max_y += margin;
        }

        // board size calculated. create layers
        for( map<string, prep_t>::iterator it = prepared_layers.begin(); it != prepared_layers.end(); it++ ) {
		// prepare the surface
                shared_ptr<Surface> surface( new Surface(dpi, min_x, max_x, min_y, max_y) );
                shared_ptr<LayerImporter> importer = it->second.get<0>();
                surface->render(importer);

		shared_ptr<Layer> layer( new Layer(it->first, surface, it->second.get<1>(), it->second.get<2>(), it->second.get<3>() ) ); // see comment for prep_t in board.hpp
                
		layers.insert( std::make_pair( layer->get_name(), layer ) );
        }

        // DEBUG output
        BOOST_FOREACH( layer_t layer, layers ) {
                layer.second->surface->save_debug_image(string("original_")+layer.second->get_name());
        }

	// mask layers with outline
	if( prepared_layers.find("outline") != prepared_layers.end()) {
		shared_ptr<Layer> outline_layer = layers.at("outline");

		if(fill_outline) {
			outline_layer->surface->fill_outline(outline_width);
		}

		for (map<string, shared_ptr<Layer> >::iterator it = layers.begin(); it != layers.end(); it++ ) {
			if(it->second != outline_layer) {
				it->second->add_mask(outline_layer);
				it->second->surface->save_debug_image("masked");
			}
		}
	}
}

vector< shared_ptr<icoords> >
Board::get_toolpath( string layername )
{
	vector< shared_ptr<icoords> > toolpath;

	try {
		return layers[layername]->get_toolpaths();
	} catch ( std::logic_error& e ) {
		std::stringstream msg;
		msg << "class Board: get_toolpath(): layer not available: ";
		msg << layername << std::endl;
		throw std::logic_error( msg.str() );
	}
}

vector< string >
Board::list_layers()
{
        vector<string> layerlist;

        BOOST_FOREACH( layer_t layer, layers ) {
                layerlist.push_back( layer.first );
        }

        return layerlist;
}

shared_ptr<Layer>
Board::get_layer( string layername )
{
	return layers.at(layername);
}
