fix: return 400 if uri is bad

require 'rack'
require 'rack/canonical_host/redirect'
require 'rack/canonical_host/version'

module Rack
  class CanonicalHost
    def initialize(app, host=nil, options={}, &block)
      self.app = app
      self.host = host
      self.options = options
      self.block = block
    end

    def call(env)
      host = evaluate_host(env)
      redirect = Redirect.new(env, host, options)

      if redirect.canonical?
        app.call(env)
      else
        redirect.response
      end
    end

  protected

    attr_accessor :app
    attr_accessor :host
    attr_accessor :options
    attr_accessor :block

  private

    def evaluate_host(env)
      block and block.call(env) or host
    end
  end
end

RSpec.describe Rack::CanonicalHost do
  let(:app_response) { [200, { 'content-type' => 'text/plain' }, %w(OK)] }
  let(:inner_app) { ->(env) { response } }

  before do
    allow(inner_app)
      .to receive(:call)
      .with(env)
      .and_return(app_response)
  end

  def build_app(host=nil, options={}, inner_app=inner_app(), &block)
    Rack::Builder.new do
      use Rack::Lint
      use Rack::CanonicalHost, host, options, &block
      run inner_app
    end
  end

  shared_examples 'a matching request' do
    context 'with a request to a matching host' do
      it { should_not be_redirect }

      it 'calls the inner app' do
        expect(inner_app).to receive(:call).with(env)
        call_app
      end
    end
  end

  shared_examples 'a non-matching request' do
    context 'with a request to a non-matching host' do
      it { should redirect_to('http://example.com/full/path') }

      it 'does not call the inner app' do
        expect(inner_app).to_not receive(:call)
        call_app
      end

      it { expect(response).to_not have_header('cache-control') }
    end
  end

  context '#call' do
    let(:url) { 'http://example.com/full/path' }
    let(:headers) { {} }

    let(:app) { build_app('example.com') }
    let(:env) { Rack::MockRequest.env_for(url, headers) }

    def call_app
      app.call(env)
    end

    subject(:response) { call_app }

    it_behaves_like 'a matching request'

    it_behaves_like 'a non-matching request' do
      let(:url) { 'http://www.example.com/full/path' }
    end

    context 'when the request has a pipe in the URL' do
      let(:url) { 'https://example.com/full/path?value=withPIPE' }

      before do
        env['QUERY_STRING'].sub!('PIPE', '|')
      end

      it { expect { call_app }.to_not raise_error }
    end

    context 'when the request has JavaScript in the URL' do
      let(:url) { 'http://www.example.com/full/path' }

      let(:app) { build_app('example.com') }

      it 'escapes the JavaScript' do
        allow_any_instance_of(Rack::Request).to receive(:query_string).
          and_return '"><script>alert(73541);</script>'

        expect(response)
          .to redirect_to('http://example.com/full/path?%22%3E%3Cscript%3Ealert(73541)%3B%3C/script%3E')
      end
    end

    context 'with an X-Forwarded-Host' do
      let(:url) { 'http://proxy.test/full/path' }

      context 'which matches the canonical host' do
        let(:headers) { { 'HTTP_X_FORWARDED_HOST' => 'example.com:80' } }

        it_behaves_like 'a matching request'
      end

      context 'which does not match the canonical host' do
        let(:headers) { { 'HTTP_X_FORWARDED_HOST' => 'www.example.com:80' } }

        it_behaves_like 'a non-matching request'
      end
    end

    context 'without a host' do
      let(:app) { build_app(nil) }

      it_behaves_like 'a matching request'
    end

    context 'with :ignore option' do
      context 'with lambda/proc' do
        let(:app) {
          build_app(
            'example.com',
            ignore: ->(uri) { uri.host == 'example.net' }
          )
        }

        it_behaves_like 'a matching request'

        it_behaves_like 'a non-matching request' do
          let(:url) { 'http://www.example.com/full/path' }
        end

        context 'with a request to an ignored host' do
          let(:url) { 'http://example.net/full/path' }

          it { should_not be_redirect }

          it 'calls the inner app' do
            expect(inner_app).to receive(:call).with(env)
            call_app
          end
        end
      end

      context 'with string' do
        let(:app) { build_app('example.com', ignore: 'example.net') }

        it_behaves_like 'a matching request'

        it_behaves_like 'a non-matching request' do
          let(:url) { 'http://www.example.com/full/path' }
        end

        context 'with a request to an ignored host' do
          let(:url) { 'http://example.net/full/path' }

          it { should_not be_redirect }

          it 'calls the inner app' do
            expect(inner_app).to receive(:call).with(env)
            call_app
          end
        end
      end

      context 'with regular expression' do
        let(:app) { build_app('example.com', ignore: /ex.*\.net/) }

        it_behaves_like 'a matching request'

        it_behaves_like 'a non-matching request' do
          let(:url) { 'http://www.example.com/full/path' }
        end

        context 'with a request to an ignored host' do
          let(:url) { 'http://example.net/full/path' }

          it { should_not be_redirect }

          it 'calls the inner app' do
            expect(inner_app).to receive(:call).with(env)
            call_app
          end
        end
      end
    end

    context 'with :if option' do
      context 'with a lambda/proc' do
        let(:app) {
          build_app(
            'www.example.com',
            if: ->(uri) { uri.host == 'example.com' }
          )
        }

        context 'with a request to a matching host' do
          let(:url) { 'http://example.com/full/path' }

          it { should redirect_to('http://www.example.com/full/path') }
        end

        context 'with a request to a non-matching host' do
          let(:url) { 'http://api.example.com/full/path' }

          it { should_not be_redirect }
        end
      end

      context 'with string' do
        let(:app) { build_app('www.example.com', if: 'example.com') }

        context 'with a request to a matching host' do
          let(:url) { 'http://example.com/full/path' }

          it { should redirect_to('http://www.example.com/full/path') }
        end

        context 'with a request to a non-matching host' do
          let(:url) { 'http://api.example.com/full/path' }

          it { should_not be_redirect }
        end
      end

      context 'with a regular expression' do
        let(:app) { build_app('example.com', if: '.*\.example\.com') }

        context 'with a request to a matching host' do
          let(:url) { 'http://www.example.com/full/path' }

          it { should_not redirect_to('http://example.com/full/path') }
        end

        context 'with a request to a non-matching host' do
          let(:url) { 'http://www.example.net/full/path' }

          it { should_not be_redirect }
        end
      end
    end

    context 'with a :cache_control option' do
      let(:url) { 'http://subdomain.example.net/full/path' }

      context 'with a max-age value' do
        let(:app) {
          build_app('example.com', cache_control: 'max-age=3600')
        }

        it {
          expect(response).to have_header('cache-control').with('max-age=3600')
        }
      end

      context 'with a no-cache value' do
        let(:app) { build_app('example.com', cache_control: 'no-cache') }

        it { expect(subject).to have_header('cache-control').with('no-cache') }
      end

      context 'with a false value' do
        let(:app) { build_app('example.com', cache_control: false) }

        it { expect(subject).to_not have_header('cache-control') }
      end

      context 'with a nil value' do
        let(:app) { build_app('example.com', cache_control: false) }

        it { expect(subject).to_not have_header('cache-control') }
      end
    end

    context 'with a block' do
      let(:app) { build_app { 'example.com' } }

      it_behaves_like 'a matching request'

      it_behaves_like 'a non-matching request' do
        let(:url) { 'http://www.example.com/full/path' }
      end

      context 'that returns nil' do
        let(:app) { build_app('example.com') { nil } }

        it_behaves_like 'a matching request'

        it_behaves_like 'a non-matching request' do
          let(:url) { 'http://www.example.com/full/path' }
        end
      end
    end
  end
end

require 'rack'
require 'rack/canonical_host/redirect'
require 'rack/canonical_host/version'

module Rack
  class CanonicalHost
    def initialize(app, host=nil, options={}, &block)
      self.app = app
      self.host = host
      self.options = options
      self.block = block
    end

    def call(env)
      host = evaluate_host(env)
      redirect = Redirect.new(env, host, options)

      begin
        return redirect.response unless redirect.canonical?
      rescue Addressable::URI::InvalidURIError
        return [400, { Rack::CONTENT_TYPE => "text/plain", Rack::CONTENT_LENGTH => "0" }, []]
      end

      app.call(env)
    end

  protected

    attr_accessor :app
    attr_accessor :host
    attr_accessor :options
    attr_accessor :block

  private

    def evaluate_host(env)
      block and block.call(env) or host
    end
  end
end

