/*
* svg2poly.ulp
* Jan 2013, By Cruz Monrreal II (Cruz.Monrreal@gmail.com)
*
* Imports Plain .svg file and will draw it as a set of polygons
* For best results, follow GitHub instructions as closely as
* possible.
*
*
* NOTE: This is not a universal .svg converter. The spec is
*       simply too large, and the .ulp language is not robust
*       enough to create an entire .svg parser.
*
*/

#usage "<b>Import a Plain SVG file to a Polygon</b>\n"
"<p><b>NOTE:</b> Polygons will automatically be centered about the current 'mark'</p>"
"<p>Usage: run svg2poly [ -ratio <i>num</i> ] [ -layer <i>name</i> ] [ -outline <i>thickness</i> ]"
"<p>Options:<br>"
"<table>"
"<tr><td>-ratio <i>num</i></td><td>scale SVG with the given ratio, default 1</td></tr>"
"<tr><td>-layer <i>name</i></td><td>change layer that SVG is drawn on, default tDocu</td></tr>"
"<tr><td>-outline <i>thickness</i> </td><td>draw an outline of the given size, default 0</td></tr>"
"</table>"
"<p><p><b>Example:</b>"
"<p>run svg2poly -ratio 2 -layer tDocu"
"<p>This will import a Plain SVG file, scale it 2x, and draw it on the tDocu layer with no outline"
"<p><p>"
"<small><table>"
"<tr><td><i>Cruz Monrreal</i></td></tr>"
"<tr><td><i>cruz.monrreal@gmail.com</i></td></tr>"
"<tr><td><i>http://anomalousmaker.com</i></td></tr>"
"</table></small>"



/* Inputs, Defaults */
string layer = "tNames";
real outline = 0.0005;
real ratioX = 0.01, ratioY = 0.01;


/* Globals */
string tmp[], master_xml;
int poly_start[], global_pts=0, num_polys=0;
real pts_x[], pts_y[];
real tmp_coords[];
real scaleFactor = 1.0; // SVG defined
string file;	// filename of SVG

/* Output */
string cmd;

/*
* Stack imlemented to get around lack of recursion
*/
string stack = "";

/*** Stack Helper Functions ***/
void push(string s){ stack = (stack == "") ? s : stack + "|" + s; }
string pull()
{
	string tmp;

	if (stack == "")
        // Nothing in stack
		return "";
	else if (strrstr(stack, "|") == -1){
        // Stack only has one element
		tmp = stack;
		stack = "";
		return tmp;
	}

    // Search for the last `|` in string
    // Return string past last `|`
	tmp = strsub(stack, strrstr(stack, "|") + 1);
	stack = strsub(stack, 0, strrstr(stack, "|"));
	return tmp;
}

/*
*  Encodes all necessary information into a single string
*  for stack-based recursion
*/
string pack(string xml, int global_pts)
{
	string tmp;
	sprintf(tmp, "%s^%d", xml, global_pts);
	return tmp;
}

/*** Functions ***/
void print(string s) {printf("%s\n", s);}

/*
*  Sets up the eagle commands for the script.
*/
void setup_cmd()
{
    int i;

    // Search for parameters
    for(i=0; i<argc; i++){
        if (argv[i] == "-ratio"){   // Scale SVG in the X and Y Axis
            if (i+1 < argc){        // Make sure that there's another argument
                if (strstr(argv[i+1], "-") == -1){    // And that it's not a command
                    ratioX = ratioY = strtod(argv[i+1]);
                    i++;
                }
            }
        }else if (argv[i] == "-ratiox"){    //Scale SVG in X Axis
            if (i+1 < argc){
                if (strstr(argv[i+1], "-") == -1)                {
                    ratioX = strtod(argv[i+1]);
                    i++;
                }
            }
        }else if (argv[i] == "-ratioy"){    // Scale SVG in Y Axis
            if (i+1 < argc){
                if (strstr(argv[i+1], "-") == -1){
                    ratioY = strtod(argv[i+1]);
                    i++;
                }
            }
        }else if (argv[i] == "-layer"){     // Change layer that SVG is drawn on
            if (i+1 < argc){
                if (strstr(argv[i+1], "-") == -1){
                    layer = argv[i+1];
                    i++;
                }
            }
        }else if (argv[i] == "-outline"){   // Draw an outline of a given size
            if (i+1 < argc){
                if (strstr(argv[i+1], "-") == -1){
                    outline = strtol(argv[i+1]);
                    i++;
                }
            }
        }
    }

	// Arguments passed to replace defaults
	/*switch (argc){
		case 5:
			outline = strtol(argv[4]);
		case 4:
			layer = argv[3];
		case 3:
			ratioX = strtod(argv[2]);
			ratioY = strtod(argv[1]);
			break;
		case 2:
			ratioX = ratioY = strtod(argv[1]);
	}*/

}

