package Padre::MimeTypes;

=pod

=head1 NAME

Padre::MimeTypes - Padre Mime-types

=head1 DESCRIPTION

See L<Padre::Document>

=head1 METHODS

=cut

use 5.008;
use strict;
use warnings;
use Carp           ();
use File::Basename ();
use Padre::Config  ();
use Padre::Current ();
use Padre::Util    ('_T');
use Padre::Wx      ();
use Padre::DB      ();

our $VERSION = '0.86';

# Binary file extensions, which we don't support loading at all
my %EXT_BINARY = ();

# Text file extension to MIME type mapping (either string or code reference)
my %EXT_MIME = ();

# The list of available syntax highlighting modules
my %HIGHLIGHTER = ();

# Highlighters preferences defined in Padre's configuration system
my %HIGHLIGHTER_CONFIG = ();

# Main MIME type database and settings.
# NOTE: This has gotten complex enough it probably needs to be a HASH
#       of objects now.
my %MIME = ();





#####################################################################
# Document Registration

_initialize();

sub _initialize {
	return if %EXT_BINARY; # call it only once

	%EXT_BINARY = map { $_ => 1 } qw{
		aiff  au    avi  bmp  cache  dat   doc  docx gif  gz   icns
		jar   jpeg  jpg  m4a  mov    mp3   mpg  ogg  pdf  png
		pnt   ppt   qt   ra   svg    svgz  svn  swf  tar  tgz
		tif   tiff  wav  xls  xlw    xlsx  zip
	};

	# This is the primary file extension to mime-type mapping
	%EXT_MIME = (
		abc   => 'text/x-abc',
		ada   => 'text/x-adasrc',
		asm   => 'text/x-asm',
		bat   => 'text/x-bat',
		bib   => 'application/x-bibtex',
		bml   => 'application/x-bml',     # dreamwidth file format
		c     => 'text/x-c',
		cc    => 'text/x-c++src',
		cpp   => 'text/x-c++src',
		cxx   => 'text/x-c++src',
		'c++' => 'text/x-c++src',
		hh    => 'text/x-c++src',
		hpp   => 'text/x-c++src',
		hxx   => 'text/x-c++src',
		'h++' => 'text/x-c++src',
		cs    => 'text/x-csharp',
		css   => 'text/css',
		diff  => 'text/x-patch',
		e     => 'text/x-eiffel',
		f     => 'text/x-fortran',
		htm   => 'text/html',
		html  => 'text/html',
		i     => 'text/x-c',              # C code that should not be preprocessed
		ii    => 'text/x-c++src',         # C++ code that should not be preprocessed
		java  => 'text/x-java-source',
		js    => 'application/javascript',
		json  => 'application/json',
		lsp   => 'application/x-lisp',
		lua   => 'text/x-lua',
		mak   => 'text/x-makefile',
		mat   => 'text/x-matlab',
		pas   => 'text/x-pascal',
		pod   => 'text/x-pod',
		php   => 'application/x-php',
		py    => 'text/x-python',
		rb    => 'application/x-ruby',
		sql   => 'text/x-sql',
		tcl   => 'application/x-tcl',
		vbs   => 'text/vbscript',
		patch => 'text/x-patch',
		pl    => \&perl_mime_type,
		plx   => \&perl_mime_type,
		pm    => \&perl_mime_type,
		pmc   => \&perl_mime_type,        # Compiled Perl Module or gimme5's output
		pod   => \&perl_mime_type,
		psgi  => 'application/x-psgi',
		sty   => 'application/x-latex',
		t     => \&perl_mime_type,
		tex   => 'application/x-latex',

		# Lacking a better solution, define our own MIME
		xs => 'text/x-perlxs',
		tt => 'text/x-perltt',

		conf  => 'text/x-config',
		sh    => 'application/x-shellscript',
		ksh   => 'application/x-shellscript',
		txt   => 'text/plain',
		xml   => 'text/xml',
		yml   => 'text/x-yaml',
		yaml  => 'text/x-yaml',
		'4th' => 'text/x-forth',
		pasm  => 'application/x-pasm',
		pir   => 'application/x-pir',

		# See docs/Perl6/Spec/S01-overview.pod for the
		# list of acceptable Perl 6 extensions
		p6  => 'application/x-perl6',
		p6l => 'application/x-perl6',
		p6m => 'application/x-perl6',
		pl6 => 'application/x-perl6',
		pm6 => 'application/x-perl6',
	);

	%HIGHLIGHTER_CONFIG = (
		'application/x-perl' => 'lang_perl5_lexer',
	);

	# This is the mime-type to Scintilla lexer mapping.
	# Lines marked with CONFIRMED indicate that the mime-typehas been checked
	# to confirm that the MIME type is either the official type, or the primary
	# one in use by the relevant language community.

	# name  => Human readable name
	# lexer => The Scintilla lexer to be used
	# class => document class

	# Padre can use Wx::Scintilla's built-in Perl 6 lexer
	my $perl6_scintilla_lexer = Wx::wxSTC_LEX_NULL;
	if ( Padre::Wx::Editor->isa('Wx::ScintillaTextCtrl') ) {
		eval "use Wx::Scintilla";
		unless ($@) {
			no warnings;
			$perl6_scintilla_lexer = $Wx::Scintilla::wxSCINTILLA_LEX_PERL6;
		}
	}
	%MIME = (
		'text/x-abc' => {
			name  => 'ABC',
			lexer => Wx::wxSTC_LEX_NULL,
		},

		'text/x-adasrc' => {
			name  => 'ADA',
			lexer => Wx::wxSTC_LEX_ADA, # CONFIRMED
		},

		'text/x-asm' => {
			name  => 'ASM',
			lexer => Wx::wxSTC_LEX_ASM, # CONFIRMED
		},

		# application/x-msdos-program includes .exe and .com, so don't use it
		# text/x-bat is used in EXT_MIME, application/x-bat was listed here,
		# they need to be the same
		'text/x-bat' => {
			name  => 'BAT',
			lexer => Wx::wxSTC_LEX_BATCH, # CONFIRMED
		},

		'application/x-bibtex' => {
			name  => 'BibTeX',
			lexer => Wx::wxSTC_LEX_NULL,
		},

		'application/x-bml' => {
			name  => 'BML',
			lexer => Wx::wxSTC_LEX_NULL,  #
		},

		'text/x-c' => {
			name  => 'C',
			lexer => Wx::wxSTC_LEX_CPP,
		},

		'text/x-c++src' => {
			name  => 'C++',
			lexer => Wx::wxSTC_LEX_CPP,   # CONFIRMED
		},

		'text/css' => {
			name  => 'CSS',
			lexer => Wx::wxSTC_LEX_CSS,   # CONFIRMED
		},

		'text/x-eiffel' => {
			name  => 'Eiffel',
			lexer => Wx::wxSTC_LEX_EIFFEL, # CONFIRMED
		},

		'text/x-forth' => {
			name  => 'Forth',
			lexer => Wx::wxSTC_LEX_FORTH,  # CONFIRMED
		},

		'text/x-fortran' => {
			name  => 'Fortran',
			lexer => Wx::wxSTC_LEX_FORTRAN, # CONFIRMED
		},

		'text/html' => {
			name  => 'HTML',
			lexer => Wx::wxSTC_LEX_HTML,    # CONFIRMED
		},

		'application/javascript' => {
			name  => 'JavaScript',
			lexer => Wx::wxSTC_LEX_ESCRIPT, # CONFIRMED
		},

		'application/json' => {
			name  => 'JSON',
			lexer => Wx::wxSTC_LEX_ESCRIPT, # CONFIRMED
		},

		'application/x-latex' => {
			name  => 'LaTeX',
			lexer => Wx::wxSTC_LEX_LATEX,   # CONFIRMED
		},

		'application/x-lisp' => {
			name  => 'LISP',
			lexer => Wx::wxSTC_LEX_LISP,    # CONFIRMED
		},

		'text/x-patch' => {
			name  => 'Patch',
			lexer => Wx::wxSTC_LEX_DIFF,    # CONFIRMED
		},

		'application/x-shellscript' => {
			name  => _T('Shell Script'),
			lexer => Wx::wxSTC_LEX_BASH,
		},

		'text/x-java-source' => {
			name  => 'Java',
			lexer => Wx::wxSTC_LEX_NULL,
		},

		'text/x-lua' => {
			name  => 'Lua',
			lexer => Wx::wxSTC_LEX_LUA, # CONFIRMED
		},

		'text/x-makefile' => {
			name  => 'Makefile',
			lexer => Wx::wxSTC_LEX_MAKEFILE, # CONFIRMED
		},

		'text/x-matlab' => {
			name  => 'Matlab',
			lexer => Wx::wxSTC_LEX_MATLAB,   # CONFIRMED
		},

		'text/x-pascal' => {
			name  => 'Pascal',
			lexer => Wx::wxSTC_LEX_PASCAL,   # CONFIRMED
		},

		'application/x-perl' => {
			name  => 'Perl 5',
			lexer => Wx::wxSTC_LEX_PERL,     # CONFIRMED
			class => 'Padre::Document::Perl',
		},

		'application/x-psgi' => {
			name  => 'PSGI',
			lexer => Wx::wxSTC_LEX_PERL,     # CONFIRMED
		},

		'text/x-python' => {
			name  => 'Python',
			lexer => Wx::wxSTC_LEX_PYTHON,   # CONFIRMED
		},

		'application/x-php' => {
			name  => 'PHP',
			lexer => Wx::wxSTC_LEX_PHPSCRIPT, # CONFIRMED
		},

		'application/x-ruby' => {
			name  => 'Ruby',
			lexer => Wx::wxSTC_LEX_RUBY,      # CONFIRMED
		},

		'text/x-sql' => {
			name  => 'SQL',
			lexer => Wx::wxSTC_LEX_SQL,       # CONFIRMED
		},

		'application/x-tcl' => {
			name  => 'Tcl',
			lexer => Wx::wxSTC_LEX_TCL,       # CONFIRMED
		},

		'text/vbscript' => {
			name  => 'VBScript',
			lexer => Wx::wxSTC_LEX_VBSCRIPT,  # CONFIRMED
		},

		'text/x-config' => {
			name  => 'Config',
			lexer => Wx::wxSTC_LEX_CONF,
			class => 'Padre::Document::Config',
		},

		# text/xml specifically means "human-readable XML".
		# This is prefered to the more generic application/xml
		'text/xml' => {
			name  => 'XML',
			lexer => Wx::wxSTC_LEX_XML, # CONFIRMED
		},

		'text/x-yaml' => {
			name  => 'YAML',
			lexer => Wx::wxSTC_LEX_YAML, # CONFIRMED
		},

		'application/x-pir' => {
			name  => 'PIR',
			lexer => Wx::wxSTC_LEX_NULL, # CONFIRMED
		},

		'application/x-pasm' => {
			name  => 'PASM',
			lexer => Wx::wxSTC_LEX_NULL, # CONFIRMED
		},

		'application/x-perl6' => {
			name  => 'Perl 6',
			lexer => $perl6_scintilla_lexer, # CONFIRMED
		},

		'text/plain' => {
			name  => _T('Text'),
			lexer => Wx::wxSTC_LEX_NULL,     # CONFIRMED
		},

		# Completely custom mime types
		'text/x-perlxs' => {                 # totally not confirmed
			name => 'XS',
			lexer =>
				Wx::wxSTC_LEX_CPP,           # for the lack of a better XS lexer (vim?)
		},
		'text/x-perltt' => {
			name  => 'Template Toolkit',
			lexer => Wx::wxSTC_LEX_HTML,
		},

		'text/x-csharp' => {
			name  => 'C#',
			lexer => Wx::wxSTC_LEX_CPP,      # better than nothing
		},

	);

	# TO DO:
	# add some mime-type for pod files
	# or remove the whole Padre::Document::POD class as it is not in use
	#'text/x-pod'         => 'Padre::Document::POD',

	# Array ref of objects with value and mime_type fields that have the raw values
	__PACKAGE__->load_highlighter_config;

	__PACKAGE__->add_highlighter(
		'stc', _T('Scintilla'),
		_T('Fast but might be out of date')
	);

	foreach my $mime ( keys %MIME ) {
		__PACKAGE__->add_highlighter_to_mime_type( $mime, 'stc' );
	}

	__PACKAGE__->add_highlighter(
		'stc', _T('Scintilla'),
		_T('Fast but might be out of date')
	);

	# Perl 5 specific highlighters
	__PACKAGE__->add_highlighter(
		'Padre::Document::Perl::Lexer',
		_T('PPI Experimental'),
		_T('Slow but accurate and we have full control so bugs can be fixed')
	);
	__PACKAGE__->add_highlighter(
		'Padre::Document::Perl::PPILexer',
		_T('PPI Standard'),
		_T('Hopefully faster than the PPI Traditional. Big file will fall back to Scintilla highlighter.')
	);

	__PACKAGE__->add_highlighter_to_mime_type(
		'application/x-perl',
		'Padre::Document::Perl::Lexer'
	);
	__PACKAGE__->add_highlighter_to_mime_type(
		'application/x-perl',
		'Padre::Document::Perl::PPILexer'
	);
}