RSpec.describe Rack::CanonicalHost do
  let(:app_response) { [200, { 'content-type' => 'text/plain' }, %w(OK)] }
  let(:inner_app) { ->(env) { response } }

  before do
    allow(inner_app)
      .to receive(:call)
      .with(env)
      .and_return(app_response)
  end

  def build_app(host=nil, options={}, inner_app=inner_app(), &block)
    Rack::Builder.new do
      use Rack::Lint
      use Rack::CanonicalHost, host, options, &block
      run inner_app
    end
  end

  shared_examples 'a matching request' do
    context 'with a request to a matching host' do
      it { should_not be_redirect }

      it 'calls the inner app' do
        expect(inner_app).to receive(:call).with(env)
        call_app
      end
    end
  end

  shared_examples 'a non-matching request' do
    context 'with a request to a non-matching host' do
      it { should redirect_to('http://example.com/full/path') }

      it 'does not call the inner app' do
        expect(inner_app).to_not receive(:call)
        call_app
      end

      it { expect(response).to_not have_header('cache-control') }
    end
  end

  context '#call' do
    let(:url) { 'http://example.com/full/path' }
    let(:headers) { {} }

    let(:app) { build_app('example.com') }
    let(:env) { Rack::MockRequest.env_for(url, headers) }

    def call_app
      app.call(env)
    end

    subject(:response) { call_app }

    it_behaves_like 'a matching request'

    it_behaves_like 'a non-matching request' do
      let(:url) { 'http://www.example.com/full/path' }
    end

    context 'when the request has a pipe in the URL' do
      let(:url) { 'https://example.com/full/path?value=withPIPE' }

      before do
        env['QUERY_STRING'].sub!('PIPE', '|')
      end

      it { expect { call_app }.to_not raise_error }
    end

    context 'when the request has JavaScript in the URL' do
      let(:url) { 'http://www.example.com/full/path' }

      let(:app) { build_app('example.com') }

      it 'escapes the JavaScript' do
        allow_any_instance_of(Rack::Request).to receive(:query_string).
          and_return '"><script>alert(73541);</script>'

        expect(response)
          .to redirect_to('http://example.com/full/path?%22%3E%3Cscript%3Ealert(73541)%3B%3C/script%3E')
      end
    end

    context 'when the app happens to have an invalid uri error' do
      before do
        allow(inner_app)
          .to receive(:call)
          .with(env)
          .and_raise(Addressable::URI::InvalidURIError)
      end

      it 'explodes as expected' do
        expect { call_app }.to raise_error(Addressable::URI::InvalidURIError)
      end
    end

    context 'with an X-Forwarded-Host' do
      let(:url) { 'http://proxy.test/full/path' }

      context 'which matches the canonical host' do
        let(:headers) { { 'HTTP_X_FORWARDED_HOST' => 'example.com:80' } }

        it_behaves_like 'a matching request'
      end

      context 'which does not match the canonical host' do
        let(:headers) { { 'HTTP_X_FORWARDED_HOST' => 'www.example.com:80' } }

        it_behaves_like 'a non-matching request'
      end

      context 'which is an invalid uri' do
        let(:headers) { { 'HTTP_X_FORWARDED_HOST' => '[${jndi:ldap://172.16.26.190:52314/nessus}]/' } }

        it { should_not be_redirect }

        it { expect(response[0]).to be 400 }

        it 'does not call the inner app' do
          expect(inner_app).to_not receive(:call)
          call_app
        end

        it { expect(response).to_not have_header('cache-control') }
      end
    end

    context 'without a host' do
      let(:app) { build_app(nil) }

      it_behaves_like 'a matching request'
    end

    context 'with :ignore option' do
      context 'with lambda/proc' do
        let(:app) {
          build_app(
            'example.com',
            ignore: ->(uri) { uri.host == 'example.net' }
          )
        }

        it_behaves_like 'a matching request'

        it_behaves_like 'a non-matching request' do
          let(:url) { 'http://www.example.com/full/path' }
        end

        context 'with a request to an ignored host' do
          let(:url) { 'http://example.net/full/path' }

          it { should_not be_redirect }

          it 'calls the inner app' do
            expect(inner_app).to receive(:call).with(env)
            call_app
          end
        end
      end

      context 'with string' do
        let(:app) { build_app('example.com', ignore: 'example.net') }

        it_behaves_like 'a matching request'

        it_behaves_like 'a non-matching request' do
          let(:url) { 'http://www.example.com/full/path' }
        end

        context 'with a request to an ignored host' do
          let(:url) { 'http://example.net/full/path' }

          it { should_not be_redirect }

          it 'calls the inner app' do
            expect(inner_app).to receive(:call).with(env)
            call_app
          end
        end
      end

      context 'with regular expression' do
        let(:app) { build_app('example.com', ignore: /ex.*\.net/) }

        it_behaves_like 'a matching request'

        it_behaves_like 'a non-matching request' do
          let(:url) { 'http://www.example.com/full/path' }
        end

        context 'with a request to an ignored host' do
          let(:url) { 'http://example.net/full/path' }

          it { should_not be_redirect }

          it 'calls the inner app' do
            expect(inner_app).to receive(:call).with(env)
            call_app
          end
        end
      end
    end

    context 'with :if option' do
      context 'with a lambda/proc' do
        let(:app) {
          build_app(
            'www.example.com',
            if: ->(uri) { uri.host == 'example.com' }
          )
        }

        context 'with a request to a matching host' do
          let(:url) { 'http://example.com/full/path' }

          it { should redirect_to('http://www.example.com/full/path') }
        end

        context 'with a request to a non-matching host' do
          let(:url) { 'http://api.example.com/full/path' }

          it { should_not be_redirect }
        end
      end

      context 'with string' do
        let(:app) { build_app('www.example.com', if: 'example.com') }

        context 'with a request to a matching host' do
          let(:url) { 'http://example.com/full/path' }

          it { should redirect_to('http://www.example.com/full/path') }
        end

        context 'with a request to a non-matching host' do
          let(:url) { 'http://api.example.com/full/path' }

          it { should_not be_redirect }
        end
      end

      context 'with a regular expression' do
        let(:app) { build_app('example.com', if: '.*\.example\.com') }

        context 'with a request to a matching host' do
          let(:url) { 'http://www.example.com/full/path' }

          it { should_not redirect_to('http://example.com/full/path') }
        end

        context 'with a request to a non-matching host' do
          let(:url) { 'http://www.example.net/full/path' }

          it { should_not be_redirect }
        end
      end
    end

    context 'with a :cache_control option' do
      let(:url) { 'http://subdomain.example.net/full/path' }

      context 'with a max-age value' do
        let(:app) {
          build_app('example.com', cache_control: 'max-age=3600')
        }

        it {
          expect(response).to have_header('cache-control').with('max-age=3600')
        }
      end

      context 'with a no-cache value' do
        let(:app) { build_app('example.com', cache_control: 'no-cache') }

        it { expect(subject).to have_header('cache-control').with('no-cache') }
      end

      context 'with a false value' do
        let(:app) { build_app('example.com', cache_control: false) }

        it { expect(subject).to_not have_header('cache-control') }
      end

      context 'with a nil value' do
        let(:app) { build_app('example.com', cache_control: false) }

        it { expect(subject).to_not have_header('cache-control') }
      end
    end

    context 'with a block' do
      let(:app) { build_app { 'example.com' } }

      it_behaves_like 'a matching request'

      it_behaves_like 'a non-matching request' do
        let(:url) { 'http://www.example.com/full/path' }
      end

      context 'that returns nil' do
        let(:app) { build_app('example.com') { nil } }

        it_behaves_like 'a matching request'

        it_behaves_like 'a non-matching request' do
          let(:url) { 'http://www.example.com/full/path' }
        end
      end
    end
  end
end


fix style conflict

var gulp = require('gulp');
var uglify = require("gulp-uglify");
var uglifycss = require("gulp-uglifycss");
var rename = require('gulp-rename');
var changelog = require('gulp-conventional-changelog');

gulp.task('min', function() {
  gulp.src('jquery.modal.js')
    .pipe(uglify({preserveComments: 'license'}))
    .pipe(rename('jquery.modal.min.js'))
    .pipe(gulp.dest('.'));
});

gulp.task('css', function() {
  gulp.src('jquery.modal.css')
    .pipe(uglifycss())
    .pipe(rename('jquery.modal.min.css'))
    .pipe(gulp.dest('.'));
});

gulp.task('changelog', function() {
  gulp.src('CHANGELOG.md')
    .pipe(changelog())
    .pipe(gulp.dest('.'));
});

.blocker {
  position: fixed;
  top: 0; right: 0; bottom: 0; left: 0;
  width: 100%; height: 100%;
  overflow: auto;
  z-index: 1;
  padding: 20px;
  box-sizing: border-box;
  background-color: rgb(0,0,0);
  background-color: rgba(0,0,0,0.75);
  text-align: center;
}
.blocker:before{
  content: "";
  display: inline-block;
  height: 100%;
  vertical-align: middle;
  margin-right: -0.05em;
}
.blocker.behind {
  background-color: transparent;
}
.modal {
  display: none;
  vertical-align: middle;
  position: relative;
  z-index: 2;
  max-width: 500px;
  box-sizing: border-box;
  width: 90%;
  background: #fff;
  padding: 15px 30px;
  -webkit-border-radius: 8px;
  -moz-border-radius: 8px;
  -o-border-radius: 8px;
  -ms-border-radius: 8px;
  border-radius: 8px;
  -webkit-box-shadow: 0 0 10px #000;
  -moz-box-shadow: 0 0 10px #000;
  -o-box-shadow: 0 0 10px #000;
  -ms-box-shadow: 0 0 10px #000;
  box-shadow: 0 0 10px #000;
  text-align: left;
}

.modal a.close-modal {
  position: absolute;
  top: -12.5px;
  right: -12.5px;
  display: block;
  width: 30px;
  height: 30px;
  text-indent: -9999px;
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center center;
  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAAAXNSR0IArs4c6QAAA3hJREFUaAXlm8+K00Acx7MiCIJH/yw+gA9g25O49SL4AO3Bp1jw5NvktC+wF88qevK4BU97EmzxUBCEolK/n5gp3W6TTJPfpNPNF37MNsl85/vN/DaTmU6PknC4K+pniqeKJ3k8UnkvDxXJzzy+q/yaxxeVHxW/FNHjgRSeKt4rFoplzaAuHHDBGR2eS9G54reirsmienDCTRt7xwsp+KAoEmt9nLaGitZxrBbPFNaGfPloGw2t4JVamSt8xYW6Dg1oCYo3Yv+rCGViV160oMkcd8SYKnYV1Nb1aEOjCe6L5ZOiLfF120EjWhuBu3YIZt1NQmujnk5F4MgOpURzLfAwOBSTmzp3fpDxuI/pabxpqOoz2r2HLAb0GMbZKlNV5/Hg9XJypguryA7lPF5KMdTZQzHjqxNPhWhzIuAruOl1eNqKEx1tSh5rfbxdw7mOxCq4qS68ZTjKS1YVvilu559vWvFHhh4rZrdyZ69Vmpgdj8fJbDZLJpNJ0uv1cnr/gjrUhQMuI+ANjyuwftQ0bbL6Erp0mM/ny8Fg4M3LtdRxgMtKl3jwmIHVxYXChFy94/Rmpa/pTbNUhstKV+4Rr8lLQ9KlUvJKLyG8yvQ2s9SBy1Jb7jV5a0yapfF6apaZLjLLcWtd4sNrmJUMHyM+1xibTjH82Zh01TNlhsrOhdKTe00uAzZQmN6+KW+sDa/JD2PSVQ873m29yf+1Q9VDzfEYlHi1G5LKBBWZbtEsHbFwb1oYDwr1ZiF/2bnCSg1OBE/pfr9/bWx26UxJL3ONPISOLKUvQza0LZUxSKyjpdTGa/vDEr25rddbMM0Q3O6Lx3rqFvU+x6UrRKQY7tyrZecmD9FODy8uLizTmilwNj0kraNcAJhOp5aGVwsAGD5VmJBrWWbJSgWT9zrzWepQF47RaGSiKfeGx6Szi3gzmX/HHbihwBser4B9UJYpFBNX4R6vTn3VQnez0SymnrHQMsRYGTr1dSk34ljRqS/EMd2pLQ8YBp3a1PLfcqCpo8gtHkZFHKkTX6fs3MY0blKnth66rKCnU0VRGu37ONrQaA4eZDFtWAu2fXj9zjFkxTBOo8F7t926gTp/83Kyzzcy2kZD6xiqxTYnHLRFm3vHiRSwNSjkz3hoIzo8lCKWUlg/YtGs7tObunDAZfpDLbfEI15zsEIY3U/x/gHHc/G1zltnAgAAAABJRU5ErkJggg==');

}

.modal-spinner {
  display: none;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translateY(-50%) translateX(-50%);
  padding: 12px 16px;
  border-radius: 5px;
  background-color: #111;
  height: 20px;
}

.modal-spinner > div {
  border-radius: 100px;
  background-color: #fff;
  height: 20px;
  width: 2px;
  margin: 0 1px;
  display: inline-block;

  -webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out;
  animation: sk-stretchdelay 1.2s infinite ease-in-out;
}

.modal-spinner .rect2 {
  -webkit-animation-delay: -1.1s;
  animation-delay: -1.1s;
}

.modal-spinner .rect3 {
  -webkit-animation-delay: -1.0s;
  animation-delay: -1.0s;
}

.modal-spinner .rect4 {
  -webkit-animation-delay: -0.9s;
  animation-delay: -0.9s;
}

@-webkit-keyframes sk-stretchdelay {
  0%, 40%, 100% { -webkit-transform: scaleY(0.5) }
  20% { -webkit-transform: scaleY(1.0) }
}

@keyframes sk-stretchdelay {
  0%, 40%, 100% {
    transform: scaleY(0.5);
    -webkit-transform: scaleY(0.5);
  }  20% {
    transform: scaleY(1.0);
    -webkit-transform: scaleY(1.0);
  }
}

var gulp = require('gulp');
var uglify = require("gulp-uglify");
var uglifycss = require("gulp-uglifycss");
var rename = require('gulp-rename');
var changelog = require('gulp-conventional-changelog');

gulp.task('min', function() {
  gulp.src('jquery.modal.js')
    .pipe(uglify({preserveComments: 'license'}))
    .pipe(rename('jquery.modal.min.js'))
    .pipe(gulp.dest('.'));
});