// launch a dialog box, for user input
void userDialog()
{

	int Result = dlgDialog ("SVG to Polygon") 
	{
		dlgHBoxLayout 
		{
			dlgStretch(1);
			dlgLabel("Enter Parameters");
			dlgStretch(1);
		}
		dlgHBoxLayout 
		{
			dlgLabel("File &name:");
			dlgStringEdit(file);
			dlgPushButton("Bro&wse") 
			{
				file = dlgFileOpen("Select a file", "", "*.svg");
			}
		}
		
		int layerNum = 25; // default ( tNames )
		dlgLabel("Choose layer for outline");
		   
		string layerSelectionString;	// build entries for combobox
		string layersArray[];		// array of entries in combobox
		int i = 0;					// counter to keep track of combobox position (layer number isn't necessarily index number)
		board(B) 
		{
			B.layers(L) // this will loop through all used layers in board
			{
				string tempString;
				sprintf(tempString, "%3d %s,", L.number, L.name);
				layerSelectionString += tempString;
				
				// check to see if layer was already defined by commandline
				if (L.name == layer)
				{
					layerNum = i;
				}
				++i;
			}
		} 
		// convert string into array
		strsplit(layersArray, layerSelectionString, ','); 
		
		dlgComboBox(layersArray, layerNum) 
		{
			string tempString[];
			strsplit(tempString, layersArray[layerNum],' '); 
			layer = tempString[2];
			// dlgMessageBox("selected layer " + layer);
		}
		
		dlgLabel("Choose a scale factor");
		dlgHBoxLayout 
		{
			dlgLabel("X");
			dlgRealEdit(ratioX, 0.0, 100.0);
			dlgSpacing(10);
			dlgLabel("Y");
			dlgRealEdit(ratioY, 0.0, 100.0);
			dlgSpacing(10);
		}
		dlgSpacing(10);
		
		dlgLabel("Specify outline width");
		dlgHBoxLayout 
		{
			dlgRealEdit(outline, 0.0, 100.0);
		}
			
		dlgHBoxLayout 
		{
			dlgStretch(1);
			dlgPushButton("+OK")    dlgAccept();
			dlgPushButton("Cancel") dlgReject();
		}
	}; 
	
	// Write it all to the eagle command
	sprintf(cmd, "set wire_bend 2;\nchange layer %s;\nset width %f;\npoly", layer, outline);
}



string matrix_to_translate(string translate){
	// Might need to implement sin & cos...




	return translate;
}

/*
*  Converts svg formatted coordinates to usable peices
*  WARNING: Function assumes string is formatted correctly
*/
int svgcoords_to_coords(string s)
{
	string coords[];
	strsplit(coords, s, ',');
	tmp_coords[0] = strtod(coords[0]);
	tmp_coords[1] = strtod(coords[1]);
/*
	// Lower the point resolution
	sprintf(s, "%2.2f,%2.2f", tmp_coords[0], tmp_coords[1]);
	strsplit(coords, s, ',');
	tmp_coords[0] = strtod(coords[0]);
	tmp_coords[1] = strtod(coords[1]);
*/

	return 0;
}