sub get_lexer {
	$MIME{ $_[1] }->{lexer};
}

# TO DO: Set some reasonable default highlighers for each mime-type for when there
# are no plugins. e.g. For Perl 6 style files that should be plain text.
# Either allow the plugins to set the defaults (maybe allow the plugin that implements
# the special features of this mime-type to pick the default or shall we have a list of
# prefered default values ?

sub add_mime_class {
	my $class      = shift;
	my $type       = shift;
	my $mime_class = shift;
	if ( not $MIME{$type} ) {
		Padre::Current->main->error(
			sprintf(
				Wx::gettext("Mime type was not supported when %s(%s) was called"),
				'add_mime_class',
				$type
			)
		);
		return;
	}

	if ( $MIME{$type}->{class} ) {
		Padre::Current->main->error(
			sprintf(
				Wx::gettext("Mime type already had a class '%s' when %s(%s) was called"),
				$MIME{$type}->{class},
				'add_mime_class',
				$type
			)
		);
		return;
	}
	$MIME{$type}->{class} = $mime_class;
}

sub remove_mime_class {
	my $class = shift;
	my $type  = shift;

	if ( not $MIME{$type} ) {
		Padre::Current->main->error(
			sprintf(
				Wx::gettext("Mime type is not supported when %s(%s) was called"),
				'remove_mime_class',
				$type
			)
		);
		return;
	}

	if ( not $MIME{$type}->{class} ) {
		Padre::Current->main->error(
			sprintf(
				Wx::gettext("Mime type did not have a class entry when %s(%s) was called"),
				'remove_mime_class',
				$type
			)
		);
		return;
	}
	delete $MIME{$type}->{class};
}

