//version=teste;
import std.stdio;
import std.intrinsic; //inp/outp
import std.stream;
import std.cstream;
import std.c.time;
import std.string; //toString();
//Constantes de bytes
static const ushort Dados=0x0378;
static const ushort Status=Dados+1;
static const ushort Control=Dados+2;
//Constantes do programa
static const ubyte Clock=    0b00000001;
static const ubyte NewByte=  0b00000010;
static const ubyte Init=     0b00000100;
static const ubyte IOControl=0b00001000;
//Controlo dos buffers da LPT
static const ubyte DIRECT=   0b11110000;//UNSET para receber; SET para enviar
//tempo de duracao de clocks (ms)
static const ushort Wait=500;

ubyte envia(ubyte dados)
{

    return(outp(Dados, dados));
}

void impulso(ubyte qual)
{
    //estado antes do nosso impulso
    ubyte actual=inp(Control);
    //calcular byte com bit de impulso
    set(qual);
    msleep(Wait); //aguardar o tempo definido
    //restaurar o estado anterior
    unset(qual);
    msleep(Wait); //esperar ate o sinal estabilizar
    return; //activar e desactivar o impulso
}

void set (ubyte sinal)
{
    ubyte actual=inp(Control);
    outp(Control, cast(ubyte)(actual&~sinal));
}

void unset(ubyte sinal)
{
    ubyte actual=inp(Control);
    outp(Control, cast(ubyte)(sinal|actual));
}

void init(ubyte chave)
{
    //garantir modo de output
    unset (IOControl);
    set (DIRECT);
    outp(Dados, chave); //colocar a chave na LPT
    //impulso de init nao eh invertido, teremos de o efectuar manualmente!
    ubyte actual=inp(Control);
    outp(Control, cast(ubyte)(Init|actual)); //activar
    msleep(Wait);
    outp(Control, cast(ubyte)(actual&~Init)); //desactivar
    return;
}

ubyte processar(in ubyte dados)
{
    unset(IOControl); //desactivar saida de dados da CPLD
    set(DIRECT); //Activar a saida de dados LPT
    outp(Dados, dados);
    impulso(NewByte);
    for (auto i=0u; i<8u; i++)
    {
        writefln("bit "~toString(i));
        impulso(Clock); //8 impulsos de clock
    }
    unset(DIRECT); //Desactivar a saida de dados da LPT (activar entrada)
    set(IOControl); //activar saida de dados da CPLD
    return inp(Dados); //ler dados e retornar o byte encriptado
}

version (teste)
{
int main ()
{
    unset(0xff); //desligar tudo
    outp(Dados, 0x00); //nada nos dados
    ubyte escolha, bDados, bStatus, bControlo;
    bDados=inp(Dados);
    ubyte _byte;
    while (true)
    {
        //actualizar registos
        bStatus=inp(Status);
        bControlo=inp(Control);
        writefln("INTERFACE PARALELA: PROGRAMA DE TESTE\n");
        writefln("Bytes dados: "~toString(bDados)~" controlo: "~toString(bControlo)~" Status: "~toString(bStatus)~"\n");
        writefln("1 - Enviar dados\n2 - Receber dados\n3 - Activar bit\n4 - Desactivar bit\n0 - Sair");
        writef("Escolha: ");
        scanf("%d", &escolha);
        switch (escolha)
        {
            case 1:
                writef("Dados a enviar: ");
                scanf("%d", &_byte);
                outp(Dados, _byte);
                break;
            case 2: bDados=inp(Dados);
                break;
            case 3:
                writefln("1 - Clock\n2 - NewByte\n3 - Init\n4 - IOControl\n5 - Direcao");
                writef("Escolha: ");
                scanf("%d", &_byte);
                switch(_byte)
                {
                    case 1: set(Clock);
                    break;
                    case 2: set(NewByte);
                    break;
                    case 3: init(0);
                    break;
                    case 4: set(IOControl);
                    break;
                    case 5: set(DIRECT);
                    break;
                    default: break;
                }
                break;
            case 4:
                writefln("1 - Clock\n2 - NewByte\n3 - Init\n4 - IOControl\n5 - Direcao");
                writef("Escolha: ");
                scanf("%d", &_byte);
                switch(_byte)
                {
                    case 1: unset(Clock);
                    break;
                    case 2: unset(NewByte);
                    break;
                    case 3: init(0);
                    break;
                    case 4: unset(IOControl);
                    break;
                    case 5: unset(DIRECT);
                    break;
                    default: break;
                }
                break;
            case 0: return 0;
            default: break;
        }
    }
    return 0;
}
} //versao de teste
else
{
int main()
{
    ubyte tamanho, chave;
    uint lido;
    char[] nome;
    writef("Introduza o tamanho da matrix de encriptacao: ");
    scanf("%d", &tamanho);
    assert(tamanho!=0);
    ubyte[] buffer1, buffer2;
    buffer1.length=buffer2.length=tamanho*tamanho;
    writef("Introduza o nome do ficheiro a encriptar: ");
    din.flush();
    nome=din.readLine();
    writefln("Abrindo " ~ nome);
    File fichEnt=new File(nome, FileMode.In);
    writef("Introduza o nome do ficheiro de saida: ");
    din.flush();
    nome=din.readLine();
    writefln("Abrindo " ~ nome);
    File fichSai=new File(nome, FileMode.OutNew); //apagar ficheiro se ja existente

    writef ("Insira a chave de encriptacao: ");
    scanf("%d", &chave);
    writefln("Enviado a chave para o dispositivo...");
    unset(0xFF);
    init(chave); //inicializar o dispositivo com esta chave

    size_t linear(size_t x, size_t y) { return (y*tamanho+x); }
    ulong qt=0;
    lido=fichEnt.read(buffer1); //Ler dados para o buffer. lido obtem quantos bytes foram obtidos
    assert(lido!=0, "O ficheiro esta vazio"); //ficheiro nao pode estar vazio
    while (lido!=0) //enquanto houver dados nao-baralhados
    {
        for (size_t x=0; x<tamanho; x++)
            for (size_t y=0; y<tamanho; y++)
            {
                qt++;
                writefln("Processando byte numero: "~toString(qt));
                buffer2[linear(y,x)]=processar(buffer1[linear(x, y)]);
            }
        fichSai.write(buffer2); //escrever dados baralhados
        lido=fichEnt.read(buffer1); //ler mais
    }

    buffer1.length=0;
    buffer2.length=0;
    fichEnt.close();
    fichSai.close();
    delete fichEnt;
    delete fichSai;
    return 0;
}
}