/*
*  Extract points from a given svg path.
*/
void extract_pts(string pts, string transform)
{

	// look for a scale transformation
	int scalePos = strrstr(transform,"scale");
	if(scalePos >= 0)
	{
		string scaleString = strsub(transform,scalePos+6);
		scaleFactor = strtod(strsub(scaleString,0, strlen(scaleString)-1)); // trim last char ")"
	}
	else 
	{
		scaleFactor = 1.0;
	}
	printf("Scale factor = %f\n",scaleFactor);
	
	string svg_pts[];
	int size = strsplit(svg_pts, pts, ' '),
		new_pts = 0;
	int i = 2;
  int mode;
  

  if(svg_pts[0] == "M"){
    // Coords are Absolute
    mode = 1;

    sprintf(svg_pts[1], "%s,%s", svg_pts[1], svg_pts[2]);

  }else{
    // Coords are Relative
    mode = 0;
  }

  poly_start[global_pts] = 1;
	// Path format:
	//  m x,y x,y L X,Y l x,y .... z
	
  svgcoords_to_coords(svg_pts[1]);
  pts_x[global_pts] = tmp_coords[0] * scaleFactor;
  pts_y[global_pts] = tmp_coords[1] * scaleFactor;
  new_pts++;

  printf("%2d) %2.2f,%2.2f\n", num_polys, tmp_coords[0], tmp_coords[1]);
  
  while (i < size - 1){
    if (svg_pts[i] == "m" || svg_pts[i] == "M")
	{
      if(svg_pts[i] == "M")
	  {
        // Coords are Absolute
        mode = 1;
        sprintf(svg_pts[i], "%s,%s", svg_pts[i], svg_pts[i+1]);
      }else{
        // Coords are Relative
        mode = 0;
      }
	  
		svgcoords_to_coords(svg_pts[++i]);

      if(mode == 0){
        pts_x[global_pts + new_pts] += tmp_coords[0] * scaleFactor;
        pts_y[global_pts + new_pts] += tmp_coords[1] * scaleFactor;
      }else{
        pts_x[global_pts + new_pts] = tmp_coords[0] * scaleFactor;
        pts_y[global_pts + new_pts] = tmp_coords[1] * scaleFactor;
        i++;
      }

      new_pts++;
    }
	
	else if (svg_pts[i] == "L" )
	{
      // Coords are Absolute, command is L
      mode = 1;
    }
	else if (svg_pts[i] == "l")
	{
      // Coords are Offset, command is l
      mode = 0;
    }
	else if (svg_pts[i] == "H")
	{
      // Coords are Absolute, command is H
      mode = 3;
    }
	else if (svg_pts[i] == "h")
	{
      // Coords are Offset, command is h
      mode = 2;
    }
	else if (svg_pts[i] == "V" )
	{
      // Coords are Absolute, command is V
      mode = 5;
    }
	else if (svg_pts[i] == "v")
	{
      // Coords are Offset, command is v
      mode = 4;
    }
	
	// assume numerical coords if none of the above conditions are met
	else
	{
	
      if (mode == 1) 
	  {
        sprintf(svg_pts[i], "%s,%s", svg_pts[i], svg_pts[i+1]);
      }
	  
	  // Line-to command, parse coordinates from text
	  if ((mode == 0) || (mode == 1))
	  {
		svgcoords_to_coords(svg_pts[i]);
	  }
	  
	  // horizontal line, update X only
	  else if ((mode == 2) || (mode == 3))
	  {
		mode -= 2;
		tmp_coords[0] = strtod(svg_pts[i]);
	  }
	  
	  // vertical line, update Y only
	  if ((mode == 4) || (mode == 5))
	  {
		mode -= 4;
		tmp_coords[1] = strtod(svg_pts[i]);
	  }

      if (tmp_coords[0] == 0 && tmp_coords[1] == 0)
	  {
        print("ERROR");
      }
	  else
	  {
        poly_start[global_pts + new_pts] = 0;
        if (mode == 0)
		{
          pts_x[global_pts + new_pts] = pts_x[global_pts + new_pts - 1] + tmp_coords[0] * scaleFactor;
          pts_y[global_pts + new_pts] = pts_y[global_pts + new_pts - 1] + tmp_coords[1] * scaleFactor;
        }
		else if (mode == 1)
		{
          pts_x[global_pts + new_pts] = tmp_coords[0] * scaleFactor;
          pts_y[global_pts + new_pts] = tmp_coords[1] * scaleFactor;
        }

        printf("    %2.2f,%2.2f\n", pts_x[global_pts + new_pts], pts_y[global_pts + new_pts]);
        poly_start[global_pts + new_pts] = 0;
        new_pts++;
      }

    }

    i++;
  }

    // Close the polygon with the starting point
  svgcoords_to_coords(svg_pts[1]);
  pts_x[global_pts + new_pts] = tmp_coords[0] * scaleFactor;
  pts_y[global_pts + new_pts] = tmp_coords[1] * scaleFactor;
  new_pts++;

  global_pts += new_pts;
  num_polys++;
}