gulp.task('css', async function() {
  gulp.src('jquery.modal.css')
    .pipe(uglifycss())
    .pipe(rename('jquery.modal.min.css'))
    .pipe(gulp.dest('.'));
});

gulp.task('changelog', function() {
  gulp.src('CHANGELOG.md')
    .pipe(changelog())
    .pipe(gulp.dest('.'));
});

.blocker {
  position: fixed;
  top: 0; right: 0; bottom: 0; left: 0;
  width: 100%; height: 100%;
  overflow: auto;
  z-index: 1;
  padding: 20px;
  box-sizing: border-box;
  background-color: rgb(0,0,0);
  background-color: rgba(0,0,0,0.75);
  text-align: center;
}
.blocker:before{
  content: "";
  display: inline-block;
  height: 100%;
  vertical-align: middle;
  margin-right: -0.05em;
}
.blocker.behind {
  background-color: transparent;
}
.jquery-modal .modal {
  display: none;
  vertical-align: middle;
  position: relative;
  z-index: 2;
  max-width: 500px;
  box-sizing: border-box;
  width: 90%;
  background: #fff;
  padding: 15px 30px;
  -webkit-border-radius: 8px;
  -moz-border-radius: 8px;
  -o-border-radius: 8px;
  -ms-border-radius: 8px;
  border-radius: 8px;
  -webkit-box-shadow: 0 0 10px #000;
  -moz-box-shadow: 0 0 10px #000;
  -o-box-shadow: 0 0 10px #000;
  -ms-box-shadow: 0 0 10px #000;
  box-shadow: 0 0 10px #000;
  text-align: left;
}

.jquery-modal .modal a.close-modal {
  position: absolute;
  top: -12.5px;
  right: -12.5px;
  display: block;
  width: 30px;
  height: 30px;
  text-indent: -9999px;
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center center;
  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAAAXNSR0IArs4c6QAAA3hJREFUaAXlm8+K00Acx7MiCIJH/yw+gA9g25O49SL4AO3Bp1jw5NvktC+wF88qevK4BU97EmzxUBCEolK/n5gp3W6TTJPfpNPNF37MNsl85/vN/DaTmU6PknC4K+pniqeKJ3k8UnkvDxXJzzy+q/yaxxeVHxW/FNHjgRSeKt4rFoplzaAuHHDBGR2eS9G54reirsmienDCTRt7xwsp+KAoEmt9nLaGitZxrBbPFNaGfPloGw2t4JVamSt8xYW6Dg1oCYo3Yv+rCGViV160oMkcd8SYKnYV1Nb1aEOjCe6L5ZOiLfF120EjWhuBu3YIZt1NQmujnk5F4MgOpURzLfAwOBSTmzp3fpDxuI/pabxpqOoz2r2HLAb0GMbZKlNV5/Hg9XJypguryA7lPF5KMdTZQzHjqxNPhWhzIuAruOl1eNqKEx1tSh5rfbxdw7mOxCq4qS68ZTjKS1YVvilu559vWvFHhh4rZrdyZ69Vmpgdj8fJbDZLJpNJ0uv1cnr/gjrUhQMuI+ANjyuwftQ0bbL6Erp0mM/ny8Fg4M3LtdRxgMtKl3jwmIHVxYXChFy94/Rmpa/pTbNUhstKV+4Rr8lLQ9KlUvJKLyG8yvQ2s9SBy1Jb7jV5a0yapfF6apaZLjLLcWtd4sNrmJUMHyM+1xibTjH82Zh01TNlhsrOhdKTe00uAzZQmN6+KW+sDa/JD2PSVQ873m29yf+1Q9VDzfEYlHi1G5LKBBWZbtEsHbFwb1oYDwr1ZiF/2bnCSg1OBE/pfr9/bWx26UxJL3ONPISOLKUvQza0LZUxSKyjpdTGa/vDEr25rddbMM0Q3O6Lx3rqFvU+x6UrRKQY7tyrZecmD9FODy8uLizTmilwNj0kraNcAJhOp5aGVwsAGD5VmJBrWWbJSgWT9zrzWepQF47RaGSiKfeGx6Szi3gzmX/HHbihwBser4B9UJYpFBNX4R6vTn3VQnez0SymnrHQMsRYGTr1dSk34ljRqS/EMd2pLQ8YBp3a1PLfcqCpo8gtHkZFHKkTX6fs3MY0blKnth66rKCnU0VRGu37ONrQaA4eZDFtWAu2fXj9zjFkxTBOo8F7t926gTp/83Kyzzcy2kZD6xiqxTYnHLRFm3vHiRSwNSjkz3hoIzo8lCKWUlg/YtGs7tObunDAZfpDLbfEI15zsEIY3U/x/gHHc/G1zltnAgAAAABJRU5ErkJggg==');

}

.jquery-modal .modal-spinner {
  display: none;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translateY(-50%) translateX(-50%);
  padding: 12px 16px;
  border-radius: 5px;
  background-color: #111;
  height: 20px;
}

.jquery-modal .modal-spinner > div {
  border-radius: 100px;
  background-color: #fff;
  height: 20px;
  width: 2px;
  margin: 0 1px;
  display: inline-block;

  -webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out;
  animation: sk-stretchdelay 1.2s infinite ease-in-out;
}

.jquery-modal .modal-spinner .rect2 {
  -webkit-animation-delay: -1.1s;
  animation-delay: -1.1s;
}

.jquery-modal .modal-spinner .rect3 {
  -webkit-animation-delay: -1.0s;
  animation-delay: -1.0s;
}

.jquery-modal .modal-spinner .rect4 {
  -webkit-animation-delay: -0.9s;
  animation-delay: -0.9s;
}

@-webkit-keyframes sk-stretchdelay {
  0%, 40%, 100% { -webkit-transform: scaleY(0.5) }
  20% { -webkit-transform: scaleY(1.0) }
}

@keyframes sk-stretchdelay {
  0%, 40%, 100% {
    transform: scaleY(0.5);
    -webkit-transform: scaleY(0.5);
  }  20% {
    transform: scaleY(1.0);
    -webkit-transform: scaleY(1.0);
  }
}


module.exports

// schema.js - index - Copyright Andreas Kalsch <andreaskalsch@gmx.de> (MIT Licensed)

"use strict";
var sys = require('sys');
require('extensions');

var LANG = 'de';

function _Error (path, name, message) {

	this.path = path
		? path.clone()
		: [];
	this.name = name || '';
	this.message = message || '';
};

_Error.prototype = new Error;
_Error.prototype.constructor = _Error;

function Validation (rootSchema, instance) {

	this.id = ++Validation._id;

	// Save combination of objects/schemas
	this._flags = {};

	this._wasError = 0;

	// Stack of schemas
	this._stack = [];
	this.push(rootSchema);

	// Current path
	this.path = [];

	/// Relevant?
	this.rootSchema = rootSchema;

	// Both relevant properties the user is interested in
	this.errors = [];
	this.instance = instance;
	this.instance = this.start(instance);

	delete this._flags, this._wasError, this._stack;
};

