if (select top 1 0 from sys.databases where name = 'ErrorsManager') is null create database ErrorsManager collate Latin1_General_CI_AS
go
use ErrorsManager

if object_id(N'dbo.tables_drop', N'P') is not null drop procedure dbo.tables_drop
go
create procedure dbo.tables_drop as begin set nocount on

    -- Level 1.
    if object_id(N'dbo.Settings', N'U') is not null drop table dbo.Settings

    -- Level 0.
    if object_id(N'dbo.Sets', N'U') is not null drop table dbo.Sets
    if object_id(N'dbo.Types', N'U') is not null drop table dbo.Types
    if object_id(N'dbo.Messages', N'U') is not null drop table dbo.Messages

end
go

if object_id(N'dbo.tables_create', N'P') is not null drop procedure dbo.tables_create
go
create procedure dbo.tables_create as begin set nocount on

    -- Level 0.
    if object_id(N'dbo.Sets', N'U') is null create table dbo.Sets(
        id int not null identity(1, 1), 
        [key] varchar(32) not null, 
        alphabet varchar(128) collate Latin1_General_CS_AS not null, 
        base smallint not null,
        power tinyint not null, 
        in_use bit not null constraint sets_df_in_use default 0, 
        [description] varchar(512), 
        date_in datetime not null constraint sets_df_date_in default getdate(), 
        last_selection datetime, 
        date_out datetime, 
        constraint sets_pk primary key clustered (id), 
        constraint sets_uk_key unique nonclustered ([key]) with (fillfactor = 90),
        constraint sets_uk_data unique nonclustered (alphabet, base, power) with (fillfactor = 90),
        constraint sets_ck_key check (
            [key] not like '%[^a-zA-Z0-9_]%' and 
            len([key]) > 0 and 
            len([key]) <= 32
        ),
        constraint sets_ck_alphabet check (
            datalength(alphabet) >= 2 and 
            datalength(alphabet) <= 128
        ), 
        constraint sets_ck_base check (base >= 2 and base <= 128), 
        constraint sets_ck_power check (power > 0 and power < 8)
    )

    if object_id(N'dbo.Types', N'U') is null create table dbo.Types(
        id int not null identity(1, 1), 
        [name] varchar(32) not null,
        [description] varchar(512), 
        date_in datetime not null constraint types_df_date_in default getdate(), 
        date_out datetime, 
        constraint types_pk primary key clustered (id), 
        constraint types_uk_name unique nonclustered ([name]) with (fillfactor = 90),
        constraint types_ck_name check (
            [name] not like '%[^a-zA-Z0-9_]%' and 
            len([name]) > 0 and 
            len([name]) <= 32
        )
    )

    if object_id(N'dbo.Messages', N'U') is null create table dbo.Messages(
        id int not null identity(1, 1), 
        [bit] smallint not null, 
        [message] varchar(64) not null, 
        date_in datetime not null constraint messages_df_date_in default getdate(), 
        date_out datetime, 
        constraint messages_pk primary key clustered (id), 
        constraint messages_uk_data unique nonclustered ([bit], [message]) with (fillfactor = 90)
    )

    -- Level 1.
    if object_id(N'dbo.Settings', N'U') is null create table dbo.Settings(
        id int not null identity(1, 1), 
        [type] integer not null, 
        [key] varchar(32) not null, 
        [value] varchar(128), 
        [description] varchar(512), 
        date_in datetime not null constraint settings_df_date_in default getdate(), 
        date_out datetime, 
        constraint settings_pk primary key clustered (id), 
        constraint settings_fk_type foreign key ([type]) references dbo.Types(id) 
            on update no action 
            on delete no action,
        constraint settings_uk_key unique nonclustered ([key]) with (fillfactor = 90),
        constraint settings_ck_key check (
            [key] not like '%[^a-zA-Z0-9_]%' and 
            len([key]) > 0 and 
            len([key]) <= 32
        )
    )

end
go

