// Rshare - a file sharing program written in Rust.
// Copyright (C) 2017 Cooper Paul EdenDay
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// Electronic mail: cedenday@protonmail.com
// Paper mail: 100 Front Street, #31, Bath, Maine, 04530, USA

pub use constants::PART_SIZE;

use std;
use byteorder;
use base64;
use crc;
use zopfli;
use inflate;
use bincode;
use ring;
use rusqlite::Connection;
use app_dirs;
use APP_DATA;
use byteorder::ByteOrder;
use std::io::{Read, Write, Seek, SeekFrom};

pub fn connect() -> Connection {
    let mut path = app_dirs::app_dir(app_dirs::AppDataType::UserData, &APP_DATA, "")
        .expect("Couldn't open user data directory.");
    path.push("metadata.db");
    let connection = Connection::open(path.as_path()).expect("Couldn't open database.");

    connection.execute("PRAGMA foreign_keys = ON", &[]).unwrap();
    connection
        .execute("CREATE TABLE IF NOT EXISTS metadata (\
        id INTEGER PRIMARY KEY, \
        num_parts INTEGER NOT NULL, \
        file_path TEXT NOT NULL, \
        key BLOB NOT NULL UNIQUE, \
        nonce BLOB NOT NULL UNIQUE\
        )",
                 &[])
        .unwrap();
    connection
        .execute("CREATE TABLE IF NOT EXISTS parts (\
        hash BLOB NOT NULL UNIQUE, \
        signature BLOB NOT NULL, \
        part_number INTEGER NOT NULL, \
        metadata_id INTEGER NOT NULL, \
        FOREIGN KEY(metadata_id) REFERENCES metadata(id)\
        )",
                 &[])
        .unwrap();

    connection
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Part {
    pub signature: Vec<u8>,
    pub part_number: u64,
}

pub fn hash(part: &[u8], nonce: &[u8], part_num: u64) -> Vec<u8> {
    let mut part_nonce: Vec<u8> = part.to_vec();
    part_nonce.extend_from_slice(nonce);
    byteorder::LittleEndian::write_u64(&mut part_nonce, part_num);
    ring::digest::digest(&ring::digest::SHA512, &part_nonce)
        .as_ref()
        .to_vec()
}

pub fn read_part(file_path: &str, part_number: u64) -> std::io::Result<Vec<u8>> {
    let file = std::fs::File::open(&file_path)?;
    let metadata = file.metadata()?;
    let mut reader = std::io::BufReader::new(&file);
    let start_position = reader.seek(SeekFrom::Start(PART_SIZE * part_number))?;
    let size = std::cmp::min(PART_SIZE, metadata.len() - start_position);
    let mut buffer = vec!(0; size as usize);
    reader.read_exact(&mut buffer)?;
    Ok(buffer)
}

pub fn write_part(file_path: &str, part_number: u64, data: &[u8]) -> std::io::Result<()> {
    let file = std::fs::OpenOptions::new()
        .create(true)
        .write(true)
        .truncate(false)
        .open(&file_path)?;
    let mut writer = std::io::BufWriter::new(&file);
    writer.seek(SeekFrom::Start(PART_SIZE * part_number))?;
    writer.write_all(data)?;
    writer.flush()?;
    Ok(())
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Link {
    pub num_parts: u64,
    pub public_key: Vec<u8>,
    pub nonce: Vec<u8>,
}

impl Link {
    pub fn encode(link: &Link) -> String {
        let serialized = bincode::serialize(&link, bincode::Infinite).unwrap();

        let compressed = {
            let mut compressed = Vec::new();
            zopfli::compress(&zopfli::Options::default(),
                             &zopfli::Format::Deflate,
                             &serialized,
                             &mut compressed)
                    .unwrap();
            compressed
        };

        let crc_compressed = {
            let mut crc_compressed = vec!(0; 4);
            let crc = crc::crc32::checksum_ieee(&compressed);
            byteorder::LittleEndian::write_u32(&mut crc_compressed, crc);
            crc_compressed.extend_from_slice(&compressed);
            crc_compressed
        };

        base64::encode_config(&crc_compressed, base64::URL_SAFE)
    }

    pub fn decode(encoded_link: &str) -> Result<Link, String> {
        let crc_compressed = base64::decode_config(encoded_link, base64::URL_SAFE);
        if let Ok(crc_compressed) = crc_compressed {
            let crc = byteorder::LittleEndian::read_u32(&crc_compressed[..4]);
            let compressed = &crc_compressed[4..];
            if crc == crc::crc32::checksum_ieee(compressed) {
                if let Ok(uncompressed) = inflate::inflate_bytes(compressed) {
                    let link = bincode::deserialize(&uncompressed);
                    if let Ok(link) = link {
                        return Ok(link);
                    } else {
                        return Err(String::from("Couldn't deserialize link."));
                    }
                } else {
                    return Err(String::from("Couldn't decompress link."));
                }
            } else {
                return Err(String::from("Couldn't verify link. Is it corrupted?"));
            }
        } else {
            return Err(String::from("Couldn't decode link."));
        }
    }
}

#[test]
fn link_coding() {
    let input_link = Link {
        num_parts: 1,
        public_key: vec!(0; 32),
        nonce: vec!(0; 8),
    };
    let encoded = Link::encode(&input_link);
    let output_link = Link::decode(&encoded).unwrap();
    assert_eq!(input_link, output_link);
}

#[test]
fn test_link_decoding() {
    let encoded_link = "p8e-_-NggAAFKC3-715NfNnfDdJLrhesiT9_vnOfefrsp7-aLix-\
    KNTX_PEfTL2z02aDBsHuZwA=";
    let target_link = Link {
        num_parts: 8,
        public_key: vec![23, 254, 222, 124, 95, 118, 253, 176, 27, 164, 215, 112, 172, 95, 207,
                         207, 137, 190, 55, 103, 155, 229, 250, 130, 208, 163, 225, 18, 142, 131,
                         241, 254],
        nonce: vec![67, 66, 179, 48, 128, 17, 139, 230],
    };
    let link = Link::decode(encoded_link).unwrap();
    assert_eq!(link, target_link);
}
