<commit_before>package noorg.bookparsing.service.impl;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import noorg.bookparsing.domain.Book;
import noorg.bookparsing.domain.Contributor;
import noorg.bookparsing.domain.types.BookCondition;
import noorg.bookparsing.domain.types.BookFormat;
import noorg.bookparsing.domain.types.BookGenre;
import noorg.bookparsing.domain.types.ContributorRole;
import noorg.bookparsing.service.ParsingService;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import au.com.bytecode.opencsv.CSVParser;


/**
 * <p>Copyright 2014 <NAME>
 * 
 * <p>Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * <p>    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * <p>Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * <p>This is where the magic happens. Here we parse a line of the GoodReads CSV 
 * and make a book out of it.
 * 
 * @author <NAME>
 *
 */
public class GoodReadsParsingService implements ParsingService<String, Book> {
	private static final Logger logger = LoggerFactory.getLogger(
			GoodReadsParsingService.class);
	
	// Some possible shelves that might be used to guess the genre
	private static final String SHELF_NON_FICTION = "non-fiction";
	private static final String SHELF_SCI_FI = "sci-fi";
	private static final String SHELF_SCIENCE_FICTION = "science-fiction";
	
	// TODO move this?
	public static SimpleDateFormat sdf = new SimpleDateFormat("yyyy/mm/dd");

	public Book parse(String input) {
		logger.debug("Parsing: {}", input);
		// TODO  finish writing me, null check input
		
		Book book = new Book();
		

		
		/* 
		 * Good reads data..
		 * 
		 * 0 Book Id	
		 * 1 Title	
		 * 2 Author	
		 * 3 Author l-f	
		 * 4 Additional Authors	
		 * 5 ISBN	
		 * 6 ISBN13	
		 * 7 My Rating	
		 * 8 Average Rating	
		 * 9 Publisher	
		 * 10 Binding	(use to get book format)
		 * 11 Number of Pages	
		 * 12 Year Published	
		 * 13 Original Publication Year	
		 * 14 Date Read	
		 * 15 Date Added
		 * 16 Bookshelves with positions	
		 * 17 Bookshelves	(use to get genre)
		 * 18 Exclusive Shelf	(use to get Read State)
		 * 19 My Review	
		 * 20 Spoiler	
		 * 21 Private Notes	
		 * 22 Read Count	
		 * 23 Recommended For	
		 * 24 Recommended By	
		 * 25 Owned Copies	
		 * 26 Original Purchase Date	
		 * 27 Original Purchase Location	
		 * 28 Condition	
		 * 29 Condition Description	
		 * 30 BCID
		 * 
		 */
		CSVParser parser = new CSVParser();
		try {
			String[]  tokens = parser.parseLine(input);
			
			if(tokens.length != 31){
				logger.error("{} produced {} tokens", input, tokens.length);
				logger.debug("{}", (Object[])tokens);
			}
			
			// convert the CSV
			book.setId(tokens[0]);
			// TODO tokenize title further for series information?
			book.setTitle(tokens[1]);
			book.setAuthor(getContributor(tokens[2], ContributorRole.AUTHOR));
			// Skip 4th token (reverse author name)
			book.setAdditionalContributors(parseAdditionalContributors(tokens[4]));
			book.setIsbn(parseISBN(tokens[5]));
			book.setIsbn13(parseISBN(tokens[6]));
			book.setMyRating(parseInt(tokens[7]));
			book.setAverageRating(parseFloat(tokens[8]));
			book.setPublisher(tokens[9]);
			book.setBinding(tokens[10]);			
			book.setNumberOfPages(parseInt(tokens[11]));
			book.setYearOfPublication(parseInt(tokens[12]));
			book.setOriginalPublicationYear(parseInt(tokens[13]));			
			book.setDateRead(convertDate(tokens[14]));
			book.setDateAdded(convertDate(tokens[15]));
			book.setBookshelves(getList(tokens[16]));			
			// TODO maybe use this list to rebuild shelves in order?
			book.setBookshelvesWithPositions(getList(tokens[17]));
			book.setExclusiveShelf(tokens[18]);
			book.setMyReview(tokens[19]);
			// TODO Not sure what this column is/contains. Spoiler of what?
			book.setSpoiler(tokens[20]);
			book.setPrivateNotes(tokens[21]);
			book.setReadCount(parseReadCount(tokens[22]));
			book.setRecommendedFor(tokens[23]);
			book.setRecommendedBy(tokens[24]);
			book.setOwnedCopies(parseInt(tokens[25]));
			book.setPurchaseDate(convertDate(tokens[26]));
			book.setPurchaseLocation(tokens[27]);
			book.setCondition(BookCondition.parse(tokens[28]));
			book.setConditionDescription(tokens[29]);
			book.setBcid(tokens[30]);
			
			// enrich the data
			book.setFormat(BookFormat.parse(book.getBinding()));
			book.setGenre(getGenre(book.getBookshelves()));
			
			// TODO read state..
			
		} catch (IOException e) {
			logger.error("Problem parsing data", e);
		}
		
		return book;
	}
	