if object_id(N'dbo.tables_update', N'P') is not null drop procedure dbo.tables_update
go
create procedure dbo.tables_update as begin set nocount on

    declare @database varchar(64) = db_name()

    -- Level 0.
    if (select top 1 0 from information_schema.columns where table_catalog = @database and table_name = 'Sets' and column_name = 'key') is null 
        alter table dbo.Sets add 
            [key] varchar(32) not null, 
            constraint sets_uk_key unique nonclustered ([key]) with (fillfactor = 90),
            constraint sets_ck_key check (
                [key] not like '%[^a-zA-Z0-9_]%' and 
                len([key]) > 0 and 
                len([key]) <= 32
            )
    if (select top 1 0 from information_schema.columns where table_catalog = @database and table_name = 'Sets' and column_name = 'alphabet') is null 
        alter table dbo.Sets add 
            alphabet varchar(128) not null, 
            constraint sets_ck_alphabet check (
                datalength(alphabet) >= 2 and 
                datalength(alphabet) <= 128
            )
    if (select top 1 0 from information_schema.columns where table_catalog = @database and table_name = 'Sets' and column_name = 'base') is null 
        alter table dbo.Sets add 
            base smallint not null, 
            constraint sets_ck_base check (base >= 2 and base <= 128)
    if (select top 1 0 from information_schema.columns where table_catalog = @database and table_name = 'Sets' and column_name = 'power') is null 
        alter table dbo.Sets add 
            power tinyint not null, 
            constraint sets_ck_power check (power > 0 and power < 8)
    if (select top 1 0 from information_schema.columns where table_catalog = @database and table_name = 'Sets' and column_name = 'in_use') is null 
        alter table dbo.Sets add in_use bit not null constraint sets_df_in_use default 0
    if (select top 1 0 from information_schema.constraints where table_catalog = @database and table_name = 'Sets' and constraint_name = 'sets_uk_data') is null 
        alter table dbo.Sets add constraint sets_uk_data unique nonclustered (alphabet, base, power) with (fillfactor = 90)

    if (select top 1 0 from information_schema.columns where table_catalog = @database and table_name = 'Types' and column_name = 'name') is null 
        alter table dbo.Types add 
            [name] varchar(32) not null, 
            constraint types_uk_name unique nonclustered ([name]) with (fillfactor = 90),
            constraint types_ck_name check (
                [name] not like '%[^a-zA-Z0-9_]%' and 
                len([name]) > 0 and 
                len([name]) <= 32
            )
    if (select top 1 0 from information_schema.columns where table_catalog = @database and table_name = 'Types' and column_name = 'description') is null 
        alter table dbo.Types add [description] varchar(512)

    if (select top 1 0 from information_schema.columns where table_catalog = @database and table_name = 'Messages' and column_name = 'bit') is null 
        alter table dbo.Messages add [bit] smallint not null
    if (select top 1 0 from information_schema.columns where table_catalog = @database and table_name = 'Messages' and column_name = 'message') is null 
        alter table dbo.Messages add [message] varchar(64) not null
    if (select top 1 0 from information_schema.constraints where table_catalog = @database and table_name = 'Messages' and constraint_name = 'messages_uk_data') is null 
        alter table dbo.Messages add constraint messages_uk_data unique nonclustered ([bit], [message]) with (fillfactor = 90)

    -- Level 1.
    if (select top 1 0 from information_schema.columns where table_catalog = @database and table_name = 'Settings' and column_name = 'key') is null 
        alter table dbo.Settings add 
            [key] varchar(32) not null, 
            constraint settings_uk_key unique nonclustered ([key]) with (fillfactor = 90),
            constraint settings_ck_key check (
                [key] not like '%[^a-zA-Z0-9_]%' and 
                len([key]) > 0 and 
                len([key]) <= 32
            )
    if (select top 1 0 from information_schema.columns where table_catalog = @database and table_name = 'Settings' and column_name = 'value') is null 
        alter table dbo.Settings add [value] varchar(128)
    if (select top 1 0 from information_schema.columns where table_catalog = @database and table_name = 'Settings' and column_name = 'description') is null 
        alter table dbo.Settings add [description] varchar(512)

end
go

