Instead of posting actual code, I think I'll just describe the logic I used.

First of all, this code was designed to use a different format of autotiles, like this:

5 tiles placed horizontally: full tile, top & bottom border, left & right border, outside corners, inside corners.

Code:
// Bit flags for sides and corners.
const SIDE_TOP = 1; SIDE_BOT = 2; SIDE_LEF = 4; SIDE_RIG = 8;
        COR_TL = 1; COR_TR = 2; COR_BL = 4; COR_BR = 8;

// I assume map to just hold integers with tile types
Procedure DrawMapTile(X,Y : Integer; Gfx : whatever)
Var Edges, Corners : Integer;
begin
Edges := 15; Corners := 15; // All flags set means that autotile is stranded and we have to draw borders.
If (Y = 0) or (Map[X][Y-1] = Map[X][Y]) then Edges -= SIDE_TOP; // We mark top side as no-border
If (Y = (MapH-1)) or (Map[X][Y+1] = Map[X][Y]) then Edges -= SIDE_BOTTOM; // We mark bottom side as no-border
// The same for left
// The same for right
If (X = 0) or (Y = 0) or (Map[X-1][Y-1] = Map[X][Y]) then Corners -= COR_TL; // We mark top-left corner as the same type
// Same for top-right
// Same for bottom-left
// Same for bottom-right
Case Edges of
00: begin // 00 means that all sides are connected. We either have to draw a full tile or an all inside-corners tile.
    DrawFullTile(X,Y);
    DrawCorner(X,Y,Corners,COR_TL);
    DrawCorner(X,Y,Corners,COR_TR);
    DrawCorner(X,Y,Corners,COR_BL);
    DrawCorner(X,Y,Corners,COR_BR)
    end;
01: begin // 01 means that there is a border on the bottom side.
    DrawBorder(X,Y,Edges,SIDE_TOP);
    DrawBorder(X,Y,Edges,SIDE_BOT);
    DrawCorner(X,Y,COR_TL);
    DrawCorner(X,Y,COR_TR);
    end;
Et cetera, et cetera. Edges can be 00-15, so you have 16 cases. DrawBorder() should check if the appropriate edge (SIDE_XXX) flag is set in Edges, and either draw a border or draw the connecting part of tile. DrawCorner() behaves the same way.

This approach requires much copy-pasta and introduces some over-draw, but I've found the easiest to implement in terms of logic.