Object.extend(Validation.prototype, {

	// change state

	pushError: function (name, message) {

		/*message = message
			|| Schema.messages[name]
				? (
					Schema.messages[name][Schema.LANG || LANG]
						|| Schema.messages[name].en
				).call(this)
				: '';*/

		this.errors.push(new _Error(this.path, name, message));
		this._wasError++;
	},

	wasError: function () {

		var was = this._wasError;
		this._wasError = 0;
		return was;
	},

	isError: function () {

		return this.errors.length > 0;
	},

	push: function (schema, key) {

		this.schema = schema;
		this._stack.push(schema);
		if (key !== undefined) this.path.push(key);
	},

	pop: function (omitKey) {

		var ret = [
			this._stack.pop(),
			omitKey ? undefined : this.path.pop()
		];
		var len = this._stack.length;
		this.schema = len
			? this._stack[len - 1]
			: null;
		return ret;
	},

	getFlag: function (instance) {

		return Object.id(instance)+'_'+this.schema.id;
	},

	flagObject: function (instance) {

		this._flags[this.getFlag(instance)] = true;
		return instance;
	},

	isObjectFlagged: function (instance) {

		return this.getFlag(instance) in this._flags;
	},

	// start

	start: function (instance) {

		if (instance instanceof Object) {

			if (this.isObjectFlagged(instance)) return instance;
			this.flagObject(instance);
		}

		instance = this.extends(instance); if (this.wasError()) return instance;

		instance = this.optional(instance); if (this.wasError() || instance === undefined) return instance;

		instance = this.adapters(instance); if (this.wasError()) return instance;

		instance = this.type(instance); if (this.wasError()) return instance;

		instance = this.requires(instance);

		return instance;
	},

	// checkers

	extends: function (instance) {

		if (!this.schema.extends) return instance;

		this.push(this.schema.extends);
		instance = this.start(instance);
		this.pop(true);

		return instance;
	},

	optional: function (instance) {

		return (this.schema.optional || instance !== undefined)
			? instance
			: this.schema.__optional.call(this, instance);
	},

	adapters: function (instance) {

		if (!this.schema.adapters) return instance;

		var adapters = this.schema.adapters;

		if (!(adapters instanceof Array)) {

			return (typeof adapters === 'string'
				? Schema.plugins[adapters]
				: adapters).call(this, instance);
		}

		var len = this.errors.length,
			adapter;
		for (var li = adapters.length, i = 0; i < li; i++) {

			adapter = adapters[i];

			instance = (typeof adapter === 'string'
				? Schema.plugins[adapter]
				: adapter).call(this, instance);

			if (this.errors.length > len) return instance;
		}

		return instance;
	},

	type: function (instance, type, isFirst) {

		isFirst = isFirst === false
			? false
			: true;

		var toType = type || this.schema.type;

		if (!(toType instanceof Array)) {

			var fromType = typeof instance;

			var ok = (function () {

				if (toType === 'any') return true;

				if (toType === 'null') return instance === null;

				if (toType === 'array') return instance instanceof Array;

				if (toType === 'object') return instance instanceof Object && !(instance instanceof Array);

				if (toType === 'number') return fromType === 'number';

				if (toType === 'integer') return Math.round(instance) === instance;

				if (toType === 'string') return fromType === 'string';

				if (toType === 'boolean') return fromType === 'boolean';
			})();

			if (!ok) {

				instance = this.schema.__type.call(this, instance); if (this.wasError()) return instance;
			}

			var type = typeof instance;

			if (type === 'number') {

				instance = this.minimum(instance); if (this.wasError()) return instance;

				instance = this.maximum(instance); if (this.wasError()) return instance;

				instance = this.maxDecimal(instance); if (this.wasError()) return instance;

				if (isFirst) instance = this.enum(instance);

				return instance;
			}

			if (type === 'string') {

				instance = this.pattern(instance); if (this.wasError()) return instance;

				instance = this.minLength(instance); if (this.wasError()) return instance;

				instance = this.maxLength(instance); if (this.wasError()) return instance;

				if (isFirst) instance = this.enum(instance);

				return instance;
			}

			if (instance instanceof Array) {

				instance = this.minItems(instance); if (this.wasError()) return instance;

				instance = this.maxItems(instance); if (this.wasError()) return instance;

				instance = this.items(instance); if (this.wasError()) return instance;

				instance = this.additionalProperties(instance); if (this.wasError()) return instance;

				if (isFirst) instance = this.enum(instance);

				return instance;
			}

			if (instance instanceof Object) {

				instance = this.properties(instance);

				instance = this.additionalProperties(instance); if (this.wasError()) return instance;

				if (isFirst) instance = this.enum(instance);

				return instance;
			}

			return instance;
		}
		else {

			var inst,
				len = this.errors.length;
			for (var li = toType.length, i = 0; i < li; i++) {

				inst = this.type(instance, toType[i], i === 0);
				if (this.errors.length === len) {

					instance = inst;
					break;
				}

				if (i + 1 < li) {

					this.errors.splice(len);
					this.wasError();
				}
			}
		}

		return instance;
	},

	enum: function (instance) {

		if ('enum' in this.schema) {

			/// Tolerant ?
			return this.schema.enum.indexOf(instance) > -1
				? instance
				: this.schema.__enum.call(this, instance);
		}

		return instance;
	},

	requires: function (instance) {

		if (this.schema.requires !== undefined
			&& this.parentInstance) {

			/// requires as string will be replaces by schem, like this:
			var tempSchema;
			if (typeof this.schema.requires === 'string') {

				tempSchema = {

					properties: {}
				};
				tempSchema.properties[this.schema.requires] = new Schema({type:'any'});
			}
			else {

				tempSchema = {

					properties: this.schema.requires
				};
			}

			var schema_key = this.pop();

			this.push(tempSchema);

			this.properties(this.parentInstance);

			this.pop(true);
			this.push.apply(this, schema_key);
		}

		return instance;
	},

	properties: function (instance) {

		if ('properties' in this.schema) {

			var props = this.schema.properties,
				prop,
				inst;

			this.parentInstance = instance;

			var isIn, inst;
			for (var key in props) {

				this.push(props[key], key);
				isIn = key in instance;
				inst = this.start(instance[key]);

				if (isIn || inst !== undefined) instance[key] = inst;
				this.pop();
			}

			this.parentInstance = null;
		}

		this.wasError();
		return instance;
	},

	additionalProperties: function (instance) {

		if ('additionalProperties' in this.schema) {

			if (instance instanceof Object && !(instance instanceof Array)) {

				var props = this.schema.properties || {};

				if (!this.schema.additionalProperties) {

					for (var ks = Object.keys(
						instance
					), li = ks.length, i = 0, key; key = ks[i], i < li; i++) {

						if (!(key in props)) {

							return this.schema.__additionalProperties.call(this, instance);
						}
					}
				}
				else if (this.schema.additionalProperties !== true) {

					for (var ks = Object.keys(
						instance
					), li = ks.length, i = 0, key; key = ks[i], i < li; i++) {

						if (!(key in props)) {

							this.push(this.schema.additionalProperties, key);
							instance[key] = this.start(instance[key]);
							this.pop();
						}
					}
				}
			}
			else if (this.schema.items instanceof Array) {

				if (!this.schema.additionalProperties) {

					if (instance.length > this.schema.items.length) {

						return this.schema.__additionalProperties.call(this, instance);
					}
				}
				else if (this.schema.additionalProperties !== true) {

					for (var li = instance.length, i = this.schema.items.length; i < li; i++) {

						this.push(this.schema.additionalProperties, i);
						instance[i] = this.start(instance[i]);
						this.pop();
					}
				}
			}
		}

		return instance;
	},

	items: function (instance) {

		if ('items' in this.schema) {

			if (this.schema.items instanceof Array) {

				var isIn, inst;
				for (var li = this.schema.items.length, i = 0; i < li; i++) {

					this.push(this.schema.items[i], i);
					isIn = i in instance;
					inst = this.start(instance[i]);
					if (isIn || inst !== undefined) instance[i] = inst;
					this.pop();
				}
			}
			else {

				for (var li = instance.length, i = 0; i < li; i++) {

					this.push(this.schema.items, i);
					instance[i] = this.start(instance[i]);
					this.pop();
				}
			}
		}

		return instance;
	},

	maxItems: function (instance) {

		return this.schema.maxItems && instance.length > this.schema.maxItems
			? this.schema.__maxItems.call(this, instance)
			: instance;
	},

	minItems: function (instance) {

		return this.schema.minItems && instance.length < this.schema.minItems
			? this.schema.__minItems.call(this, instance)
			: instance;
	},

	minimum: function (instance) {

		if (!('minimum' in this.schema)) return instance;

		var minimumCanEqual = 'minimumCanEqual' in this.schema
			? this.schema.minimumCanEqual
			: true;
		return (minimumCanEqual
			? instance >= this.schema.minimum
			: instance > this.schema.minimum
		)
			? instance
			: this.schema.__minimum.call(this, instance);
	},

	maximum: function (instance) {

		if (!('maximum' in this.schema)) return instance;

		var maximumCanEqual = 'maximumCanEqual' in this.schema
			? this.schema.maximumCanEqual
			: true;
		return (maximumCanEqual
			? instance <= this.schema.maximum
			: instance < this.schema.maximum
		)
			? instance
			: this.schema.__maximum.call(this, instance);
	},

	maxDecimal: function (instance) {

		if ('maxDecimal' in this.schema) {

			var factor = Math.pow(10, this.schema.maxDecimal);
			return Math.round(instance * factor) / factor === instance
				? instance
				: this.schema.__maxDecimal.call(this, instance);
		}

		return instance;
	},

	pattern: function (instance) {

		if (!this.schema.pattern) return instance;

		return this.schema.pattern.test(instance)
			? instance
			: this.schema.__pattern.call(this, instance);
	},

	minLength: function (instance) {

		if (this.schema.minLength === undefined) return instance;

		return instance.length >= this.schema.minLength
			? instance
			: this.schema.__minLength.call(this, instance);
	},

	maxLength: function (instance) {

		if (this.schema.maxLength === undefined) return instance;

		return instance.length <= this.schema.maxLength
			? instance
			: this.schema.__maxLength.call(this, instance);
	}
});

Object.extend(Validation, {

	_id: 0,
	Error: _Error
});

function Schema (rawSchema) {

	if (typeof rawSchema === 'string') rawSchema = JSON.parse(rawSchema);

	if (rawSchema instanceof Schema) return rawSchema;

	Object.extend(this, rawSchema);

	var id = typeof this.id === 'string'
		? this.id
		: '__'+(++Schema._id);
	this.constructor.instances[id] = this;
};

Object.extend(Schema.prototype, {

	validate: function (instance) {

		return new Validation(this, instance);
	},

	setFallbacks: function (obj) {

		var adapterName;

		for (var propertyName in obj) {

			adapterName = obj[propertyName];

			if (!(adapterName in Schema.plugins)) throw 'Fallback not registered!';

			this['__'+propertyName] = Schema.plugins[adapterName](propertyName);
		}

		return this;
	}
});

Object.extend(Schema, {

	_id: 0,

	create: function (rawSchema) {

		var validation = Schema.instances.jsonSchemaCore.validate(rawSchema);
		if (validation.isError()) {

			var E = new Error('Invalid schema');
			E.validation = validation
			throw E;
		}

		return validation.instance;
	},

	createSeveral: function (schemaDict) {

		for (var key in schemaDict) {

			if (schemaDict[key].id === undefined) schemaDict[key].id = key;

			schemaDict[key] = this.create(schemaDict[key]);
		}

		return schemaDict;
	},

	instances: {},

	Validation: Validation,

	resolveRefs: function () {

		for (var id in this.instances) {

			Object.resolveRefs(this.instances[id], true);
		}
	},

	plugins: {},

	messages: {

		type: {

			de: function () {

				//return 'Dieser Wert muss vom Typ %s sein.'.sprintf(Schema.instances.jsonSchemaCore.properties.type.labels[this.schema.type].de);
			}
		}
	},

	TOLERANT_FALLBACKS: {

		optional: 'setDefault',
		type: 'castTolerantlyToType',
		enum: 'setDefault',
		requires: 'pushError',
		additionalProperties: 'removeAdditionalProperties',
		maxItems: 'sliceToMaxItems',
		minItems: 'pushError',
		maximum: 'setMaximum',
		minimum: 'setMinimum',
		maxDecimal: 'roundToMaxDecimal',
		pattern: 'pushError',
		minLength: 'pushError',
		maxLength: 'pushError'
	},

	STRICT_FALLBACKS: {

		optional: 'pushError',
		type: 'pushError',
		enum: 'pushError',
		requires: 'pushError',
		additionalProperties: 'pushError',
		maxItems: 'pushError',
		minItems: 'pushError',
		maximum: 'pushError',
		minimum: 'pushError',
		maxDecimal: 'pushError',
		pattern: 'pushError',
		minLength: 'pushError',
		maxLength: 'pushError'
	}
});

Object.extend(Schema.plugins, require('./plugins/default'));

Schema.prototype.setFallbacks(Schema.TOLERANT_FALLBACKS);

Object.extend(Schema.plugins, {

	// adapters - for the core schema

	addToRefs: function (instance) {

		if (instance instanceof Object
			&& typeof instance.$ref === 'string') {

			/// Still no URIs supported
			var toPath = this.path,
				fromPath = instance.$ref.split('.');

			Object.addRef(
				this.instance,
				toPath.clone(),
				fromPath[0] === '#'
					? fromPath.clone()
					: ['__Schema', 'instances'].concat(fromPath)
			);
		}

		return instance;
	},

	instantiateSchema: function (instance) {

		if (instance instanceof Schema || !(instance instanceof Object)) return instance;

		var isThisInstance = instance === this.instance;
		instance = new Schema(instance);
		if (isThisInstance) this.instance = instance;

		if (instance.properties instanceof Object) {

			var props = instance.properties || {};

			for (var key in props) {

				if (props[key] instanceof Object && !(props[key] instanceof Schema))
					props[key] = new Schema(props[key]);
			}
		}

		if (instance.items instanceof Array) {

			var items = instance.items;
			for (var li = items.length, i = 0; i < li; i++) {

				if (items[i] instanceof Object && !(items[i] instanceof Schema))
					items[i] = new Schema(items[i]);
			}
		}
		else if (instance.items instanceof Object) {

			if (!(instance.items instanceof Schema))
				instance.items = new Schema(instance.items);
		}

		if (instance.requires
			&& instance.requires instanceof Object
			&& !(instance.requires instanceof Schema)) {

			instance.requires = new Schema(instance.requires);
		}

		if (instance.additionalProperties instanceof Object
			&& !(instance.additionalProperties instanceof Schema)) {

			instance.additionalProperties = new Schema(instance.additionalProperties);
		}

		if (instance.extends instanceof Object) {

			if (!(instance.extends instanceof Schema)) {

				instance.extends = new Schema(instance.extends);
			}
		}

		if (instance.fallbacks) {

			instance.setFallbacks(instance.fallbacks);
			delete instance.fallbacks;
		}

		Object.resetRefs(instance);

		return instance;
	}
});

var jsonSchemaCore = require('./schemas/jsonSchemaCore');

/* The schema schema cannot validate/adapt itself after having been instantiated from raw JSON.
 * At first we apply all adaptations manually, including:
 * - instantiating (sub)schemas
 * - resolve references
 * - set fallbacks (strict)
 * - eventually make extends change the parent schema
 */

jsonSchemaCore = new Schema(jsonSchemaCore);
jsonSchemaCore.setFallbacks(Schema.STRICT_FALLBACKS);

for (var key in jsonSchemaCore.properties) {

	jsonSchemaCore.properties[key] = new Schema(jsonSchemaCore.properties[key]);
	jsonSchemaCore.properties[key].setFallbacks(Schema.STRICT_FALLBACKS);
}