sub get_mime_class {
	my $class = shift;
	my $type  = shift;

	if ( not $MIME{$type} ) {
		Padre::Current->main->error(
			sprintf(
				Wx::gettext("Mime type is not supported when %s(%s) was called"),
				'get_mime_class',
				$type
			)
		);
		return;
	}

	return $MIME{$type}->{class};
}

sub add_highlighter {
	my $class       = shift;
	my $module      = shift;
	my $human       = shift;
	my $explanation = shift || '';

	if ( not defined $human ) {
		Carp::Cluck("human name not defined for '$module'\n");
		return;
	}
	$HIGHLIGHTER{$module} = {
		name        => $human,
		explanation => $explanation,
	};
}

sub get_highlighter_explanation {
	my $class = shift;
	my $name  = shift;

	my ($highlighter) =
		grep { $HIGHLIGHTER{$_}->{name} eq $name }
		keys %HIGHLIGHTER;
	if ( not $highlighter ) {
		Carp::cluck("Could not find highlighter for '$name'\n");
		return '';
	}
	return Wx::gettext( $HIGHLIGHTER{$highlighter}->{explanation} );
}

sub get_highlighter_name {
	my $class       = shift;
	my $highlighter = shift;

	# TO DO this can happen if the user configureda highlighter but on the next start
	# the highlighter is not available any more
	# we need to handle this situation
	return '' if !defined($highlighter);
	return ''
		if not $HIGHLIGHTER{$highlighter}; # avoid autovivification
	return $HIGHLIGHTER{$highlighter}->{name};
}