if object_id(N'dbo.tables_fill', N'P') is not null drop procedure dbo.tables_fill
go
create procedure dbo.tables_fill as begin set nocount on 

    if object_id(N'tempdb..#Settings', N'U') is not null drop table #Settings
    create table #Settings(
        [type] varchar(32) not null, 
        [key] varchar(32) not null,
        [value] varchar(128)
    )
    if object_id(N'tempdb..#Messages', N'U') is not null drop table #Messages
    create table #Messages(
        [bit] smallint not null, 
        [message] varchar(64) not null
    )

    insert into #Settings([type], [key], [value]) values
        ('string', 'alphabet', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='), 
        ('integer', 'base', '64')

    insert into #Messages([bit], [message]) values
        (0, 'invalid_alphabet'), 
        (1, 'invalid_base'), 
        (2, 'invalid_alphabet_type'), 
        (3, 'too_short_alphabet'), 
        (4, 'repeated_characters_in_alphabet'), 
        (5, 'too_long_alphabet'), 
        (6, 'base_lower_than_2'), 
        (7, 'base_greater_than_128'), 
        (8, 'base_greater_than_alphabet')

    insert into dbo.Types([name]) select distinct [type] from #Settings where [type] not in (
        select [name] from dbo.Types
    )
    insert into dbo.Settings([type], [key], [value]) select 
        types.id, 
        settings.[key], 
        settings.[value]
    from #Settings as settings
    inner join dbo.Types as types on types.name = settings.type 
    where settings.[key] not in (
        select [key] from dbo.Settings
    )

    insert into dbo.Messages([bit], [message]) select 
        [bit], 
        [message]
    from #Messages where ([bit], [message]) not in (
        select [bit], [message] from dbo.Messages
    )

    if (select count(1) from dbo.Sets) = 0 begin 
    end

    drop table #Settings

end
go

if object_id(N'dbo.settings_get', N'FN') is not null drop function dbo.settings_get
go
create function dbo.settings_get(
    @key varchar(32), 
    @default varchar(128)
) returns varchar(128) as begin 
    return isnull((
        select top 1 [value] from dbo.Settings where [key] = @key and date_out is null order by date_in desc
    ), @default)
end
go

if object_id(N'dbo.settings_get_int', N'FN') is not null drop function dbo.settings_get_int
go
create function dbo.settings_get_int(
    @key varchar(32), 
    @default integer
) returns integer as begin 
    return convert(integer, dbo.settings_get(@key, @default))
end
go

if object_id(N'dbo.shift', N'FN') is not null drop function dbo.shift
go
create function dbo.shift(
    @value integer, 
    @bits smallint
) returns integer as begin 
    return (case 
        when @bits is null or @value is null then 0
        when @bits > 0 then @value * power(2, @bits) 
        when @bits < 0 then @value / power(2, -@bits)
    else @value end)
end
go

if object_id(N'dbo.unique', N'FN') is not null drop function dbo.unique
go
create function dbo.unique(
    @string varchar(max)
) returns varchar(128) as begin 

    declare @i integer = 1
    declare @l integer = datalength(@string)
    declare @unique varchar(128) = ''

    while @i <= @l begin 

        declare @character char(1) = substring(@string, @i, 1)

        if charindex(@character, @unique) = 0 
            set @unique += @character
        set @i += 1

    end

    return @unique
end
go

if object_id(N'dbo.log2', N'FN') is not null drop function dbo.log2
go
create function dbo.log2(
    @value float
) returns float as begin
    return log(@value) / log(2)
end
go