jsonSchemaCore.properties.type.items = new Schema(jsonSchemaCore.properties.type.items);
jsonSchemaCore.properties.type.items.setFallbacks(Schema.STRICT_FALLBACKS);
jsonSchemaCore.properties.type.items.enum = jsonSchemaCore.properties.type.enum;

jsonSchemaCore.properties.id.pattern = new RegExp(jsonSchemaCore.properties.id.pattern);

jsonSchemaCore.properties.options.items = new Schema(jsonSchemaCore.properties.options.items);
jsonSchemaCore.properties.options.items.setFallbacks(Schema.STRICT_FALLBACKS);

jsonSchemaCore.properties.requires.properties = jsonSchemaCore.properties;

jsonSchemaCore.properties.adapters.items = new Schema(jsonSchemaCore.properties.adapters.items);
jsonSchemaCore.properties.adapters.items.setFallbacks(Schema.STRICT_FALLBACKS);

jsonSchemaCore.properties.fallbacks.additionalProperties = new Schema(jsonSchemaCore.properties.fallbacks.additionalProperties);
jsonSchemaCore.properties.fallbacks.additionalProperties.setFallbacks(Schema.STRICT_FALLBACKS);

jsonSchemaCore.properties.properties.additionalProperties = jsonSchemaCore;

//Luhmannesk moment
//jsonSchemaCore.properties.extends.extends = jsonSchemaCore;

jsonSchemaCore.properties.items.properties = jsonSchemaCore.properties;
jsonSchemaCore.properties.items.items = jsonSchemaCore;

jsonSchemaCore.properties.additionalProperties.properties = jsonSchemaCore.properties;

/* Now, the core schema should be as if it had validated itself.
 * Check it out:
 */

var validation = jsonSchemaCore.validate(jsonSchemaCore);

if (validation.isError()) {

	sys.puts('The core schema is not valid!');
	validation.errors.forEach(function (error) {

		sys.puts(sys.inspect(error));
	});
	sys.puts(sys.inspect(validation));
}

global.__Schema = module.exports = Schema;

var sys = require('sys');
require('extensions');

module.exports = {

	// fallbacks

	castTolerantlyToType: function (errorName) {

		return function (instance) {

			var toType = this.schema.type,
				fromType = typeof instance,
				inst;

			if (toType === 'string') {

				if (fromType === 'object') {

					if (
						instance !== null
						&& typeof instance.toString === 'function'
						&& instance.toString !== Object.prototype.toString
						&& instance.toString !== Array.prototype.toString
					) return ''+instance;

					this.pushError(errorName);
					return instance;
				}
				return ''+instance;
			}

			if (toType === 'number') {

				if (fromType !== 'boolean') {

					inst = parseFloat(instance);
					if (isNaN(inst)) {

						this.pushError(errorName);
						return instance;
					}

					return inst;
				}
				if (instance === false) return 0;
				if (instance === true) return 1;
			}

			if (toType === 'integer') {

				if (fromType !== 'boolean') {

					inst = parseInt(instance, 10);
					if (isNaN(inst)) {

						this.pushError(errorName);
						return instance;
					}

					return inst;
				}
				if (instance === false) return 0;
				if (instance === true) return 1;
			}

			if (toType === 'boolean') {

				if (fromType === 'string') {

					if (instance.trim().toLowerCase() === 'false') return false;

					inst = parseFloat(instance);
					return isNaN(inst) ? true : !!inst;
				}

				if (fromType === 'object') {

					this.pushError(errorName);
					return instance;
				}

				return !!instance;
			}

			if (toType === 'object') {

				if (instance instanceof Array && instance.length === 0) return {};
				if (fromType !== 'object' || instance === null) this.pushError(errorName);
				return instance;
			}

			if (toType === 'array') {

				if (!instance) return [];
				if (instance instanceof Object) {

					if (Object.keys(instance).length === 0) return [];
				}
				this.pushError(errorName);
				return instance;
			}

			if (toType === 'null') {

				this.pushError(errorName);
				return instance;
			}
		};
	},

	roundToMaxDecimal: function (errorName) {

		return function (instance) {

			if (this.schema.maxDecimal === undefined) {

				this.pushError(errorName);
				return instance;
			}

			var factor = Math.pow(10, this.schema.maxDecimal);
			return Math[instance < 0 ? 'ceil' : 'floor'](instance * factor) / factor;
		};
	},

	setMinimum: function (errorName) {

		return function (instance) {

			if (this.schema.minimum === undefined) {

				this.pushError(errorName);
				return instance;
			}

			return this.schema.minimum;
		};
	},

	setMaximum: function (errorName) {

		return function (instance) {

			if (this.schema.maximum === undefined) {

				this.pushError(errorName);
				return instance;
			}

			return this.schema.maximum;
		};
	},

	truncateToMaxLength: function (errorName) {

		return function (instance) {

			if (this.schema.maxLength === undefined) {

				this.pushError(errorName);
				return instance;
			}

			return instance.substr(0, this.schema.maxLength);
		};
	},

	setDefault: function (errorName) {

		return function (instance) {

			if ('default' in this.schema) return this.schema['default'];

			this.pushError(errorName);
		};
	},

	removeAdditionalProperties: function (errorName) {

		return function (instance) {

			if (this.schema.type === 'object') {

				var props = this.schema.properties || {};
				for (var ks = Object.keys(
					instance
				), li = ks.length, i = 0, key; key = ks[i], i < li; i++) {

					if (props[key] === undefined) delete instance[key];
				}

				return instance;
			}

			if (this.schema.items && this.schema.items instanceof Array) {

				for (var li = instance.length, i = this.schema.items.length; i < li; i++) {

					delete instance[i];
				}
			}

			return instance;
		};
	},

	sliceToMaxItems: function (errorName) {

		return function (instance) {

			if (this.schema.maxItems !== undefined) {

				/// instance.length will not change
				/*for (var li = instance.length, i = this.schema.maxItems; i < li; i++) {

					delete instance[i];
				}*/

				if (instance.length > this.schema.maxItems) {

					instance.splice(this.schema.maxItems);
				}
			}
			else this.pushError(errorName);

			return instance;
		};
	},

	pushError: function (errorName) {

		return function (instance) {

			this.pushError(errorName);
			return instance;
		};
	},

	deleteInstance: function (errorName) {

		/*
		Delete instance
		*/
		return function (instance) {

			// Solution needed - undefined means that property still exists
			return undefined;
		};
	}
};

module.exports = {

	id: 'jsonSchemaCore',
	title: 'JSON Schema Core Schema',
	description: "This is the JSON Schema for JSON Schemas.",
	type: "object",

	properties: {

		// *

		type: {

			type: ["string", "array"],
			items: {

				type: "string",
				enum: {"$ref": "#.properties.type.enum"}
			},
			uniqueItems: true,
			description: "This is a type definition value. This can be a simple type, or a union type",
			enum: ["string", "object", "array", "boolean", "number", "integer", "null", "any"],
			minItems: 2,
			optional: true,
			"default": "any",
				adapters: 'addToRefs',
				labels: {

					de: {

						string: 'Text',
						object: 'Objekt',
						array: 'Liste',
						boolean: 'wahr/falsch',
						number: 'Zahl',
						integer: 'ganze Zahl',
						'null': 'undefiniert'
					},
					en: {

					}
				}
		},

		id: {

			type: "string",
			optional: true,
			minLength: 1,
			pattern: "[^\\.#]",
			description: "A globally unique ID for this schema. It can be used to reference the schema from another one."
		},

		optional: {

			type: "boolean",
			description: "This indicates that the instance property in the instance object is not required.",
			optional: true,
			"default": false,
				adapters: 'addToRefs'
		},

		enum: {
			type: "array",
			optional: true,
			description: "This provides an enumeration of possible values that are valid for the instance property. It goes only for the first type, if type is an array of types.",
			minItems: 1,
			uniqueItems: true
		},

		description: {

			type: "string",
			optional: true,
			description: "This provides a description of the purpose the instance property. The value can be a string or it can be an object with properties corresponding to various different instance languages (with an optional default property indicating the default description).",
				adapters: 'addToRefs'
		},

		title: {

			type: "string",
			optional: true,
			description: "This provides the title of the property",
				adapters: 'addToRefs'
		},

		"default": {
			type: "any",
			optional: true,
			description: "This indicates the default for the instance property.",
				adapters: 'addToRefs'
		},

		// should go with enum
		options: {

			type: "array",
			optional: true,
			items: {
				type: "object",
				properties: {
					label: {
						type: "string",
						optional: true
					},
					value: {
						type: "any"
					}
				}
			},
				adapters: 'addToRefs'
		},

		extends: {

			// You cannot do this, because the schema itself is not optional
			// and so #.properties.extends does inherit from core schema, but
			// isn't as restrictive. So you cannot validate extends in a schema
			/// Better solution needed!
			//extends: {"$ref": "#"},
			type: 'object',
			optional: true,
				adapters: 'addToRefs'
		},

		// must be in object
		requires: {

			type: ["string", "object"],
			properties: {"$ref": "#.properties"},
			optional: true,
			description: "indicates a required property or a schema that must be validated if this property is present",
				adapters: 'addToRefs'
		},

			// just for schema schemas ;)
			nonStandard: {

				type: "boolean",
				optional: true,
				"default": false,
				nonStandard: true
			},

			adapters: {

				// Allow closures
				type: ['string', 'array', 'any'],
				items: {

					type: ['string', 'any']
				},
				optional: true,
				description: "One or several adapters identified by plugin IDs",
				nonStandard: true,
					adapters: 'addToRefs'
			},

			fallbacks: {

				type: "object",
				optional: true,
				additionalProperties: {

					type: "string",
					description: "Identifier for a adapter function that will be executed if a constraint is violated"
				},
				description: "Fallback object, with property names = schema property names",
				nonStandard: true,
					adapters: 'addToRefs'
			},

		// object

		properties: {

			type: "object",
			additionalProperties: {"$ref": "#"},
			description: "This is a definition for the properties of an object value",
			optional: true,
			"default": {},
				adapters: 'addToRefs'
		},

		// array

		items: {

			type: ["object", "array"],
			properties: {"$ref": "#.properties"},
			items: {"$ref": "#"},
			description: "When the value is an array, this indicates the schema to use to validate each item in an array",
			optional: true,
				adapters: 'addToRefs'
		},

		maxItems: {

			type: "integer",
			optional: true,
			description: "When the instance value is an array, this indicates maximum number of items.",
			minimum: 0,
			adapters: 'addToRefs'
		},

		minItems: {

			type: "integer",
			optional: true,
			description: "When the instance value is an array, this indicates minimum number of items.",
			minimum: 0,
				adapters: 'addToRefs'
		},

		// object, array

		additionalProperties: {

			type: ["boolean", "object"],
			properties: {"$ref": "#.properties"},
			description: "This provides a default property definition for all properties that are not explicitly defined in an object type definition.",
			optional: true,
			"default": true,
				adapters: ['addToRefs']
		},

		// number, integer

		minimum: {

			type: "number",
			optional: true,
			description: "This indicates the minimum value for the instance property when the type of the instance value is a number.",
				adapters: 'addToRefs'
		},

		minimumCanEqual: {

			type: "boolean",
			optional: true,
			description: "If the minimum is defined, this indicates whether or not the instance property value can equal the minimum.",
			"default": true,
				adapters: 'addToRefs'
		},

		maximum: {

			type: "number",
			optional: true,
			description: "This indicates the maximum value for the instance property when the type of the instance value is a number.",
				adapters: 'addToRefs'
		},

		maximumCanEqual: {

			type: "boolean",
			optional: true,
			description: "If the maximum is defined, this indicates whether or not the instance property value can equal the maximum.",
			"default": true,
				adapters: 'addToRefs'
		},

		maxDecimal: {

			type: "integer",
			optional: true,
			description: "This indicates the maximum number of decimal places in a floating point number.",
				adapters: 'addToRefs'
		},

		// string

		pattern: {

			// Allow JS RegExp instances
			type: ['string', 'object'],
			format: "regex",
			description: "When the instance value is a string, this provides a regular expression that a instance string value should match in order to be valid.",
			optional: true,
				adapters: [
					'addToRefs',
					function (instance, walker) {

						if (instance instanceof RegExp) return instance;

						return new RegExp(instance);
					}
				]
		},

		maxLength: {

			type: "number",
			optional: true,
			description: "When the instance value is a string, this indicates maximum length of the string.",
				adapters: 'addToRefs'
		},

		minLength: {

			type: "number",
			optional: true,
			description: "When the instance value is a string, this indicates minimum length of the string.",
			adapters: 'addToRefs'
		},

		// string, number, integer

		format: {
			type: "string",
			optional: true,
			description: "This indicates what format the data is among some predefined formats which may include:\n\ndate - a string following the ISO format \naddress \nschema - a schema definition object \nperson \npage \nhtml - a string representing HTML",
			adapters: 'addToRefs'
		}
	},

	adapters: [
		'instantiateSchema',
		'addToRefs'
	]
};

