/*
 * File: imprima_calendario.c
 * --------------------------
 * Este programa imprime um calendario para o ano dado pelo 
 * usuario. (O ano deve ser pelo menos 1900.)
 *
 * Este programa foi adaptado do livro "The Art and Science of C", de 
 * Eric S. Roberts.  Veja em:
 * 
 * http://cseng.awl.com/bookdetail.qry?ISBN=0-201-54322-2&ptype=0
 * 
 */

#include <stdio.h>

/* ************************************************************
 * Constantes:
 * -----------
 * Os dias da semana sao representados pelos inteiros de 0 a 6.
 * Os meses do ano sao representados pelos inteiros de 1 a 12.
 */

#define domingo    0
#define segunda    1
#define terca      2
#define quarta     3
#define quinta     4
#define sexta      5
#define sabado     6

#define VERDADEIRO 1
#define FALSO      0
/****************************************************************/
/* Prototipos de funcoes */
/****************************************************************/
void forneca_instrucoes(void);
int obtenha_ano(void);
void imprima_calendario(int ano);
void imprima_calendario_do_mes(int mes, int ano);
void indente_primeira_linha(int dia_da_semana);
int numero_de_dias_do_mes(int mes, int ano);
int primeiro_dia_do_mes(int mes, int ano);
int eh_bissexto(int ano);
void imprima_cabecalho(int mes, int ano);

/****************************************************************/
/* Programa principal */
/****************************************************************/
int main()
{
    int ano;

    forneca_instrucoes();
    ano = obtenha_ano();
    imprima_calendario(ano);

    return 0;
}

/*****************************************************************
 * Funcao: forneca_instrucoes()
 * Uso: forneca_instrucoes();
 * --------------------------
 * Esta funcao imprime uma mensagem com intrucoes
 * para o usuario.
 */

void forneca_instrucoes(void)
{
    printf("Este programa imprime o calendario para um dado ano.\n");
    printf("O ano nao pode ser anterior a 1900.\n");
}

/*****************************************************************
 * Funcao: obtenha_ano()
 * Uso: ano = obtenha_ano();
 * ---------------------------
 * Esta funcao le um ano digitado pelo usuario. Se o usuario fornece 
 * um ano anterior a 1900, a funcao avisa que o ano deve ser pelo menos 1900.
 * Quando um tal valor e' fornecido, a funcao devolve esse valor.
 */

int obtenha_ano(void)
{
    int ano;

    while (VERDADEIRO) {
	printf("Calendario de que ano? ");
	scanf("%d", &ano);
	if (ano >= 1900){
            printf("Calendario de %d\n\n", ano);
	    return ano;
        }
	printf("O ano precisa ser pelo menos 1900.\n");
    }
}

/*****************************************************************
 * Funcao: imprima_calendario()
 * Uso: imprima_calendario(ano);
 * -----------------------------
 * Esta funcao imprime o calendario do ano dado.
 */

void imprima_calendario(int ano)
{
    int mes;
    
    for (mes = 1; mes <= 12; mes++) {
	imprima_calendario_do_mes(mes, ano);
	printf("\n");
    }
}

/****************************************************************
 * Funcao: imprima_calendario_do_mes()
 * Uso: imprima_calendario_do_mes(mes, ano);
 * ---------------------------------------
 * Esta funcao imprime o calendario de um dado mes e ano. 
 */

void imprima_calendario_do_mes(int mes, int ano)
{
    int dia_da_semana, n_dias, dia;

    imprima_cabecalho(mes, ano);
    printf(" Do Se Te Qu Qu Se Sa\n");
    n_dias = numero_de_dias_do_mes(mes, ano);
    dia_da_semana = primeiro_dia_do_mes(mes, ano);
    indente_primeira_linha(dia_da_semana);
    for (dia = 1; dia <= n_dias; dia++) {
	printf(" %2d", dia);
	if (dia_da_semana == sabado)
	    printf("\n");
	dia_da_semana = (dia_da_semana + 1) % 7;
    }
    if (dia_da_semana != domingo)
	printf("\n");
}

/****************************************************************
 * Funcao: imprima_cabecalho()
 * Uso: imprima_cabecalho(mes, ano);
 * ---------------------------------
 * Esta funcao imprime um cabecalho para 
 * o calendario do mes do ano dado.
 */

void imprima_cabecalho(int mes, int ano)
{
  switch(mes) {
  case  1: printf("    Janeiro"); break;
  case  2: printf("    Fevereiro"); break; 
  case  3: printf("    Marco"); break; 
  case  4: printf("    Abril"); break; 
  case  5: printf("    Maio"); break; 
  case  6: printf("    Junho"); break; 
  case  7: printf("    Julho"); break; 
  case  8: printf("    Agosto"); break; 
  case  9: printf("    Setembro"); break; 
  case 10: printf("    Outubro"); break; 
  case 11: printf("    Novembro"); break; 
  case 12: printf("    Dezembro"); break; 
  default: printf("Nao pode acontecer!\n"); exit(0);
  }

  printf(" %d\n", ano);
}

/*
 * Funcao: indente_primeira_linha()
 * Uso: indente_primeira_linha(dia_da_semana);
 * -------------------------------------------
 * Esta funcao indenta a primeira linha de um calendario
 * de um mes, imprimindo o numero suficiente de brancos
 * para colocar o cursor na posicao correta para a impressao
 * do primeiro dia daquele mes, que cai no dia da semana
 * dado como parametro (dia_da_semana).
 */

void indente_primeira_linha(int dia_da_semana)
{
    int i;

    for (i = 0; i < dia_da_semana; i++) {
        printf("   ");
    }
}

/*
 * Funcao: numero_de_dias_do_mes()
 * Uso: n_dias = numero_de_dias_do_mes(mes, ano);
 * ----------------------------------------------
 * Esta funcao devolve o numero de dias do dado mes do 
 * dado ano.
 */

int numero_de_dias_do_mes(int mes, int ano)
{
    switch (mes) {
      case 2:
        if (eh_bissexto(ano)) return 29;
        return 28;
      case 4: case 6: case 9: case 11:
        return 30;
      default:
        return 31;
    }
}

/*
 * Funcao: primeiro_dia_do_mes();
 * Uso: dia_da_semana = primeiro_dia_do_mes(mes, ano);
 * ---------------------------------------------------
 * Esta funcao devolve o dia da semana em que cai o dia
 * primeiro do mes dado do ano dado.
 * 
 * O metodo que usamos para determinar este dia da semana
 * 'e simples: usamos o fato que 1/1/1900 foi uma 
 * segunda-feira e contamos o numero de dias decorridos 
 * desde entao ate a data em questao.
 */

int primeiro_dia_do_mes(mes, ano)
{
    int dia_da_semana, i;

    dia_da_semana = segunda;
    for (i = 1900; i < ano; i++) {
        dia_da_semana = (dia_da_semana + 365) % 7;
        if (eh_bissexto(i)) dia_da_semana = (dia_da_semana + 1) % 7;
    }
    for (i = 1; i < mes; i++) {
        dia_da_semana = (dia_da_semana + numero_de_dias_do_mes(i, ano)) % 7; 
    }
    return dia_da_semana;
}

/*
 * Funcao: eh_bissexto()
 * Uso: if (eh_bissexto(ano)) . . .
 * ----------------------------------
 * Esta funcao devolve VERDADEIRO se o dado ano
 * 'e bissexto.
 */

int eh_bissexto(int ano)
{
    return  ((ano % 4 == 0) && (ano % 100 != 0))
             || (ano % 400 == 0);
}