	/**
	 * This is an attempt to use the user's bookshelves to get the genre of
	 * the book. See {@link BookGenre} for more about the genres.
	 * 
	 * This is going to largely depend on how a user shelves their books. What
	 * I'm writing will work for my books, but probably not for everyone.<commit_msg>Log book title for unknown bindings.<commit_after>package noorg.bookparsing.service.impl;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import noorg.bookparsing.domain.Book;
import noorg.bookparsing.domain.Contributor;
import noorg.bookparsing.domain.types.BookCondition;
import noorg.bookparsing.domain.types.BookFormat;
import noorg.bookparsing.domain.types.BookGenre;
import noorg.bookparsing.domain.types.ContributorRole;
import noorg.bookparsing.service.ParsingService;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import au.com.bytecode.opencsv.CSVParser;


/**
 * <p>Copyright 2014 <NAME>
 * 
 * <p>Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * <p>    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * <p>Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * <p>This is where the magic happens. Here we parse a line of the GoodReads CSV 
 * and make a book out of it.
 * 
 * @author <NAME>
 *
 */
public class GoodReadsParsingService implements ParsingService<String, Book> {
	private static final Logger logger = LoggerFactory.getLogger(
			GoodReadsParsingService.class);
	
	// Some possible shelves that might be used to guess the genre
	private static final String SHELF_NON_FICTION = "non-fiction";
	private static final String SHELF_SCI_FI = "sci-fi";
	private static final String SHELF_SCIENCE_FICTION = "science-fiction";
	
	// TODO move this?
	public static SimpleDateFormat sdf = new SimpleDateFormat("yyyy/mm/dd");

	public Book parse(String input) {
		logger.debug("Parsing: {}", input);
		// TODO  finish writing me, null check input
		
		Book book = new Book();
		

		
		/* 
		 * Good reads data..
		 * 
		 * 0 Book Id	
		 * 1 Title	
		 * 2 Author	
		 * 3 Author l-f	
		 * 4 Additional Authors	
		 * 5 ISBN	
		 * 6 ISBN13	
		 * 7 My Rating	
		 * 8 Average Rating	
		 * 9 Publisher	
		 * 10 Binding	(use to get book format)
		 * 11 Number of Pages	
		 * 12 Year Published	
		 * 13 Original Publication Year	
		 * 14 Date Read	
		 * 15 Date Added
		 * 16 Bookshelves with positions	
		 * 17 Bookshelves	(use to get genre)
		 * 18 Exclusive Shelf	(use to get Read State)
		 * 19 My Review	
		 * 20 Spoiler	
		 * 21 Private Notes	
		 * 22 Read Count	
		 * 23 Recommended For	
		 * 24 Recommended By	
		 * 25 Owned Copies	
		 * 26 Original Purchase Date	
		 * 27 Original Purchase Location	
		 * 28 Condition	
		 * 29 Condition Description	
		 * 30 BCID
		 * 
		 */
		CSVParser parser = new CSVParser();
		try {
			String[]  tokens = parser.parseLine(input);
			
			if(tokens.length != 31){
				logger.error("{} produced {} tokens", input, tokens.length);
				logger.debug("{}", (Object[])tokens);
			}
			
			// convert the CSV
			book.setId(tokens[0]);
			// TODO tokenize title further for series information?
			book.setTitle(tokens[1]);
			book.setAuthor(getContributor(tokens[2], ContributorRole.AUTHOR));
			// Skip 4th token (reverse author name)
			book.setAdditionalContributors(parseAdditionalContributors(tokens[4]));
			book.setIsbn(parseISBN(tokens[5]));
			book.setIsbn13(parseISBN(tokens[6]));
			book.setMyRating(parseInt(tokens[7]));
			book.setAverageRating(parseFloat(tokens[8]));
			book.setPublisher(tokens[9]);
			book.setBinding(tokens[10]);			
			book.setNumberOfPages(parseInt(tokens[11]));
			book.setYearOfPublication(parseInt(tokens[12]));
			book.setOriginalPublicationYear(parseInt(tokens[13]));			
			book.setDateRead(convertDate(tokens[14]));
			book.setDateAdded(convertDate(tokens[15]));
			book.setBookshelves(getList(tokens[16]));			
			// TODO maybe use this list to rebuild shelves in order?
			book.setBookshelvesWithPositions(getList(tokens[17]));
			book.setExclusiveShelf(tokens[18]);
			book.setMyReview(tokens[19]);
			// TODO Not sure what this column is/contains. Spoiler of what?
			book.setSpoiler(tokens[20]);
			book.setPrivateNotes(tokens[21]);
			book.setReadCount(parseReadCount(tokens[22]));
			book.setRecommendedFor(tokens[23]);
			book.setRecommendedBy(tokens[24]);
			book.setOwnedCopies(parseInt(tokens[25]));
			book.setPurchaseDate(convertDate(tokens[26]));
			book.setPurchaseLocation(tokens[27]);
			book.setCondition(BookCondition.parse(tokens[28]));
			book.setConditionDescription(tokens[29]);
			book.setBcid(tokens[30]);
			
			// enrich the data
			final BookFormat format = BookFormat.parse(book.getBinding());
			if(BookFormat.UNKNOWN.equals(format)){
				logger.warn("{} has unknown format from binding: {}", 
						book.getTitle(), book.getBinding());
			}
			book.setFormat(format);
			book.setGenre(getGenre(book.getBookshelves()));
			
			// TODO read state..
			
		} catch (IOException e) {
			logger.error("Problem parsing data", e);
		}
		
		return book;
	}
	
	/**
	 * This is an attempt to use the user's bookshelves to get the genre of
	 * the book. See {@link BookGenre} for more about the genres.
	 * 
	 * This is going to largely depend on how a user shelves their books. What
	 * I'm writing will work for my books, but probably not for everyone.
{'fraction_non_alphanumeric': 0.10910424498028788, 'fraction_numerical': 0.022004217475016043, 'mean_word_length': 3.8052863436123348, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2406666', 'n_tokens_mistral': 3994, 'n_tokens_neox': 3278, 'n_words': 953}