// schema.js - index - Copyright Andreas Kalsch <andreaskalsch@gmx.de> (MIT Licensed)

"use strict";
var sys = require('sys');
require('extensions');

var LANG = 'de';

function _Error (path, name, message) {

	this.path = path
		? path.clone()
		: [];
	this.name = name || '';
	this.message = message || '';
};

_Error.prototype = new Error;
_Error.prototype.constructor = _Error;

function Validation (rootSchema, instance) {

	this.id = ++Validation._id;

	// Save combination of objects/schemas
	this._flags = {};

	this._wasError = 0;

	// Stack of schemas
	this._stack = [];
	this.push(rootSchema);

	// Current path
	this.path = [];

	/// Relevant?
	this.rootSchema = rootSchema;

	// Both relevant properties the user is interested in
	this.errors = [];
	this.instance = instance;
	this.instance = this.start(instance);

	delete this._flags, this._wasError, this._stack;
};

Object.extend(Validation.prototype, {

	// change state

	pushError: function (name, message) {

		/*message = message
			|| Schema.messages[name]
				? (
					Schema.messages[name][Schema.LANG || LANG]
						|| Schema.messages[name].en
				).call(this)
				: '';*/

		this.errors.push(new _Error(this.path, name, message));
		this._wasError++;
	},

	wasError: function () {

		var was = this._wasError;
		this._wasError = 0;
		return was;
	},

	isError: function () {

		return this.errors.length > 0;
	},

	push: function (schema, key) {

		this.schema = schema;
		this._stack.push(schema);
		if (key !== undefined) this.path.push(key);
	},

	pop: function (omitKey) {

		var ret = [
			this._stack.pop(),
			omitKey ? undefined : this.path.pop()
		];
		var len = this._stack.length;
		this.schema = len
			? this._stack[len - 1]
			: null;
		return ret;
	},

	getFlag: function (instance) {

		return Object.id(instance)+'_'+this.schema.id;
	},

	flagObject: function (instance) {

		this._flags[this.getFlag(instance)] = true;
		return instance;
	},

	isObjectFlagged: function (instance) {

		return this.getFlag(instance) in this._flags;
	},

	// start

	start: function (instance) {

		if (instance instanceof Object) {

			if (this.isObjectFlagged(instance)) return instance;
			this.flagObject(instance);
		}

		instance = this.extends(instance); if (this.wasError()) return instance;

		instance = this.optional(instance); if (this.wasError() || instance === undefined) return instance;

		//instance = this.readonly(instance); if (this.wasError() || instance === undefined) return instance;

		instance = this.adapters(instance); if (this.wasError()) return instance;

		instance = this.type(instance); if (this.wasError()) return instance;

		instance = this.requires(instance);

		return instance;
	},

	// checkers

	extends: function (instance) {

		if (!this.schema.extends) return instance;

		this.push(this.schema.extends);
		instance = this.start(instance);
		this.pop(true);

		return instance;
	},

	optional: function (instance) {

		return (this.schema.optional || instance !== undefined)
			? instance
			: this.schema.__optional.call(this, instance);
	},

	readonly: function (instance) {

		return (!this.schema.readonly)
			? instance
			: this.schema.__readonly.call(this, instance);
	},

	adapters: function (instance) {

		if (!this.schema.adapters) return instance;

		var adapters = this.schema.adapters;

		if (!(adapters instanceof Array)) {

			return (typeof adapters === 'string'
				? Schema.plugins[adapters]
				: adapters).call(this, instance);
		}

		var len = this.errors.length,
			adapter;
		for (var li = adapters.length, i = 0; i < li; i++) {

			adapter = adapters[i];

			instance = (typeof adapter === 'string'
				? Schema.plugins[adapter]
				: adapter).call(this, instance);

			if (this.errors.length > len) return instance;
		}

		return instance;
	},

	type: function (instance, type, isFirst) {

		isFirst = isFirst === false
			? false
			: true;

		var toType = type || this.schema.type;

		if (!(toType instanceof Array)) {

			var fromType = typeof instance;

			var ok = (function () {

				if (toType === 'any') return true;

				if (toType === 'null') return instance === null;

				if (toType === 'array') return instance instanceof Array;

				if (toType === 'object') return instance instanceof Object && !(instance instanceof Array);

				if (toType === 'number') return fromType === 'number';

				if (toType === 'integer') return Math.round(instance) === instance;

				if (toType === 'string') return fromType === 'string';

				if (toType === 'boolean') return fromType === 'boolean';

				if (toType === 'date') return instance instanceof Date;
			})();

			if (!ok) {

				instance = this.schema.__type.call(this, instance); if (this.wasError()) return instance;
			}

			var type = typeof instance;

			if (type === 'number') {

				instance = this.minimum(instance); if (this.wasError()) return instance;

				instance = this.maximum(instance); if (this.wasError()) return instance;

				instance = this.maxDecimal(instance); if (this.wasError()) return instance;

				if (isFirst) instance = this.enum(instance);

				return instance;
			}

			if (type === 'string') {

				instance = this.pattern(instance); if (this.wasError()) return instance;

				instance = this.minLength(instance); if (this.wasError()) return instance;

				instance = this.maxLength(instance); if (this.wasError()) return instance;

				if (isFirst) instance = this.enum(instance);

				return instance;
			}

			if (instance instanceof Array) {

				instance = this.minItems(instance); if (this.wasError()) return instance;

				instance = this.maxItems(instance); if (this.wasError()) return instance;

				instance = this.items(instance); if (this.wasError()) return instance;

				instance = this.additionalProperties(instance); if (this.wasError()) return instance;

				if (isFirst) instance = this.enum(instance);

				return instance;
			}

			if (instance instanceof Date) {

				instance = this.minimum(instance); if (this.wasError()) return instance;

				instance = this.maximum(instance); if (this.wasError()) return instance;

				if (isFirst) instance = this.enum(instance);

				return instance;
			}

			if (instance instanceof Object) {

				instance = this.properties(instance);

				instance = this.additionalProperties(instance); if (this.wasError()) return instance;

				if (isFirst) instance = this.enum(instance);

				return instance;
			}

			return instance;
		}
		else {

			var inst,
				len = this.errors.length;
			for (var li = toType.length, i = 0; i < li; i++) {

				inst = this.type(instance, toType[i], i === 0);
				if (this.errors.length === len) {

					instance = inst;
					break;
				}

				if (i + 1 < li) {

					this.errors.splice(len);
					this.wasError();
				}
			}
		}

		return instance;
	},

	enum: function (instance) {

		if ('enum' in this.schema) {

			/// Tolerant ?
			return this.schema.enum.indexOf(instance) > -1
				? instance
				: this.schema.__enum.call(this, instance);
		}

		return instance;
	},

	requires: function (instance) {

		if (this.schema.requires !== undefined
			&& this.parentInstance) {

			/// requires as string will be replaces by schem, like this:
			var tempSchema;
			if (typeof this.schema.requires === 'string') {

				tempSchema = {

					properties: {}
				};
				tempSchema.properties[this.schema.requires] = new Schema({type:'any'});
			}
			else {

				tempSchema = {

					properties: this.schema.requires
				};
			}

			var schema_key = this.pop();

			this.push(tempSchema);

			this.properties(this.parentInstance);

			this.pop(true);
			this.push.apply(this, schema_key);
		}

		return instance;
	},

	properties: function (instance) {

		if ('properties' in this.schema) {

			var props = this.schema.properties,
				prop,
				inst;

			this.parentInstance = instance;

			var isIn, inst;
			for (var key in props) {

				this.push(props[key], key);
				isIn = key in instance;
				inst = this.start(instance[key]);

				if (isIn || inst !== undefined) instance[key] = inst;
				this.pop();
			}

			this.parentInstance = null;
		}

		this.wasError();
		return instance;
	},

	additionalProperties: function (instance) {

		if ('additionalProperties' in this.schema) {

			if (instance instanceof Object && !(instance instanceof Array)) {

				var props = this.schema.properties || {};

				if (!this.schema.additionalProperties) {

					for (var ks = Object.keys(
						instance
					), li = ks.length, i = 0, key; key = ks[i], i < li; i++) {

						if (!(key in props)) {

							return this.schema.__additionalProperties.call(this, instance);
						}
					}
				}
				else if (this.schema.additionalProperties !== true) {

					for (var ks = Object.keys(
						instance
					), li = ks.length, i = 0, key; key = ks[i], i < li; i++) {

						if (!(key in props)) {

							this.push(this.schema.additionalProperties, key);
							instance[key] = this.start(instance[key]);
							this.pop();
						}
					}
				}
			}
			else if (this.schema.items instanceof Array) {

				if (!this.schema.additionalProperties) {

					if (instance.length > this.schema.items.length) {

						return this.schema.__additionalProperties.call(this, instance);
					}
				}
				else if (this.schema.additionalProperties !== true) {

					for (var li = instance.length, i = this.schema.items.length; i < li; i++) {

						this.push(this.schema.additionalProperties, i);
						instance[i] = this.start(instance[i]);
						this.pop();
					}
				}
			}
		}

		return instance;
	},

	items: function (instance) {

		if ('items' in this.schema) {

			if (this.schema.items instanceof Array) {

				var isIn, inst;
				for (var li = this.schema.items.length, i = 0; i < li; i++) {

					this.push(this.schema.items[i], i);
					isIn = i in instance;
					inst = this.start(instance[i]);
					if (isIn || inst !== undefined) instance[i] = inst;
					this.pop();
				}
			}
			else {

				for (var li = instance.length, i = 0; i < li; i++) {

					this.push(this.schema.items, i);
					instance[i] = this.start(instance[i]);
					this.pop();
				}
			}
		}

		return instance;
	},

	maxItems: function (instance) {

		return this.schema.maxItems && instance.length > this.schema.maxItems
			? this.schema.__maxItems.call(this, instance)
			: instance;
	},

	minItems: function (instance) {

		return this.schema.minItems && instance.length < this.schema.minItems
			? this.schema.__minItems.call(this, instance)
			: instance;
	},

	minimum: function (instance) {

		if (!('minimum' in this.schema)) return instance;

		var minimumCanEqual = 'minimumCanEqual' in this.schema
			? this.schema.minimumCanEqual
			: true;
		return (minimumCanEqual
			? instance >= this.schema.minimum
			: instance > this.schema.minimum
		)
			? instance
			: this.schema.__minimum.call(this, instance);
	},

	maximum: function (instance) {

		if (!('maximum' in this.schema)) return instance;

		var maximumCanEqual = 'maximumCanEqual' in this.schema
			? this.schema.maximumCanEqual
			: true;
		return (maximumCanEqual
			? instance <= this.schema.maximum
			: instance < this.schema.maximum
		)
			? instance
			: this.schema.__maximum.call(this, instance);
	},

	maxDecimal: function (instance) {

		if ('maxDecimal' in this.schema) {

			var factor = Math.pow(10, this.schema.maxDecimal);
			return Math.round(instance * factor) / factor === instance
				? instance
				: this.schema.__maxDecimal.call(this, instance);
		}

		return instance;
	},

	pattern: function (instance) {

		if (!this.schema.pattern) return instance;

		return this.schema.pattern.test(instance)
			? instance
			: this.schema.__pattern.call(this, instance);
	},

	minLength: function (instance) {

		if (this.schema.minLength === undefined) return instance;

		return instance.length >= this.schema.minLength
			? instance
			: this.schema.__minLength.call(this, instance);
	},

	maxLength: function (instance) {

		if (this.schema.maxLength === undefined) return instance;

		return instance.length <= this.schema.maxLength
			? instance
			: this.schema.__maxLength.call(this, instance);
	}
});