# get a hash of mime-type => highlighter
# update the database
sub change_highlighters {
	my $class   = shift;
	my $changed = shift;

	my %mtn = map { $MIME{$_}->{name} => $_ } keys %MIME;
	my %highlighters =
		map { $HIGHLIGHTER{$_}->{name} => $_ }
		keys %HIGHLIGHTER;

	foreach my $name ( keys %$changed ) {
		my $type        = $mtn{$name};
		my $highlighter = $highlighters{ $changed->{$name} };
		Padre::DB::SyntaxHighlight->set_mime_type( $type, $highlighter );
	}

	$class->load_highlighter_config;
}

sub load_highlighter_config {
	my $current_highlighters = Padre::DB::SyntaxHighlight->select || [];

	# Set defaults
	foreach my $type ( keys %MIME ) {
		$MIME{$type}->{current_highlighter} = 'stc';
	}

	# TO DO check if the highlighter is really available
	foreach my $e (@$current_highlighters) {
		if ( defined $e->mime_type ) {
			$MIME{ $e->mime_type }->{current_highlighter} = $e->value;
		}
	}

	# Override with settings that have been moved from the database
	# to the Padre::Config system
	# Can't use Padre::Current here, because we won't have Padre->new yet.
	my $config = Padre::Config->read;
	foreach my $type ( keys %HIGHLIGHTER_CONFIG ) {
		my $method = $HIGHLIGHTER_CONFIG{$type};
		$MIME{$type}->{current_highlighter} = $config->$method();
	}
}