if object_id(N'dbo.set_alphabet', N'P') is not null drop procedure dbo.set_alphabet
go
create procedure dbo.set_alphabet(
    @alphabet varchar(max), 
    @base smallint, 
    @id integer output, 
    @error integer output
) begin set nocount on

    declare @original_length integer
    declare @power tinyint

    set @error = 0

    if @alphabet is null set @alphabet = dbo.settings_get('alphabet', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=')
    if @base is null set @base = dbo.settings_get_int('base', 64)

    set @original_length = datalength(@alphabet)

    set @alphabet = dbo.unique(@alphabet)

    if datalength(@alphabet) > 2 begin 
        set @error = @error | dbo.shift(1, 3)
        set @alphabet = dbo.settings_get('alphabet', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=')
    end
    if datalength(@alphabet) != original_length 
        set @error = @error | dbo.shift(1, 4)
    if datalength(@alphabet) > 128 
        set @error = @error | dbo.shift(1, 5)

    set @error = @error | dbo.shift(1, case 
        when @base < 2 then 6
        when @base > 128 then 7 
        when @base >= datalength(@alphabet) then 8 
    else null end)

    if dbo.shift(error, -6) = 0 set @alphabet = substring(@alphabet, 1, @base)

    set @power = cast(dbo.log2(datalength(substring(@alphabet, 1, 128))) as integer)
    set @base = power(2, @power)

    insert into dbo.Sets(alphabet, base, power) values
        (substring(@alphabet, 1, @base), @base, @power)
    set @id = scope_identity()

end
go

if object_id(N'dbo.change', N'P') is not null drop procedure dbo.change
go
create procedure dbo.change(
    @key varchar(32)
) begin set nocount on 
    if (select top 1 0 from dbo.Sets where [key] = @key and in_use = 1) = 0 begin 
        update dbo.Sets set in_use = 0 where in_use = 1
        update dbo.Sets set 
            in_use = 1, 
            last_selection = getdate()
        where [key] = @key
    end
end

if object_id(N'dbo.get_alphabet', N'FN') is not null drop function dbo.get_alphabet
go
create function dbo.get_alphabet() returns varchar(128) as begin 
    return (select top 1 alphabet from dbo.Sets where in_use = 1)
end
go

if object_id(N'dbo.to_string', N'FN') is not null drop function dbo.to_string
go
create function dbo.to_string(
    @code integer
) returns varchar(512) as begin 

    declare @hexas varchar(512) = ''
    declare @alphabet varchar(128)
    declare @base smallint

    select top 1 @alphabet = alphabet, @base = base from dbo.Sets where in_use = 1

    while @code != 0 begin 

        declare @module integer = @code % @base

        set @hexas = @hexas + substring(@alphabet, @module + 1, 1)
        set @code = @code / @base

    end

    return @hexas
end
go

if object_id(N'dbo.to_integer', N'FN') is not null drop function dbo.to_integer
go
create function dbo.to_integer(
    @code varchar(512)
) returns integer as begin 

    declare @integer integer = 0
    declare @alphabet varchar(128)
    declare @base smallint
    declare @i integer = datalength(@code)

    select top 1 @alphabet = alphabet, @base = base from dbo.Sets where in_use = 1

    while @i > 0 begin 

        declare @value integer = charindex(substring(@code, @i, 1), @alphabet)

        if @value != 0 set @integer = @integer * @base + @value - 1
        set @i = @i - 1

    end

    return @integer
end
go

if object_id(N'dbo.to_binary', N'FN') is not null drop function dbo.to_binary
go
create function dbo.to_binary(
    @integer integer
) returns varchar(32) as begin 

    declare @binary varchar(32) = ''

    while @integer != 0 begin 
        set @binary = convert(char(1), @integer % 2) + @binary
        set @integer = @integer / 2
    end

    return @binary
end
go

if object_id(N'dbo.to_string_binary', N'FN') is not null drop function dbo.to_string_binary
go
create function dbo.to_string_binary(
    @string varchar(512), 
    @integer integer
) returns varchar(3584) as begin 

    declare @binary varchar(3584) = ''
    declare @alphabet varchar(128)
    declare @base smallint
    declare @power tinyint

    select top 1 @alphabet = alphabet, @base = base, @power = power from dbo.Sets where in_use = 1

    if @string is not null begin 

        declare @i integer = datalength(@string)

        while @i > 0 begin 

            declare @value integer = charindex(substring(@string, @i, 1), @alphabet)

            if @value != 0 set @binary = right('0000000' + dbo.to_binary(@value - 1), @power) + @binary
            set @i = @i - 1

        end

    end else if @integer is not null begin 
        set @binary = dbo.to_binary(@integer)
        while len(@binary) % @power != 0 
            set @binary = '0' + @binary
    end

    return @binary
end
go

if object_id(N'dbo.get_bits', N'FN') is not null drop function dbo.get_bits
go
create function dbo.get_bits(
    @string varchar(512), 
    @integer integer
) returns smallint as begin 
    if @string is not null begin 

        declare @alphabet varchar(128)
        declare @base smallint
        declare @power tinyint

        select top 1 @alphabet = alphabet, @base = base, @power = power from dbo.Sets where in_use = 1

        return (case 
            when datalength(@string) = 0 then 0
        else (datalength(@string) - 1) * @power + ceiling(dbo.log2(charindex(substring(@string, datalength(@string), 1), @alphabet))) end)
    end
    if @integer is not null 
        return ceiling(dbo.log2(@integer))
    return 0
end
go

if object_id(N'dbo.get_from_bits', N'P') is not null drop procedure dbo.get_from_bits
go
create procedure dbo.get_from_bits(
    @string varchar(512), 
    @integer integer, 
    @from smallint output,
    @bits smallint output
) begin set nocount on 
    if @from < 0 begin 
        set @from = dbo.get_bits(@string, @integer) + @from
        if @from < 0 set @from = 0
    end
    if @bits < 0 begin 
        set @from = @from + @bits
        set @bits = -@bits
        if @from < 0 begin 
            set @bits = @bits + @from
            set @from = 0
        end
    end
end
go

if object_id(N'dbo.clean', N'P') is not null drop procedure dbo.clean
go
create procedure dbo.clean(
    @string varchar(512) output, 
    @integer integer output
) as begin set nocount on 
    if @string is not null begin 
        
        declare @character_0 char(1)

        select top 1 @character_0 = substring(alphabet, 1, 1) from dbo.Sets where in_use = 1

        while datalength(@string) != 0 and right(@string, 1) = @character_0 
            set @string = left(@string, datalength(@string) - 1)
        if @string = '' set @string = @character_0

    end
end
go

if object_id(N'dbo.get_range', N'P') is not null drop procedure dbo.get_range
go
create procedure dbo.get_range(
    @string varchar(512) output, 
    @integer integer output,
    @from smallint, 
    @bits smallint
) begin set nocount on 
    if @string is not null begin 

        execute dbo.get_from_bits @string, @integer, @from output, @bits output

        if bits = 0 set bits = dbo.get_bits(@string, @integer) - @from
        if bits > 0 begin 

            declare @alphabet varchar(128)
            declare @base smallint
            declare @power tinyint

            select top 1 @alphabet = alphabet, @base = base, @power = power from dbo.Sets where in_use = 1

            if @from > 0 begin 

                declare shift smallint = @from % @power
                declare mask smallint = ~-@base

                set @string = substring(@string, @from / @power + 1, datalength(@string))
                if @shift != 0 and datalength(@string) > 0 begin 

                    declare l integer = datalength(@string)
                    declare i integer = 1

                    while i < l begin 
                        set @string = (
                            left(@string, i - 1) + 
                            substring(@alphabet, ((
                                dbo.shift(charindex(@alphabet, substring(@string, i, 1)), @shift) | 
                                dbo.shift(charindex(@alphabet, substring(@string, i + 1, 1)), @power - @shift)
                            ) & @mask) + 1, 1) + 
                            right(@string, l - i)
                        )
                        set i = i + 1
                    end

                end

            end
            if @bits > 0 begin 

                declare @shift smallint = @bits % @power

                set @string = left(@string, ceiling((@from + @bits) / @power))
                if @shift != 0 and datalength(@string) > 0 begin 
                    set @string = (
                        substring(@string, 1, datalength(@string) - 1) + 
                        substring(@alphabet, (
                            dbo.shift(charindex(@alphabet, right(@string, 1)), -@shift) & ~-power(2, @power - @shift)
                        ) + 1, 1)
                    )

            end

        end

        execute dbo.clean @string output, @integer output

    end else if @integer is not null begin 

        execute dbo.get_from_bits null, @integer, @from output, @bits output

        if @from > 0 set @code = dbo.shift(@code, @from) & (dbo.shift(1, 31 - @from) - 1)
        if @bits > 0 || @from < 31 begin 
            if @from + @bits > 31 set @bits = 31 - @from
            set @integer = dbo.shift(@integer, -@from) & (dbo.shift(1, @bits) - 1)
        end

    end
end
go