Object.extend(Validation, {

	_id: 0,
	Error: _Error
});

function Schema (rawSchema) {

	if (typeof rawSchema === 'string') rawSchema = JSON.parse(rawSchema);

	if (rawSchema instanceof Schema) return rawSchema;

	Object.extend(this, rawSchema);

	var id = typeof this.id === 'string'
		? this.id
		: '__'+(++Schema._id);
	this.constructor.instances[id] = this;
};

Object.extend(Schema.prototype, {

	validate: function (instance) {

		return new Validation(this, instance);
	},

	setFallbacks: function (obj) {

		var adapterName;

		for (var propertyName in obj) {

			adapterName = obj[propertyName];

			if (!(adapterName in Schema.plugins)) throw 'Fallback not registered!';

			this['__'+propertyName] = Schema.plugins[adapterName](propertyName);
		}

		return this;
	}
});

Object.extend(Schema, {

	_id: 0,

	create: function (rawSchema) {

		var validation = Schema.instances.jsonSchemaCore.validate(rawSchema);
		if (validation.isError()) {

			var E = new Error('Invalid schema');
			E.validation = validation
			throw E;
		}

		return validation.instance;
	},

	createSeveral: function (schemaDict) {

		for (var key in schemaDict) {

			if (schemaDict[key].id === undefined) schemaDict[key].id = key;

			schemaDict[key] = this.create(schemaDict[key]);
		}

		return schemaDict;
	},

	instances: {},

	Validation: Validation,

	resolveRefs: function () {

		for (var id in this.instances) {

			Object.resolveRefs(this.instances[id], true);
		}
	},

	plugins: {},

	messages: {

		type: {

			de: function () {

				//return 'Dieser Wert muss vom Typ %s sein.'.sprintf(Schema.instances.jsonSchemaCore.properties.type.labels[this.schema.type].de);
			}
		}
	},

	TOLERANT_FALLBACKS: {

		optional: 'setDefault',
		type: 'castTolerantlyToType',
		enum: 'setDefault',
		requires: 'pushError',
		additionalProperties: 'removeAdditionalProperties',
		maxItems: 'sliceToMaxItems',
		minItems: 'pushError',
		maximum: 'setMaximum',
		minimum: 'setMinimum',
		maxDecimal: 'roundToMaxDecimal',
		pattern: 'pushError',
		minLength: 'pushError',
		maxLength: 'pushError'
	},

	STRICT_FALLBACKS: {

		optional: 'pushError',
		type: 'pushError',
		enum: 'pushError',
		requires: 'pushError',
		additionalProperties: 'pushError',
		maxItems: 'pushError',
		minItems: 'pushError',
		maximum: 'pushError',
		minimum: 'pushError',
		maxDecimal: 'pushError',
		pattern: 'pushError',
		minLength: 'pushError',
		maxLength: 'pushError'
	}
});

Object.extend(Schema.plugins, require('./plugins/default'));

Schema.prototype.setFallbacks(Schema.TOLERANT_FALLBACKS);

Object.extend(Schema.plugins, {

	// adapters - for the core schema

	addToRefs: function (instance) {

		if (instance instanceof Object
			&& typeof instance.$ref === 'string') {

			/// Still no URIs supported
			var toPath = this.path,
				fromPath = instance.$ref.split('.');

			Object.addRef(
				this.instance,
				toPath.clone(),
				fromPath[0] === '#'
					? fromPath.clone()
					: ['__Schema', 'instances'].concat(fromPath)
			);
		}

		return instance;
	},

	instantiateSchema: function (instance) {

		if (instance instanceof Schema || !(instance instanceof Object)) return instance;

		var isThisInstance = instance === this.instance;
		instance = new Schema(instance);
		if (isThisInstance) this.instance = instance;

		if (instance.properties instanceof Object) {

			var props = instance.properties || {};

			for (var key in props) {

				if (props[key] instanceof Object && !(props[key] instanceof Schema))
					props[key] = new Schema(props[key]);
			}
		}

		if (instance.items instanceof Array) {

			var items = instance.items;
			for (var li = items.length, i = 0; i < li; i++) {

				if (items[i] instanceof Object && !(items[i] instanceof Schema))
					items[i] = new Schema(items[i]);
			}
		}
		else if (instance.items instanceof Object) {

			if (!(instance.items instanceof Schema))
				instance.items = new Schema(instance.items);
		}

		if (instance.requires
			&& instance.requires instanceof Object
			&& !(instance.requires instanceof Schema)) {

			instance.requires = new Schema(instance.requires);
		}

		if (instance.additionalProperties instanceof Object
			&& !(instance.additionalProperties instanceof Schema)) {

			instance.additionalProperties = new Schema(instance.additionalProperties);
		}

		if (instance.extends instanceof Object) {

			if (!(instance.extends instanceof Schema)) {

				instance.extends = new Schema(instance.extends);
			}
		}

		if (instance.fallbacks) {

			instance.setFallbacks(instance.fallbacks);
			delete instance.fallbacks;
		}

		Object.resetRefs(instance);

		return instance;
	}
});

var jsonSchemaCore = require('./schemas/jsonSchemaCore');

/* The schema schema cannot validate/adapt itself after having been instantiated from raw JSON.
 * At first we apply all adaptations manually, including:
 * - instantiating (sub)schemas
 * - resolve references
 * - set fallbacks (strict)
 * - eventually make extends change the parent schema
 */

jsonSchemaCore = new Schema(jsonSchemaCore);
jsonSchemaCore.setFallbacks(Schema.STRICT_FALLBACKS);

for (var key in jsonSchemaCore.properties) {

	jsonSchemaCore.properties[key] = new Schema(jsonSchemaCore.properties[key]);
	jsonSchemaCore.properties[key].setFallbacks(Schema.STRICT_FALLBACKS);
}

jsonSchemaCore.properties.type.items = new Schema(jsonSchemaCore.properties.type.items);
jsonSchemaCore.properties.type.items.setFallbacks(Schema.STRICT_FALLBACKS);
jsonSchemaCore.properties.type.items.enum = jsonSchemaCore.properties.type.enum;

jsonSchemaCore.properties.id.pattern = new RegExp(jsonSchemaCore.properties.id.pattern);

jsonSchemaCore.properties.options.items = new Schema(jsonSchemaCore.properties.options.items);
jsonSchemaCore.properties.options.items.setFallbacks(Schema.STRICT_FALLBACKS);

jsonSchemaCore.properties.requires.properties = jsonSchemaCore.properties;

jsonSchemaCore.properties.adapters.items = new Schema(jsonSchemaCore.properties.adapters.items);
jsonSchemaCore.properties.adapters.items.setFallbacks(Schema.STRICT_FALLBACKS);

jsonSchemaCore.properties.fallbacks.additionalProperties = new Schema(jsonSchemaCore.properties.fallbacks.additionalProperties);
jsonSchemaCore.properties.fallbacks.additionalProperties.setFallbacks(Schema.STRICT_FALLBACKS);

jsonSchemaCore.properties.properties.additionalProperties = jsonSchemaCore;

//Luhmannesk moment
//jsonSchemaCore.properties.extends.extends = jsonSchemaCore;

jsonSchemaCore.properties.items.properties = jsonSchemaCore.properties;
jsonSchemaCore.properties.items.items = jsonSchemaCore;

jsonSchemaCore.properties.additionalProperties.properties = jsonSchemaCore.properties;

/* Now, the core schema should be as if it had validated itself.
 * Check it out:
 */

var validation = jsonSchemaCore.validate(jsonSchemaCore);

if (validation.isError()) {

	sys.puts('The core schema is not valid!');
	validation.errors.forEach(function (error) {

		sys.puts(sys.inspect(error));
	});
	sys.puts(sys.inspect(validation));
}

global.__Schema = exports = Schema;

var sys = require('sys');
require('extensions');