# returns hash of mime_type => highlighter
sub get_current_highlighters {
	map { $_ => $MIME{$_}->{current_highlighter} } keys %MIME;
}

# returns hash-ref of mime_type_name => highlighter_name
sub get_current_highlighter_names {
	my $class = shift;
	my %hash  = ();

	foreach my $type ( keys %MIME ) {
		$hash{ $class->get_mime_type_name($type) } =
			$class->get_highlighter_name( $MIME{$type}->{current_highlighter} );
	}
	return \%hash;
}

sub get_current_highlighter_of_mime_type {
	return $MIME{ $_[1] }->{current_highlighter};
}

sub add_highlighter_to_mime_type {
	my $class  = shift;
	my $mime   = shift;
	my $module = shift; # Or 'stc' to indicate Scintilla

	# TO DO check overwrite, check if it is listed in HIGHLIGHTER_EXPLANATIONS
	$MIME{$mime}->{highlighters}->{$module} = 1;
}

sub remove_highlighter_from_mime_type {
	my $class  = shift;
	my $mime   = shift;
	my $module = shift;

	# TO DO check overwrite
	delete $MIME{$mime}->{highlighters}->{$module};
}

# return the MIME types ordered according to their display name
sub get_mime_types {
	return [
		sort { lc $MIME{$a}->{name} cmp lc $MIME{$b}->{name} }
			keys %MIME
	];
}

# return the display-names of the MIME types ordered according to the display names
sub get_mime_type_names {
	my $class = shift;

	return [ map { $MIME{$_}->{name} } @{ $class->get_mime_types } ]; # Need to be checked with non Western languages

}

# given a MIME type
# return its display name
sub get_mime_type_name {
	my $class = shift;
	my $type = shift || '';
	return Wx::gettext('UNKNOWN')
		if $type eq ''
			or not $MIME{$type}
			or not $MIME{$type}->{name};
	return Wx::gettext( $MIME{$type}->{name} );
}