/*
*  Traverse through the tree of groups, adding points to paths,
*  while applying group transforms in the process
*/
void extract_nested_data()
{
	string xml[];
	string elements[];
	string tags[];
	int size;

	while(stack != ""){
        // Pop xml to parse
		strsplit(xml, pull(), '^');

        // Find name of root
        string tmp[], root;
        xmltags(tmp, xml[0], "");
        root = tmp[0];

        if (root == "path"){
            // Path found
            print(" PATH " + xmlattribute(xml[0], "path", "id") + " FOUND ");
            extract_pts(xmlattribute(xml[0], "path", "d"), xmlattribute(xml[0], "path", "transform"));
        //}else if (root == ""){
        // Add more cases here
        }else{
            int a = xmltags(tags, xml[0], root);
            for(int i=0; i<a; i++){
                int b = xmlelements(elements, xml[0], root+"/"+tags[i]);

                // Reverse push children
                for(int j=0; j<b; j++){
                    // Push child back to search for more paths
                    push(pack(elements[j], global_pts));
                }
            }
        }
	}
}

/*
*  Writes all activity to a log file, while doing its thing
*/
output("svg2poly.log", "wt")
{
	setup_cmd();	// get info from command line, if any
	userDialog();	// launch user GUI

	// Select file
	//string file = dlgFileOpen("Select a file", "", "*.svg");

	if (file != ""){
		int i=0, j=0;
		string svg[];
		master_xml= "";

		int len = fileread(svg, file);

		// Read file into one massive xml line
		for(; i<len; i++){
			//print(svg[i]);
			master_xml += svg[i];
		}

		// Extract Points and apply transformations
        push(pack(master_xml, global_pts));
		extract_nested_data();

		// Correct Inkscape's Formatting issue
		for(i=0; i<global_pts; i++)
			pts_y[i] *= -1;

		// Center object
		real min_x, min_y,
			max_x, max_y;
		min_x = max_x = pts_x[0];
		min_y = max_y = pts_y[0];

		for(i=1; i<global_pts; i++){
			// Find Min & Max
			if (pts_x[i] < min_x)
				min_x = pts_x[i];
			if (pts_x[i] > max_x)
				max_x = pts_x[i];

			if (pts_y[i] < min_y)
				min_y = pts_y[i];
			if (pts_y[i] > max_y)
				max_y = pts_y[i];
		}

		real x_center = (max_x - min_x)/2,
			y_center = (max_y - min_y)/2;

		// Apply offsets to all points
		for(i=0; i < global_pts; i++){
			pts_x[i] -= min_x + x_center;
			pts_y[i] -= min_y + y_center;
		}
		printf("\nObject origin:\n%f %f\n", x_center, y_center);

		// Generate eagle command
		for(i=0; i<global_pts; i++){
			if (poly_start[i] == 1){
				//printf("%2d) %3.4f %3.4f\n", j++, pts_x[i] * ratioX, pts_y[i] * ratioY);
				if (i != 0)
					sprintf(cmd, "%s;\npoly (R %f %f)", cmd, pts_x[i] * ratioX, pts_y[i] * ratioY);
				else
					sprintf(cmd, "%s (R %f %f)", cmd, pts_x[i] * ratioX, pts_y[i] * ratioY);
			}else{
				//printf("    %3.4f %3.4f\n", pts_x[i] * ratioX, pts_y[i] * ratioY);
				sprintf(cmd, "%s (R %f %f)", cmd, pts_x[i] * ratioX, pts_y[i] * ratioY);
			}
		}

		cmd += ";\n";
		print("\nEagle Command:\n" + cmd);
		exit(cmd);
	}

	exit("");
}