exports = {

	// fallbacks

	castTolerantlyToType: function (errorName) {

		return function (instance) {

			var toType = this.schema.type,
				fromType = typeof instance,
				inst;

			if (toType === 'string') {

				if (fromType === 'object') {

					if (
						instance !== null
						&& typeof instance.toString === 'function'
						&& instance.toString !== Object.prototype.toString
						&& instance.toString !== Array.prototype.toString
					) return ''+instance;

					this.pushError(errorName);
					return instance;
				}
				return ''+instance;
			}

			if (toType === 'number') {

				if (fromType !== 'boolean') {

					inst = parseFloat(instance);
					if (isNaN(inst)) {

						this.pushError(errorName);
						return instance;
					}

					return inst;
				}
				if (instance === false) return 0;
				if (instance === true) return 1;
			}

			if (toType === 'integer') {

				if (fromType !== 'boolean') {

					inst = parseInt(instance, 10);
					if (isNaN(inst)) {

						this.pushError(errorName);
						return instance;
					}

					return inst;
				}
				if (instance === false) return 0;
				if (instance === true) return 1;
			}

			if (toType === 'boolean') {

				if (fromType === 'string') {

					// simple and clear
					return !!instance;

					if (instance.trim().toLowerCase() === 'false') return false;

					inst = parseFloat(instance);
					return isNaN(inst) ? true : !!inst;
				}

				if (fromType === 'object') {

					this.pushError(errorName);
					return instance;
				}

				return !!instance;
			}

			if (toType === 'object') {

				if (instance instanceof Array && instance.length === 0) return {};
				if (fromType !== 'object' || instance === null) this.pushError(errorName);
				return instance;
			}

			if (toType === 'array') {

				if (!instance) return [];
				if (instance instanceof Object) {

					if (Object.keys(instance).length === 0) return [];
				}
				this.pushError(errorName);
				return instance;
			}

			if (toType === 'null') {

				this.pushError(errorName);
				return instance;
			}

			if (toType === 'date') {

				var date = new Date(instance);
				if (isNaN(date.getTime())) {
					this.pushError(errorName);
				} else {
					instance = date;
				}
				return instance;
			}
		};
	},

	roundToMaxDecimal: function (errorName) {

		return function (instance) {

			if (this.schema.maxDecimal === undefined) {

				this.pushError(errorName);
				return instance;
			}

			var factor = Math.pow(10, this.schema.maxDecimal);
			return Math[instance < 0 ? 'ceil' : 'floor'](instance * factor) / factor;
		};
	},

	setMinimum: function (errorName) {

		return function (instance) {

			if (this.schema.minimum === undefined) {

				this.pushError(errorName);
				return instance;
			}

			return this.schema.minimum;
		};
	},

	setMaximum: function (errorName) {

		return function (instance) {

			if (this.schema.maximum === undefined) {

				this.pushError(errorName);
				return instance;
			}

			return this.schema.maximum;
		};
	},

	truncateToMaxLength: function (errorName) {

		return function (instance) {

			if (this.schema.maxLength === undefined) {

				this.pushError(errorName);
				return instance;
			}

			return instance.substr(0, this.schema.maxLength);
		};
	},

	setDefault: function (errorName) {

		return function (instance) {

			if ('default' in this.schema) {
				var def = this.schema['default'];
				return typeof def === 'function' ? def.call(this) : def;
			}

			this.pushError(errorName);
		};
	},

	removeAdditionalProperties: function (errorName) {

		return function (instance) {

			if (this.schema.type === 'object') {

				var props = this.schema.properties || {};
				for (var ks = Object.keys(
					instance
				), li = ks.length, i = 0, key; key = ks[i], i < li; i++) {

					if (props[key] === undefined) delete instance[key];
				}

				return instance;
			}

			if (this.schema.items && this.schema.items instanceof Array) {

				for (var li = instance.length, i = this.schema.items.length; i < li; i++) {

					delete instance[i];
				}
			}

			return instance;
		};
	},

	sliceToMaxItems: function (errorName) {

		return function (instance) {

			if (this.schema.maxItems !== undefined) {

				/// instance.length will not change
				/*for (var li = instance.length, i = this.schema.maxItems; i < li; i++) {

					delete instance[i];
				}*/

				if (instance.length > this.schema.maxItems) {

					instance.splice(this.schema.maxItems);
				}
			}
			else this.pushError(errorName);

			return instance;
		};
	},

	pushError: function (errorName) {

		return function (instance) {

			this.pushError(errorName);
			return instance;
		};
	},

	deleteInstance: function (errorName) {

		/*
		Delete instance
		*/
		return function (instance) {

			// Solution needed - undefined means that property still exists
			return undefined;
		};
	}
};

var sys = require('sys');
require('extensions');

module.exports = {

	// fallbacks

	fixture: function (errorName) {

		return function (instance) {

			var toType = this.schema.type,
				fromType = typeof instance,
				inst;

			if (toType === 'string') {

				if (fromType === 'object') {

					if (
						instance !== null
						&& typeof instance.toString === 'function'
						&& instance.toString !== Object.prototype.toString
						&& instance.toString !== Array.prototype.toString
					) return ''+instance;

					this.pushError(errorName);
					return instance;
				}
				return ''+instance;
			}

			if (toType === 'number') {

				if (fromType !== 'boolean') {

					inst = parseFloat(instance);
					if (isNaN(inst)) {

						this.pushError(errorName);
						return instance;
					}

					return inst;
				}
				if (instance === false) return 0;
				if (instance === true) return 1;
			}

			if (toType === 'integer') {

				if (fromType !== 'boolean') {

					inst = parseInt(instance, 10);
					if (isNaN(inst)) {

						this.pushError(errorName);
						return instance;
					}

					return inst;
				}
				if (instance === false) return 0;
				if (instance === true) return 1;
			}

			if (toType === 'boolean') {

				if (fromType === 'string') {

					// simple and clear
					return !!instance;

					if (instance.trim().toLowerCase() === 'false') return false;

					inst = parseFloat(instance);
					return isNaN(inst) ? true : !!inst;
				}

				if (fromType === 'object') {

					this.pushError(errorName);
					return instance;
				}

				return !!instance;
			}

			if (toType === 'object') {

				if (instance instanceof Array && instance.length === 0) return {};
				if (fromType !== 'object' || instance === null) this.pushError(errorName);
				return instance;
			}

			if (toType === 'array') {

				if (!instance) return [];
				if (instance instanceof Object) {

					if (Object.keys(instance).length === 0) return [];
				}
				this.pushError(errorName);
				return instance;
			}

			if (toType === 'null') {

				this.pushError(errorName);
				return instance;
			}

			if (toType === 'date') {

				var date = new Date(instance);
				if (isNaN(date.getTime())) {
					this.pushError(errorName);
				} else {
					instance = date;
				}
				return instance;
			}
		};
	}

};

exports = {

	id: 'jsonSchemaCore',
	title: 'JSON Schema Core Schema',
	description: "This is the JSON Schema for JSON Schemas.",
	type: "object",

	properties: {

		// *

		type: {

			type: ["string", "array"],
			items: {

				type: "string",
				enum: {"$ref": "#.properties.type.enum"}
			},
			uniqueItems: true,
			description: "This is a type definition value. This can be a simple type, or a union type",
			enum: ["string", "object", "array", "boolean", "number", "integer", "null", "any", "date"],
			minItems: 2,
			optional: true,
			"default": "any",
				adapters: 'addToRefs',
				labels: {

					de: {

						string: 'Text',
						object: 'Objekt',
						array: 'Liste',
						boolean: 'wahr/falsch',
						number: 'Zahl',
						integer: 'ganze Zahl',
						'null': 'undefiniert'
					},
					en: {

					}
				}
		},

		id: {

			type: "string",
			optional: true,
			minLength: 1,
			pattern: "[^\\.#]",
			description: "A globally unique ID for this schema. It can be used to reference the schema from another one."
		},

		optional: {

			type: "boolean",
			description: "This indicates that the instance property in the instance object is not required.",
			optional: true,
			"default": false,
				adapters: 'addToRefs'
		},

		enum: {
			type: "array",
			optional: true,
			description: "This provides an enumeration of possible values that are valid for the instance property. It goes only for the first type, if type is an array of types.",
			minItems: 1,
			uniqueItems: true
		},

		description: {

			type: "string",
			optional: true,
			description: "This provides a description of the purpose the instance property. The value can be a string or it can be an object with properties corresponding to various different instance languages (with an optional default property indicating the default description).",
				adapters: 'addToRefs'
		},

		title: {

			type: "string",
			optional: true,
			description: "This provides the title of the property",
				adapters: 'addToRefs'
		},

		"default": {
			type: "any",
			optional: true,
			description: "This indicates the default for the instance property.",
				adapters: 'addToRefs'
		},

		readonly: {
			type: "boolean",
			optional: true,
			description: "This indicates the property can not be changed unless it is undefined.",
				adapters: 'addToRefs'
		},

		// should go with enum
		options: {

			type: "array",
			optional: true,
			items: {
				type: "object",
				properties: {
					label: {
						type: "string",
						optional: true
					},
					value: {
						type: "any"
					}
				}
			},
				adapters: 'addToRefs'
		},

		extends: {

			// You cannot do this, because the schema itself is not optional
			// and so #.properties.extends does inherit from core schema, but
			// isn't as restrictive. So you cannot validate extends in a schema
			/// Better solution needed!
			//extends: {"$ref": "#"},
			type: 'object',
			optional: true,
				adapters: 'addToRefs'
		},

		// must be in object
		requires: {

			type: ["string", "object"],
			properties: {"$ref": "#.properties"},
			optional: true,
			description: "indicates a required property or a schema that must be validated if this property is present",
				adapters: 'addToRefs'
		},

			// just for schema schemas ;)
			nonStandard: {

				type: "boolean",
				optional: true,
				"default": false,
				nonStandard: true
			},

			adapters: {

				// Allow closures
				type: ['string', 'array', 'any'],
				items: {

					type: ['string', 'any']
				},
				optional: true,
				description: "One or several adapters identified by plugin IDs",
				nonStandard: true,
					adapters: 'addToRefs'
			},

			fallbacks: {

				type: "object",
				optional: true,
				additionalProperties: {

					type: "string",
					description: "Identifier for a adapter function that will be executed if a constraint is violated"
				},
				description: "Fallback object, with property names = schema property names",
				nonStandard: true,
					adapters: 'addToRefs'
			},

		// object

		properties: {

			type: "object",
			additionalProperties: {"$ref": "#"},
			description: "This is a definition for the properties of an object value",
			optional: true,
			"default": {},
				adapters: 'addToRefs'
		},

		// array

		items: {

			type: ["object", "array"],
			properties: {"$ref": "#.properties"},
			items: {"$ref": "#"},
			description: "When the value is an array, this indicates the schema to use to validate each item in an array",
			optional: true,
				adapters: 'addToRefs'
		},

		maxItems: {

			type: "integer",
			optional: true,
			description: "When the instance value is an array, this indicates maximum number of items.",
			minimum: 0,
			adapters: 'addToRefs'
		},

		minItems: {

			type: "integer",
			optional: true,
			description: "When the instance value is an array, this indicates minimum number of items.",
			minimum: 0,
				adapters: 'addToRefs'
		},

		// object, array

		additionalProperties: {

			type: ["boolean", "object"],
			properties: {"$ref": "#.properties"},
			description: "This provides a default property definition for all properties that are not explicitly defined in an object type definition.",
			optional: true,
			"default": true,
				adapters: ['addToRefs']
		},

		// number, integer

		minimum: {

			type: "number",
			optional: true,
			description: "This indicates the minimum value for the instance property when the type of the instance value is a number.",
				adapters: 'addToRefs'
		},

		minimumCanEqual: {

			type: "boolean",
			optional: true,
			description: "If the minimum is defined, this indicates whether or not the instance property value can equal the minimum.",
			"default": true,
				adapters: 'addToRefs'
		},

		maximum: {

			type: "number",
			optional: true,
			description: "This indicates the maximum value for the instance property when the type of the instance value is a number.",
				adapters: 'addToRefs'
		},

		maximumCanEqual: {

			type: "boolean",
			optional: true,
			description: "If the maximum is defined, this indicates whether or not the instance property value can equal the maximum.",
			"default": true,
				adapters: 'addToRefs'
		},

		maxDecimal: {

			type: "integer",
			optional: true,
			description: "This indicates the maximum number of decimal places in a floating point number.",
				adapters: 'addToRefs'
		},

		// string

		pattern: {

			// Allow JS RegExp instances
			type: ['string', 'object'],
			format: "regex",
			description: "When the instance value is a string, this provides a regular expression that a instance string value should match in order to be valid.",
			optional: true,
				adapters: [
					'addToRefs',
					function (instance, walker) {

						if (instance instanceof RegExp) return instance;

						return new RegExp(instance);
					}
				]
		},

		maxLength: {

			type: "number",
			optional: true,
			description: "When the instance value is a string, this indicates maximum length of the string.",
				adapters: 'addToRefs'
		},

		minLength: {

			type: "number",
			optional: true,
			description: "When the instance value is a string, this indicates minimum length of the string.",
			adapters: 'addToRefs'
		},

		// string, number, integer

		format: {
			type: "string",
			optional: true,
			description: "This indicates what format the data is among some predefined formats which may include:\n\ndate - a string following the ISO format \naddress \nschema - a schema definition object \nperson \npage \nhtml - a string representing HTML",
			adapters: 'addToRefs'
		}
	},

	adapters: [
		'instantiateSchema',
		'addToRefs'
	]
};