# given a MIME type
# return the display names of the available highlighters
sub get_highlighters_of_mime_type {
	my $class = shift;
	my $type  = shift;
	my @names = map { __PACKAGE__->get_highlighter_name($_) } sort keys %{ $MIME{$type}->{highlighters} };
	return \@names;
}

# given the display name of a MIME type
# return the display names of the available highlighters
sub get_highlighters_of_mime_type_name {
	my ( $class, $name ) = @_;
	my ($type) =
		grep { $MIME{$_}->{name} eq $name } keys %MIME;
	if ( not $type ) {
		warn "Could not find the MIME type of the display name '$name'\n";
		return []; # return [] to avoid crash
	}
	$class->get_highlighters_of_mime_type($type);
}





#####################################################################
# Bad/Ugly/Broken Methods
# These don't really completely belong in this class, but there's
# currently nowhere better for them. Some break API boundaries...
# NOTE: This is NOT an excuse to invent somewhere new that's just as
# innappropriate just to get them out of here.

sub _guess_mimetype {
	warn join( ',', caller ) . ' called MimeTypes::_guess_mimetype which is depreached, use ::guess_mimetype!';
	return $_[0]->guess_mimetype(@_);
}

sub guess_mimetype {
	my $class = shift;
	my $text  = shift;
	my $file  = shift; # Could be a filename or a Padre::File - object

	my $filename;

	if ( ref($file) ) {
		$filename = $file->{filename};

		# Combining this to one line would check if the method ->mime exists, not the result!
		my $MIME = $file->mime;
		defined($MIME) and return $MIME;

	} else {
		$filename = $file;
		undef $file;
	}


	# Try derive the mime type from the file extension
	if ( $filename and $filename =~ /\.([^.]+)$/ ) {
		my $ext = lc $1;
		if ( $EXT_MIME{$ext} ) {
			if ( ref $EXT_MIME{$ext} ) {
				return $EXT_MIME{$ext}->( $class, $text );
			} else {
				return $EXT_MIME{$ext};
			}
		}
	}

	# Try derive the mime type from the basename
	if ($filename) {
		my $basename = File::Basename::basename($filename);
		if ($basename) {
			return 'text/x-makefile' if $basename =~ /^Makefile\.?/i;
			return 'text/plain'      if $basename =~ /^(changes|changelog)/i;
		}
	}

	# Fall back on deriving the type from the content.
	# Hardcode this for now for the cases that we care about and
	# are obvious.
	if ( defined $text ) {
		my $eval_mime_type = eval {

			# Working on content with malformed/bad UTF-8 chars may drop warnings
			# which just say that there are bad UTF-8 chars in the file currently
			# being checked. Maybe they are no UTF-8 chars at all but just a line
			# of bits and Padre/Perl simply has the wrong point of view (UTF-8),
			# so we drop these warnings:
			local $SIG{__WARN__} = sub {
				print STDERR $_[0] . ' while looking for mime type of $filename'
					unless $_[0] =~ /Malformed UTF\-8 char/;
			};

			# Is this a script of some kind?
			if ( $text =~ /\A#!/ ) {
				return $class->perl_mime_type($text)
					if $text =~ /\A#!.*\bperl6?\b/m;
				return 'application/x-tcl'
					if $text =~ /\A#!.*\bsh\b.*(?:\n.*)?\nexec wish/m;
				return 'application/x-tcl'
					if $text =~ /\A#!.*\bwish\b/m;
				return 'application/x-shellscript'
					if $text =~ /\A#!.*\b(?:z|k|ba|t?c|da)?sh\b/m;
				return 'text/x-python'
					if $text =~ /\A#!.*\bpython\b/m;
				return 'application/x-ruby'
					if $text =~ /\A#!.*\bruby\b/m;
			}

			# YAML will start with a ---
			if ( $text =~ /\A---/ ) {
				return 'text/x-yaml';
			}

			# Try to identify Perl Scripts based on soft criterias as a last resort
			# TO DO: Improve the tests
			SCOPE: {
				my $score = 0;
				if ( $text =~ /(use \w+\:\:\w+.+?\;[\r\n][\r\n.]*){3,}/ ) {
					$score += 2;
				}
				if ( $text =~ /use \w+\:\:\w+.+?\;[\r\n]/ ) { $score += 1; }
				if ( $text =~ /require ([\"\'])[a-zA-Z0-9\.\-\_]+\1\;[\r\n]/ ) {
					$score += 1;
				}
				if ( $text =~ /[\r\n]sub \w+ ?(\(\$*\))? ?\{([\s\t]+\#.+)?[\r\n]/ ) {
					$score += 1;
				}
				if ( $text =~ /\=\~ ?[sm]?\// )  { $score += 1; }
				if ( $text =~ /\bmy [\$\%\@]/ )  { $score += .5; }
				if ( $text =~ /1\;[\r\n]+$/ )    { $score += .5; }
				if ( $text =~ /\$\w+\{/ )        { $score += .5; }
				if ( $text =~ /\bsplit[ \(]\// ) { $score += .5; }
				return $class->perl_mime_type($text) if $score >= 3;
			}

			# Look for Template::Toolkit syntax
			#  - traditional syntax:
			return 'text/x-perltt'
				if $text =~ /\[\%[\+\-\=\~]? (PROCESS|WRAPPER|FOREACH|BLOCK|END|INSERT|INCLUDE)\b .* [\+\-\=\~]?\%\]/;

			#  - default alternate styles (match 2 tags)
			return 'text/x-perltt'
				if $text
					=~ /(\%\%[\+\-\=\~]? (PROCESS|WRAPPER|FOREACH|BLOCK|END|INSERT|INCLUDE)\b .* [\+\-\=\~]?\%\%.*){2}/s;
			return 'text/x-perltt'
				if $text
					=~ /(\[\*[\+\-\=\~]? (PROCESS|WRAPPER|FOREACH|BLOCK|END|INSERT|INCLUDE)\b .* [\+\-\=\~]?\*\].*){2}/s;

			#  - other languages defaults (match 3 tags)
			return 'text/x-perltt'
				if $text
					=~ /(\<([\?\%])[\+\-\=\~]? (PROCESS|WRAPPER|FOREACH|BLOCK|END|INSERT|INCLUDE)\b .* [\+\-\=\~]?\1\>.*){3}/s;
			return 'text/x-perltt'
				if $text
					=~ /(\<\%[\+\-\=\~]? (PROCESS|WRAPPER|FOREACH|BLOCK|END|INSERT|INCLUDE)\b .* [\+\-\=\~]?\>.*){3}/s;
			return 'text/x-perltt'
				if $text
					=~ /(\<\!\-\-[\+\-\=\~]? (PROCESS|WRAPPER|FOREACH|BLOCK|END|INSERT|INCLUDE)\b .* [\+\-\=\~]?\-\-\>.*){3}/s;

			#  - traditional, but lowercase syntax (3 tags)
			return 'text/x-perltt'
				if $text
					=~ /(\[\%[\+\-\=\~]? (PROCESS|WRAPPER|FOREACH|BLOCK|END|INSERT|INCLUDE)\b .* [\+\-\=\~]?\%\].*){3}/si;

			# Try to recognize XHTML
			return 'text/html'
				if $text =~ /\A<\?xml version="\d+\.\d+" encoding=".+"\?>/m
					and $text =~ /^<!DOCTYPE html/m;

			# Try to recognize XML
			return 'text/xml'
				if $text =~ /^<\?xml version="\d+\.\d+"(?: +encoding=".+")?(?: +standalone="(?:yes|no)")?\?>/;

			# Look for HTML (now we can be relatively confident it's not HTML inside Perl)
			if ( $text =~ /\<\/(?:html|body|div|p|table)\>/ ) {

				# Is it Template Toolkit HTML?
				# Only try to text the default [% %]
				if ( $text =~ /\[\%\-?\s+\w+(?:\.\w+)*\s+\-?\%\]/ ) {
					return 'text/x-perltt';
				}
				return 'text/html';
			}

			# Try to detect plain CSS without HTML around it
			return 'text/css'
				if $text !~ /\<\w+\/?\>/
					and $text =~ /^([\.\#]?\w+( [\.\#]?\w+)*)(\,[\s\t\r\n]*([\.\#]?\w+( [\.\#]?\w+)*))*[\s\t\r\n]*\{/;

			# LUA detection
			my $lua_score = 0;
			for ( 'end', 'it', 'in', 'nil', 'repeat', '...', '~=' ) {
				$lua_score += 1.1 if $text =~ /[\s\t]$_[\s\t]/;
			}
			$lua_score += 2.01
				if $text =~ /^[\s\t]?function[\s\t]+\w+[\s\t]*\([\w\,]*\)[\s\t\r\n]+[^\{]/;
			$lua_score -= 5.02 if $text =~ /[\{\}]/; # Not used in lua
			$lua_score += 3.04 if $text =~ /\-\-\[.+?\]\]\-\-/s; # Comment
			return 'text/x-lua' if $lua_score >= 5;

			return '';
		};
		return if $@;
		return $eval_mime_type if $eval_mime_type;
	}

	# Fallback mime-type of new files, should be configurable in the GUI
	# TO DO: Make it configurable in the GUI :)
	unless ($filename) {
		return $class->perl_mime_type($text);
	}

	# Fall back to plain text file
	return 'text/plain';
}

sub perl_mime_type {
	my $class = shift;
	my $text  = shift;

	# Sometimes Perl 6 will look like Perl 5
	# But only do this test if the Perl 6 plugin is enabled.
	if ( $MIME{'application/x-perl6'}->{class} and is_perl6($text) ) {
		return 'application/x-perl6';
	} else {
		return 'application/x-perl';
	}
}

sub mime_type_by_extension {
	$EXT_MIME{ $_[1] };
}

sub get_extensions_by_mime_type {

	# %EXT_MIME holds a mapping of extenions to their mimetypes
	# We may want to know what extensions belong to a mimetype:
	my $class    = shift;
	my $mimetype = shift;

	my @extensions;
	while ( my ( $key, $value ) = each(%EXT_MIME) ) {

		# this is just so bad, but to be honest I have no idea
		# how else to do this :(
		$value = 'application/x-perl'
			if ( ref($value) eq 'CODE' ); # assume the hash holds a code ref for all perl extensions.
		if ( $value eq $mimetype ) {
			push @extensions, $key;
		}

	}
	return @extensions;

}

# naive sub to decide if a piece of code is Perl 6 or Perl 5.
# Perl 6:   use v6; class ..., module ...
# maybe also grammar ...
# but make sure that is real code and not just a comment or doc in some perl 5 code...
sub is_perl6 {
	my $text = shift;

	# empty/undef text is not Perl 6 :)
	return if not $text;

	# Perl 6 POD
	return 1 if $text =~ /^=begin\s+pod/msx;

	# Needed for eg/perl5_with_perl6_example.pod
	return if $text =~ /^=head[12]/msx;

	# =cut is a sure sign for Perl 5 code (moritz++)
	return if $text =~ /^=cut/msx;

	# Special case: If MooseX::Declare is there, then we're in Perl 5 land
	return if $text =~ /^\s*use\s+MooseX::Declare/msx;

	# Perl 6 'use v6;'
	return 1 if $text =~ /^\s*use\s+v6;/msx;

	# One of Perl 6 compilation units
	return 1 if $text =~ /^\s*(?:class|grammar|module|role)\s+\w/msx;

	# Not Perl 6 for sure...
	return;
}

sub menu_view_mimes {
	my %menu_view_mimes = ();
	foreach my $type ( keys %MIME ) {
		my $name = $MIME{$type}->{name};
		if ($name) {
			$menu_view_mimes{$type} = $name;
		}
	}
	return %menu_view_mimes;
}


1;

# Copyright 2008-2011 The Padre development team as listed in Padre.pm.
# LICENSE
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl 5 itself.
